Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@
!src/lsst/
!examples/
!alembic/
!packages/
!LICENSE
!README.md
!Makefile
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ SHELL := /bin/bash
GIT_BRANCH := $(shell git branch --show-current)
PY_VENV := .venv/
UV_LOCKFILE := uv.lock
WEB_CANVAS_STEM := cm-canvas-bundle
WEB_PACKAGE_ROOT := packages/cm-web/

#------------------------------------------------------------------------------
# Default help target (thanks ChatGPT)
Expand Down Expand Up @@ -131,8 +133,12 @@ run-worker: run-compose
alembic upgrade head
python3 -m lsst.cmservice.daemon

.PHONY: packages
packages:
$(MAKE) -C packages/cm-canvas rebuild

.PHONY: run-web
run-web: $(PY_VENV)
run-web: $(PY_VENV) packages
uv run web

.PHONY: migrate
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ https://cm-service.lsst.io.

## Developer Quick Start

You can build and run `cm-service` on any system which has Python 3.12 or greater, `uv`, `make`, and Docker w/ the
You can build and run `cm-service` on any system which has Python 3.13 or greater, `uv`, `make`, and Docker w/ the
Docker Compose V2 CLI plugin (this includes, in particular, recent MacOS with Docker Desktop). Proceed as
follows:

Expand Down
20 changes: 20 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,26 @@ services:
migratedb:
condition: service_completed_successfully

cmweb:
profiles:
- full
- client
build:
context: .
dockerfile: docker/Dockerfile
target: cmweb
env_file:
- path: .env
required: false
environment:
CM_ENDPOINT: http://cmservice:8080/cm-service
CM_API_VERSION: v2
networks:
- cmservice
depends_on:
cmservice:
condition: service_healthy

postgresql:
image: "postgres:16"
hostname: "postgresql"
Expand Down
24 changes: 23 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ ENDRUN
EXPOSE ${ASGI_PORT}


#==============================================================================
# NODEJS BUILDER IMAGE
FROM node:lts-alpine AS node-builder

WORKDIR /home/node
COPY packages/ ./packages/
RUN <<ENDRUN
set -e
apk add --no-cache make
cd packages/cm-canvas
make rebuild
ENDRUN


#==============================================================================
# BUILDER IMAGE
FROM base-image AS build-image
Expand Down Expand Up @@ -81,7 +95,7 @@ COPY . .
# Install project production dependency graph in /opt/venv
RUN --mount=type=cache,target=/root/.cache/uv <<ENDRUN
set -e
/bin/uv sync --no-group dev --no-install-project --no-editable
/bin/uv sync --no-group dev --no-install-project --no-editable --all-packages
ENDRUN


Expand Down Expand Up @@ -121,3 +135,11 @@ COPY --from=htcondor /opt/htcondor /opt/htcondor

USER lsstsvc1
CMD ["lsst.cmservice.daemon"]


#==============================================================================
# RUNTIME IMAGE - Web
FROM cmservice AS cmweb

# COPY --from=node-builder packages/cm-canvas/dist
CMD ["lsst.cmservice.web.libexec.go"]
141 changes: 141 additions & 0 deletions packages/cm-canvas/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.*
!.env.example

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist
.output

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Sveltekit cache directory
.svelte-kit/

# vitepress build output
**/.vitepress/dist

# vitepress cache directory
**/.vitepress/cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# Firebase cache directory
.firebase/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v3
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Vite files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vite/
1 change: 1 addition & 0 deletions packages/cm-canvas/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock.json
6 changes: 6 additions & 0 deletions packages/cm-canvas/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"bracketSameLine": false,
"bracketSpacing": true,
"singleQuote": false,
"plugins": ["prettier-plugin-svelte"]
}
17 changes: 17 additions & 0 deletions packages/cm-canvas/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
NPM_LOCKFILE := package-lock.json
CM_STATIC_CONTENT_DIR ?= ../cm-web/src/lsst/cmservice/web/static

$(NPM_LOCKFILE):
npm install

.PHONY: clean
clean:
rm -f $(NPM_LOCKFILE)
rm -rf dist/

