@peter.prib/renderengine 0.2.11 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -6
- package/REFERENCE.md +132 -2
- package/dist/renderengine-workspace.umd.js +3346 -1245
- package/dist/renderengine.umd.js +1509 -158
- package/package.json +1 -1
- package/src/charting/ModelCheckDialog.js +10 -3
- package/src/charting/ModelComparisonDialog.js +10 -3
- package/src/charting/ModelFileFormat.js +4 -1
- package/src/charting/archimate/ArchimateCanvasEditor.js +14 -2
- package/src/charting/archimate/ArchimateElementTemplate.js +23 -0
- package/src/charting/arrangement/layouts.js +9 -4
- package/src/charting/datamodel/DataModel.js +18 -71
- package/src/charting/datamodel/DataModelCompared.js +1 -31
- package/src/charting/diagramModel/ListCompared.js +35 -0
- package/src/charting/model/Benchmark.js +59 -0
- package/src/charting/model/EditableModel.js +104 -0
- package/src/charting/{datamodel/DataModelEditCommand.js → model/ModelEditCommand.js} +47 -47
- package/src/charting/requirements/AzureDevOpsApi.js +55 -0
- package/src/charting/requirements/GitLabApi.js +43 -0
- package/src/charting/requirements/JiraApi.js +69 -0
- package/src/charting/requirements/RequirementsChecker.js +76 -0
- package/src/charting/requirements/RequirementsCompared.js +33 -0
- package/src/charting/requirements/RequirementsFileFormat.js +32 -0
- package/src/charting/requirements/RequirementsImportFormats.js +48 -0
- package/src/charting/requirements/RequirementsModel.js +305 -0
- package/src/charting/requirements/Storyboard.js +94 -0
- package/src/charting/requirements/TrackerApi.js +142 -0
- package/src/charting/requirements/TrackerExport.js +274 -0
- package/src/charting/requirements/index.js +12 -0
- package/src/charting/requirements/trackerApis.js +21 -0
- package/src/index.js +7 -0
- package/src/workspace/Workspace.js +329 -321
- package/src/workspace/index.js +2 -0
- package/src/workspace/parts/ArchimateCanvasPart.js +31 -1
- package/src/workspace/parts/DataModelPart.js +668 -735
- package/src/workspace/parts/EditorPart.js +1 -1
- package/src/workspace/parts/ModelFile.js +3 -1
- package/src/workspace/parts/ModelHosted.js +176 -0
- package/src/workspace/parts/ModelsPart.js +146 -7
- package/src/workspace/parts/RequirementsPart.js +465 -0
- package/src/workspace/storage/ModelLibrary.js +8 -2
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# RenderEngine
|
|
2
2
|
|
|
3
|
-
**An ArchiMate modeller, a data modeller, a diagram editor and a chart library that run
|
|
4
|
-
no install and no runtime dependencies.**
|
|
3
|
+
**An ArchiMate modeller, a data modeller, a requirements story map, a diagram editor and a chart library that run
|
|
4
|
+
in any browser tab, with no install and no runtime dependencies.**
|
|
5
5
|
|
|
6
6
|
Open an Archi model, edit it the way you would in Archi, and save it back to the same `.archimate` file, without
|
|
7
7
|
installing anything. Model data at three levels, governed and checked the way DAMA-DMBOK2 has it, with banking at
|
|
@@ -167,6 +167,20 @@ the browser and backed up.
|
|
|
167
167
|
|
|
168
168
|
---
|
|
169
169
|
|
|
170
|
+
## Requirements and story maps
|
|
171
|
+
|
|
172
|
+
Bring in the epics and stories your team already has: a Jira, Azure DevOps or GitLab export (CSV or JSON), or a
|
|
173
|
+
storyboard written in Markdown. Read as a **story map** (epics across, releases down) or a **backlog**, each story
|
|
174
|
+
carries its "As a … I want … so that …", its acceptance criteria (read from their own field, a list in the
|
|
175
|
+
description, or Given/When/Then) and its **traces** to what realises it: an ArchiMate requirement or work package,
|
|
176
|
+
a component, a data concept, entity or table. Drag a card to another epic or release; the toolbar says how many
|
|
177
|
+
stories say when they are done and how many trace to something, and **Check model** lists what is missing.
|
|
178
|
+
|
|
179
|
+
- **Every model alike**: kept, compared and merged as the ArchiMate and data models are, a newer tracker export
|
|
180
|
+
brought in over the old one replacing what changed.
|
|
181
|
+
- **Yours to extend**: a host says what can be traced to, what a story's live state is, and how to find stories by
|
|
182
|
+
meaning (a vector store) — the SqlEngine workbench does all three.
|
|
183
|
+
|
|
170
184
|
## The diagram editor
|
|
171
185
|
|
|
172
186
|
The ArchiMate modeller is built on a general diagram editor, and every editing feature above is part of that
|
|
@@ -237,10 +251,12 @@ page:
|
|
|
237
251
|
</script>
|
|
238
252
|
```
|
|
239
253
|
|
|
240
|
-
Use `editorType: 'diagram-editor'` with `DiagramEditorPart` for the diagram editor,
|
|
241
|
-
with `DataModelPart` for the data modeller
|
|
242
|
-
|
|
243
|
-
|
|
254
|
+
Use `editorType: 'diagram-editor'` with `DiagramEditorPart` for the diagram editor, `editorType: 'data-model'`
|
|
255
|
+
with `DataModelPart` for the data modeller, and `editorType: 'requirements'` with `RequirementsPart` for the story
|
|
256
|
+
map. The models are plain JavaScript APIs (`ArchimateModel`, `DiagramModel`, `DataModel`, `RequirementsModel`), so
|
|
257
|
+
you can also build, check, score, search, report on and export models with no UI at all, under Node too:
|
|
258
|
+
`new DataModel(spec).checker().run()`, `new SqlDdl().write(model)`,
|
|
259
|
+
`new RequirementsModel(new TrackerExport().read(csv)).coverage()`.
|
|
244
260
|
|
|
245
261
|
---
|
|
246
262
|
|
package/REFERENCE.md
CHANGED
|
@@ -881,6 +881,12 @@ types — see `ARCHITECTURE.md`):
|
|
|
881
881
|
`LevelSchema.resolveReference(ref)`: the whole text as a table (by id or name, any case), else what is before its
|
|
882
882
|
last dot as the table and the rest as its column — `{ entity, column }`, or null.
|
|
883
883
|
|
|
884
|
+
**Edit ▸ Lay out.** The level shown is laid out again by one of the arrangement's layouts (`LAYOUTS`: grid, circle,
|
|
885
|
+
a tree down or right, force-directed, by kind — the same ones a diagram's Arrange ▸ Layout uses), its relationships
|
|
886
|
+
guiding the trees and the force, only what the perspective draws moving (`part.layoutCommand(kind)`, one undo
|
|
887
|
+
step; where each box goes is kept as a drag would keep it). Not the mapping view or the other views, which lay
|
|
888
|
+
themselves out.
|
|
889
|
+
|
|
884
890
|
**A dimensional scheme.** At the logical and physical levels an entity may have a role (`EntityRole`, registered as
|
|
885
891
|
relationship kinds are): a **fact** (`FactRole`, `role: 'fact'`: what is measured, at a stated `grain`, its
|
|
886
892
|
attributes' `measure` additive, semi-additive or non-additive) or a **dimension** (`DimensionRole`: its `scdType`,
|
|
@@ -1623,7 +1629,10 @@ sharing the spec's data cannot reach them). File ▸ New ▸ **From base: …**
|
|
|
1623
1629
|
`models.baseItems()`) opens a model as a copy of one; Tools ▸ **Compare with** and **Merge from** offer them, after
|
|
1624
1630
|
the other open models (`models.toolItems(part)`); File ▸ **Duplicate model** (`models.duplicate(part)`) opens a copy
|
|
1625
1631
|
of the model in front, written and read back through its own file format. A copy, from a base or duplicated, is named
|
|
1626
|
-
"… (copy)" (`ModelsPart.copyName`, the suffix once).
|
|
1632
|
+
"… (copy)" (`ModelsPart.copyName`, the suffix once). A host whose reference models change (a live database
|
|
1633
|
+
whose schema changed) gives them afresh with `models.setBases(specs)`; what is open is left alone. File ▸ New ▸ **View base: …**
|
|
1634
|
+
(`models.viewBase(i)`) opens a base to look at: a tab of its own copy, named "… (view)", that can be laid out and
|
|
1635
|
+
moved about but is never kept (`part.keepOff`).
|
|
1627
1636
|
|
|
1628
1637
|
**Kept in this browser, and backed up.** With `keep: true` in its spec, a `ModelsPart` keeps every model it holds
|
|
1629
1638
|
in this browser, so work outlives the page without saving a file: a moment after each edit (`ModelsPart.KEEP_DELAY`)
|
|
@@ -1640,6 +1649,38 @@ as a history, and under its plain name too (`keepUnstamped`); where a folder can
|
|
|
1640
1649
|
permission refused) they download instead. `backups.log()` records each (newest first, `LOG_MAX`), and
|
|
1641
1650
|
`backups.daysSinceLast()` how long ago the last was. `examples/archimate.html` keeps its models.
|
|
1642
1651
|
|
|
1652
|
+
**A host's live status, and its menu items.** A host subclass of `ArchimateCanvasPart` says what it knows of an
|
|
1653
|
+
element that the model does not: `elementStatus(element)` → `{ text, color }` (or null) is drawn as a pill inside
|
|
1654
|
+
the bottom right of each of the element's boxes, asked afresh whenever the view is drawn and never written to the
|
|
1655
|
+
model; `elementMenuExtras(element)` → menu items end a box's right-click menu and the element's menu in the model
|
|
1656
|
+
tree. The canvas editor takes them as options (`ArchimateCanvasEditor`'s `elementStatus`, `elementMenuExtras`;
|
|
1657
|
+
the part hands them over in `editorHooks()`); a box's status is its template's `status` (`statusPill(ctx)`).
|
|
1658
|
+
|
|
1659
|
+
```js
|
|
1660
|
+
class LiveArchitecture extends ArchimateCanvasPart {
|
|
1661
|
+
elementStatus(el) { const s = states.get(el.id); return s ? { text: s, color: s === 'deployed' ? '#2e7d32' : '#f9a825' } : null; }
|
|
1662
|
+
elementMenuExtras(el) { return transitions(el.id).map(t => ({ label: t, action: () => fire(el.id, t) })); }
|
|
1663
|
+
}
|
|
1664
|
+
```
|
|
1665
|
+
|
|
1666
|
+
**Kept where the host says.** A host keeps the models itself by giving a library among the workspace's services
|
|
1667
|
+
(`ws.services = { modelLibrary }`, reaching the part as `ctx.modelLibrary`): anything with `ModelLibrary`'s methods
|
|
1668
|
+
(`keep`, `load`, `list`, `openOnes`, `setOpen`, `remove`) and a `label`, which the File menu and the tab menu use in
|
|
1669
|
+
place of **This browser** (`models.keptWhere`). An entry is the model in its editor's file format, so the host
|
|
1670
|
+
stores it as it likes — a database row, a file, a graph read from it. The backup folder's handle stays in the
|
|
1671
|
+
browser's store.
|
|
1672
|
+
|
|
1673
|
+
```js
|
|
1674
|
+
ws.services = { modelLibrary: new ModelLibrary(myStore, { label: 'The engine' }) }; // myStore: a KeyValueStore
|
|
1675
|
+
```
|
|
1676
|
+
|
|
1677
|
+
**Proposals.** A model offered as a change to the one in front — by an agent, a colleague, an import — waits as a
|
|
1678
|
+
proposal: `models.propose({ title, rationale, source, spec })`, `spec` a model as the editor takes it (as a base is
|
|
1679
|
+
given). Tools ▸ **Proposals (n)** lists them (`models.proposalItems(part)`); **Review and merge…**
|
|
1680
|
+
(`models.reviewProposal(part, p)`) is the merge dialog with who proposed it and why above the differences
|
|
1681
|
+
(`ModelMergeDialog`'s `note`), so what is taken is chosen and is one undo step; taking it, or **Dismiss**
|
|
1682
|
+
(`models.dismissProposal(p)`), removes it from the list.
|
|
1683
|
+
|
|
1643
1684
|
```js
|
|
1644
1685
|
ws.addParts([{ id: 'models', type: 'models', editorType: 'archimate-canvas', newModel: { analysis: true },
|
|
1645
1686
|
models: [{ id: 'modeller', title: 'Sample model', elements: [...], views: [...] }] }]);
|
|
@@ -1738,7 +1779,8 @@ editor.colourByCommand('property:Lifecycle', { invest: '#98fb98', maintain: '#ff
|
|
|
1738
1779
|
**Layouts, in every editor.** The toolbar's **Arrange** menu has **Layout** (**Layout the view** with fewer than two
|
|
1739
1780
|
shapes selected): **Grid**, **Circle**, **Tree, top down**, **Tree, left to right** (along the lines between the
|
|
1740
1781
|
shapes: a parent centred over its children; a shape no line reaches is a root), **Force-directed** (lines pull,
|
|
1741
|
-
shapes push apart
|
|
1782
|
+
shapes push apart, within a frame round where they were, so one no line reaches stays near the rest; the same
|
|
1783
|
+
input gives the same result) and **By kind, a row each** (the shape class in the
|
|
1742
1784
|
template editor, the layer on the ArchiMate canvas). The shapes keep the top-left corner of the area they took;
|
|
1743
1785
|
what a container holds goes with it; one undo step; a locked shape stops it. On the ArchiMate canvas, an element
|
|
1744
1786
|
with parts on the view (the targets of its composition and aggregation relationships) has **Nest its parts inside**
|
|
@@ -2520,6 +2562,94 @@ class. Library code imports its collaborators directly rather than looking them
|
|
|
2520
2562
|
`undo()` rather than checking `instanceof Command`. A command built from either bundle therefore
|
|
2521
2563
|
works in either bundle's history.
|
|
2522
2564
|
|
|
2565
|
+
### Models edited as a whole: `EditableModel` and `ModelHosted`
|
|
2566
|
+
|
|
2567
|
+
A model edited as a whole rather than view by view extends `EditableModel` (`DataModel`, `RequirementsModel`): a
|
|
2568
|
+
`name`; changed since saved (`edited()`, `markSaved()`, `isDirty()`, `onEdit(fn)`); an undo history
|
|
2569
|
+
(`model.history`), every edit one step on it — `model.editCommand(label, change)` keeps the model before and after
|
|
2570
|
+
as its `toJSON()` and puts it back with `restore` (the fields the class lists in `static STATE`); and
|
|
2571
|
+
`compareWith(other)` over its `comparedKinds()`, which merges the other's into it. A list of things with ids is
|
|
2572
|
+
compared and merged by a `ListCompared` subclass (`list(model)`, `content(item)`, `requires(item)`).
|
|
2573
|
+
|
|
2574
|
+
Its part mixes in `ModelHosted` (`class DataModelPart extends ModelHosted(ChartPart)`): the file (`part.file`, a
|
|
2575
|
+
`ModelFile`), `keepId`/`keepEntry()` for a `ModelsPart`, the File menu (Open…, Save, Save As…, **Import ▸** each
|
|
2576
|
+
format that only reads — another tool's export, `format.exports === false`, opened as a new model — then New,
|
|
2577
|
+
Duplicate, Close model, where models are kept), `undoItems()`, `runModelEdit(command)` (then `afterModelEdit()`,
|
|
2578
|
+
the part's redraw) and `readInto({ accept, command, … })` (a file read into the model as one undo step).
|
|
2579
|
+
|
|
2580
|
+
**The bench.** `Benchmark.of(model)` is what a model is measured by — `[{ key, label, value, better: 'lower' |
|
|
2581
|
+
'higher', format }]`: every model's check warnings and findings (`Benchmark.findings`), and a model's own
|
|
2582
|
+
`benchmark()` beyond them (a data model's Scorecard total, a requirements model's share of stories with
|
|
2583
|
+
acceptance criteria and of stories traced). `Benchmark.compare(before, after)` says of each whether it got better,
|
|
2584
|
+
worse or stayed, `Benchmark.text(rows)` in a line. A `ModelsPart` proposal's review says how the proposal, taken
|
|
2585
|
+
whole, would move them (`modelsPart.proposalBench(part, proposal)`), so an agent's suggestion is judged by the
|
|
2586
|
+
model's own checks. `modelsPart.propose({ title, rationale, source, spec | entry, onSettle })` takes the model as
|
|
2587
|
+
a spec or as an `entry` in one of the editor's file formats (`{ format, data }`, as a kept model is — what a
|
|
2588
|
+
server hands back); `onSettle(outcome, { bench })` hears whether it was `'taken'` (with how what was taken moved
|
|
2589
|
+
the bench) or `'dismissed'`, once, so whoever proposed it can record the outcome. A host adds its own tools for the model in front
|
|
2590
|
+
— an agent to run on it — as `modelTools(part, models)` among the workspace's services (`workspace.services`);
|
|
2591
|
+
their items end the Tools menu.
|
|
2592
|
+
|
|
2593
|
+
### Requirements: epics, stories and the story map
|
|
2594
|
+
|
|
2595
|
+
`RequirementsModel` holds what a system is to do as an agile team writes it: **epics**, the **stories** that deliver
|
|
2596
|
+
them ("As a … I want … so that …": `as`, `want`, `soThat`), each with **acceptance criteria**, sliced into
|
|
2597
|
+
**releases**, and **traces** from a story to what realises it in the other models — `{ kind, ref, name?, model? }`,
|
|
2598
|
+
`kind` one of `RequirementsModel.TRACE_KINDS` (an ArchiMate requirement or work package, an element, a data
|
|
2599
|
+
concept, entity or table) or any other. `storyMap()` gives the map: `columns` the epics (then *No epic*), `rows`
|
|
2600
|
+
the releases (then *Not planned*), `cell(epicId, releaseId)` the stories there. `tracingTo(ref, kind?)` says what
|
|
2601
|
+
a change to something touches; `coverage()` how complete it is (stories with criteria, traced; each a share);
|
|
2602
|
+
`checker()` (`RequirementsChecker`) what is missing — no criteria, no narrative, traced to nothing (or, with
|
|
2603
|
+
`resolve(trace)`, to something not there), no epic, an empty epic, two stories of one name. Every edit is a
|
|
2604
|
+
command (`saveStoryCommand`, `moveStoryCommand`, `traceCommand`, `bringCommand`, …), merged and compared as the
|
|
2605
|
+
other models are (a story brings its epic and release). Saved as `.requirements.json` (`RequirementsFileFormat`).
|
|
2606
|
+
|
|
2607
|
+
Read from what teams already have — each an import-only format, and `format.spec(text)` for bringing into an
|
|
2608
|
+
open model:
|
|
2609
|
+
|
|
2610
|
+
- **A tracker's export** (`TrackerExportFileFormat`, `TrackerExport`): Jira, Azure DevOps or GitLab, CSV or their
|
|
2611
|
+
API's JSON, told apart by its columns. Epics by issue type (GitLab's from its issues' epic), stories under them,
|
|
2612
|
+
status, priority, points, labels and release (fix version, iteration, milestone); criteria from their own field,
|
|
2613
|
+
else from the description (an "Acceptance criteria" list, or Given/When/Then); the sentence from the description
|
|
2614
|
+
or title. Ids are the tracker's keys, so a newer export brought in replaces what it had. Where each tracker keeps
|
|
2615
|
+
each field is data (`TRACKERS`); a team's own column is given with `new TrackerExport({ columns: { criteria:
|
|
2616
|
+
['Done when'] } })` (in Jira's JSON, a custom field's key).
|
|
2617
|
+
- **A tracker, live** (`TrackerApi`; `JiraApi`, `AzureDevOpsApi`, `GitLabApi`): the issues a connection names, read
|
|
2618
|
+
through the tracker's API page by page — Jira by JQL (a project's by default; Cloud with an email and API token,
|
|
2619
|
+
Server / Data Center with a personal access token), Azure DevOps by WIQL (a project's work items but its tasks,
|
|
2620
|
+
200 at a time, each linked to its page in Boards), GitLab a project's issues (open and closed, filtered as its
|
|
2621
|
+
issues API filters). `read()` is the JSON `TrackerExport` reads, `model()` the model's spec, named as the
|
|
2622
|
+
connection is; at most `maxIssues` (5000) are read, `truncated` saying when there were more. `fetch` is given,
|
|
2623
|
+
since a tracker's token belongs where the request is made (a server); an error says what the tracker answered,
|
|
2624
|
+
never the token. What a connection asks for is each class's `fields` (`trackerConnectionFields()`: per tracker,
|
|
2625
|
+
`{key, label, type?, choices?, required?, secret?, placeholder?, hint?}`), for a host to build its form from;
|
|
2626
|
+
`problems()` what is missing before the tracker is asked.
|
|
2627
|
+
- **A storyboard** (`StoryboardFileFormat`, `Storyboard`): Markdown — a `#` title names it, `##` are epics (or any
|
|
2628
|
+
heading saying *Epic*), `###` stories (or *Story*); "As a … I want …" list items under an epic are stories; a
|
|
2629
|
+
picture is kept by its caption.
|
|
2630
|
+
|
|
2631
|
+
```javascript
|
|
2632
|
+
const { RequirementsModel, TrackerExport } = window.RenderEngine;
|
|
2633
|
+
const model = new RequirementsModel(new TrackerExport().read(jiraCsvText));
|
|
2634
|
+
model.storyMap().columns.map(c => c.name); // the epics, across
|
|
2635
|
+
model.coverage().criteriaShare; // how many stories say when they are done
|
|
2636
|
+
|
|
2637
|
+
// The same, live — on a server, where the token is kept:
|
|
2638
|
+
const api = trackerApi({ tracker: 'jira', name: 'Shop backlog', baseUrl: 'https://shop.atlassian.net',
|
|
2639
|
+
user: 'me@example.com', token: process.env.JIRA_TOKEN, project: 'SHOP' }, { fetch });
|
|
2640
|
+
const live = new RequirementsModel(await api.model());
|
|
2641
|
+
```
|
|
2642
|
+
|
|
2643
|
+
`RequirementsPart` (type `requirements`, in a `ModelsPart` with `editorType: 'requirements'`) shows the model as
|
|
2644
|
+
the **story map** (a card per story — its criteria, traces, points, status — dragged to another epic or release,
|
|
2645
|
+
or before another card; + adds one in a cell) or the **backlog** (a row per story); a card opened edits the story,
|
|
2646
|
+
right-clicked traces it (Trace to…) or removes it. Model ▸ New story / epic / release, **Bring in stories** (from a
|
|
2647
|
+
tracker's export or a storyboard, one undo step), **Check model…**; the toolbar says how complete it is. A host
|
|
2648
|
+
extends it by subclass: `traceTargets()` (what can be traced to, suggested in the dialog), `resolveTrace(trace)`
|
|
2649
|
+
(for the check), `storyStatus(story)` (a live state on the card), and `searchByMeaning(query)` — a promise of
|
|
2650
|
+
`[{ id, score }]` — which adds Model ▸ **Find by meaning…** (results listed in the `ModelCheckDialog`, which takes a
|
|
2651
|
+
`title` and `empty` text for any list of things to open).
|
|
2652
|
+
|
|
2523
2653
|
### Toolbars and dialogs
|
|
2524
2654
|
|
|
2525
2655
|
`MenuBar` renders a row of toolbar items and has no controls of its own. Each editor contributes its
|