.PHONY: dist
dist: $(NPM_LOCKFILE)
npm run build
cp dist/*.iife.js $(CM_STATIC_CONTENT_DIR)

rebuild: clean dist
119 changes: 119 additions & 0 deletions packages/cm-canvas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# CM Canvas

The Canvas is a Svelte Flow component that is designed to allow the easy and visual creation and editing of a Campaign graph on a reactive canvas. The component is built as an Immediately Invoked Function Expression (IIFE).

## Dev Setup

To work on the Canvas, you will be using Svelte 5, Svelte Flow, and Typescript along with Vite and NPM.

To set up the dev environment, you must have `npm` and `make` installed and available. This is best accomplished by installing NodeJS v24.

Basic setup is performed using the `Makefile`:

```
make rebuild
```

; or:

```
npm install
npm run build
```

## Conventions

This component uses Svelte, which combines script, DOM, and style details in a single `.svelte` file for each part of the component.

This project uses `prettier` to format files in the project.

This component should be developed as a TypeScript project.

## Dev Test

The canvas tool can be run in a dev mode that supports hotloading and a local web server for testing or debugging simple operations; this mode is not integrated with the cm web tool at large. When using the canvas IIFE in another project as an embedded component, there is no `node` runtime dependency.

This test mode uses the `index.html` and `dev.js` files to mount the Svelte-Flow component in a div named "app" on an otherwise unremarkable index page.

```
npm run dev
```

## Build

The canvas tool is compiled to an IIFE module and accompanying CSS file with the vite packaging utility.

```
npm run build
```

The resulting dist artifacts must be made available to the cm web tool by copying them to the `static/` directory in the web tool source tree, which is performed automatically by the `make dist` target.

## Integration

This canvas component is integrated with CM Web via the inclusion of the compiled IIFE and CSS bundles. The script should be run to mount the component on a named div as expected by the `index.js` file, which is the "entrypoint" of the component.

For NiceGUI, a page can be made to include the bundles via header tags, and the script mounted to a div like so:

```
from nicegui import ui

ui.add_head_html("""<script src="/static/cm-canvas-bundle.iife.js"></script>""")

ui.element("div").classes("w-full h-full").props('id="flow-container"')
ui.run_javascript("""
window.flowInstance = initializeFlow('flow-container', {});
""")
```

### Properties

The `initializeFlow` function in the IIFE component passes a set of properties to the Canvas:

- `nodes`: An array of Node objects. These objects should conform to the Svelte Flow implementation of a Node. A default set of nodes (i.e., "START" and "END") will be used when this property is null or missing.
- `edges`: An array of Edge objects. Again, these are Svelte Flow Edges. This defaults to an empty array (i.e., no Nodes are connected) when this property is null or missing.
- `onClick`: A Javascript function definition to be used with the "Edit" button available on Step on the Canvas.

### Example Node
```
{
"id": "unique id",
"type": "step | start | end",
"data": { "name": "node name" },
"position": { "x": 0, "y": 0 }
}
```

Note the "position" of a Node doesn't really matter when initializing the canvas, because the component will call one of the layout functions automatically in an "onMount" lifecycle callback.

### Example Edge
```
{
"id": "source--target",
"source": "id",
"target": "id"
}
```

Note that the "source" and "target" keys need the unique IDs of the subject Nodes, not their names. The handle positions of an Edge will be manipulated automatically by the Canvas component when one of the layout functions is called. Generally, this means that "Horizontal" layout connects edges from left-to-right, and "Vertical" connects them bottom-to-top. It is not necessary to specify handle positions when supplying edges in the initializer.

## Usage

The Canvas is meant for campaign designers to layout a Campaign using nodes and edges. Only basic campaign graph structure is configurable with this Canvas; additional configurations are made through other CM interfaces. This component does not perform graph validation.

A canvas by default includes "START" and "END" nodes. New _Steps_ can be added by dragging an edge out of an existing node and ending on a blank part of the canvas where the new Step should be placed.

When hovering over a Node on the Canvas, four handles appear; the top and left handles are target/destination handles and new connections cannot be pulled out of these. The bottom and right handles are source handles and new connections can be pulled out of these. A new connection can be dropped on a blank area of the Canvas to create a new Step node, or connected to the top or left handle of an existing node -- a valid connection will be highlighted in green.

A new Step node can be given a _Name_. A Step node also has an "Edit" button which performs the action configured by the `onClick` property passed into the component's initialization. By convention, the CM Web UI causes this button to emit a Javascript event which in turn allows an Edit dialog to appear.

While Step names are arbitrary and can be changed, they are given an initial name based on the order the Node was added to the Canvas, starting with "Step 1".

Note that this canvas component does not itself save or fully configure campaigns or steps: it is only through one of the action buttons that the configured graph is sent as data to the CM Web ui for additional configuration and setup.

The canvas also includes two panel buttons that are self-contained (i.e., they do not emit events for external handling). These are auto-layout buttons to redesign the current graph in either a vertical or horizontal layout. Larger graphs can be complicated to read; these layout buttons help designers keep the graph clean.

## Events
The Canvas component has the following event listeners by default:

- `canvasExport`: Causes the current graph on the canvas to be serialized and returned as data with an answering `canvasExported` event. The current positions of the nodes are not part of the export: only the shape of the graph and core identity of the nodes (i.e., their unique ID and Name values) are exported. No other state for a Node is ever stored or saved by the Canvas.
Loading