@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
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { parseCsvTable } from '../../csvTable.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where each tracker's export keeps what a story needs: per tracker, the
|
|
5
|
+
* column names (CSV) each field may be under, first found wins; which issue
|
|
6
|
+
* types are epics; how its list fields are separated. One definition per
|
|
7
|
+
* tracker — the CSV and the JSON readers both map onto these fields. A host
|
|
8
|
+
* gives its own (a custom field's column) through `new TrackerExport({ columns })`.
|
|
9
|
+
*/
|
|
10
|
+
export const TRACKERS = Object.freeze({
|
|
11
|
+
jira: {
|
|
12
|
+
label: 'Jira',
|
|
13
|
+
// Columns only its exports have, to tell it by.
|
|
14
|
+
marks: ['Issue key', 'Issue Type'],
|
|
15
|
+
columns: {
|
|
16
|
+
key: ['Issue key', 'Key'], id: ['Issue id'], type: ['Issue Type'], title: ['Summary'],
|
|
17
|
+
description: ['Description'], parent: ['Parent', 'Parent id', 'Parent key', 'Custom field (Epic Link)', 'Epic Link'],
|
|
18
|
+
status: ['Status'], priority: ['Priority'],
|
|
19
|
+
points: ['Custom field (Story Points)', 'Story Points', 'Custom field (Story point estimate)', 'Story point estimate'],
|
|
20
|
+
release: ['Fix Version/s', 'Fix versions', 'Sprint'], labels: ['Labels'],
|
|
21
|
+
criteria: ['Custom field (Acceptance Criteria)', 'Acceptance Criteria'], url: [],
|
|
22
|
+
},
|
|
23
|
+
epicTypes: ['Epic'], skipTypes: ['Sub-task', 'Subtask'], labelSeparator: /[\s,]+/,
|
|
24
|
+
},
|
|
25
|
+
azure: {
|
|
26
|
+
label: 'Azure DevOps',
|
|
27
|
+
marks: ['Work Item Type'],
|
|
28
|
+
columns: {
|
|
29
|
+
key: ['ID'], id: ['ID'], type: ['Work Item Type'], title: ['Title', 'Title 1', 'Title 2', 'Title 3'],
|
|
30
|
+
description: ['Description'], parent: ['Parent'], status: ['State'], priority: ['Priority'],
|
|
31
|
+
points: ['Story Points', 'Effort', 'Size'], release: ['Iteration Path'], labels: ['Tags'],
|
|
32
|
+
criteria: ['Acceptance Criteria'], url: [],
|
|
33
|
+
},
|
|
34
|
+
epicTypes: ['Epic', 'Feature'], skipTypes: ['Task'], labelSeparator: /\s*;\s*/,
|
|
35
|
+
},
|
|
36
|
+
gitlab: {
|
|
37
|
+
label: 'GitLab',
|
|
38
|
+
marks: ['Issue ID', 'URL'],
|
|
39
|
+
columns: {
|
|
40
|
+
key: ['Issue ID', 'IID'], id: ['Issue ID', 'IID'], type: [], title: ['Title'], description: ['Description'],
|
|
41
|
+
parent: ['Epic ID'], parentName: ['Epic Title'], status: ['State'], priority: [], points: ['Weight'],
|
|
42
|
+
release: ['Milestone'], labels: ['Labels'], criteria: [], url: ['URL'],
|
|
43
|
+
},
|
|
44
|
+
epicTypes: [], skipTypes: [], labelSeparator: /\s*,\s*/,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Where Jira's JSON keeps what has no field of its own: custom fields, by key.
|
|
50
|
+
* Jira Cloud's "Story point estimate" is customfield_10016 unless a site
|
|
51
|
+
* changed it; a host names its own (`columns: { points: ['customfield_10026'] }`).
|
|
52
|
+
*/
|
|
53
|
+
const JIRA_FIELDS = Object.freeze({
|
|
54
|
+
points: ['story_points', 'storyPoints', 'customfield_10016'],
|
|
55
|
+
criteria: ['acceptanceCriteria'],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/** Text with HTML in it (Azure DevOps keeps descriptions as HTML) as lines of plain text. */
|
|
59
|
+
function plainText(value) {
|
|
60
|
+
if (value == null) return '';
|
|
61
|
+
if (typeof value === 'object') return documentText(value);
|
|
62
|
+
return String(value)
|
|
63
|
+
.replace(/<\s*(br|\/p|\/div|\/li|\/h\d)\s*\/?>/gi, '\n').replace(/<\s*li[^>]*>/gi, '- ')
|
|
64
|
+
.replace(/<[^>]+>/g, '')
|
|
65
|
+
.replace(/ /g, ' ').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, '&')
|
|
66
|
+
.replace(/\r\n?/g, '\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Jira Cloud's description, a document of nodes, as text: each paragraph and list item a line. */
|
|
70
|
+
function documentText(node) {
|
|
71
|
+
if (!node || typeof node !== 'object') return '';
|
|
72
|
+
if (node.type === 'text') return node.text ?? '';
|
|
73
|
+
const inner = (node.content ?? []).map(documentText).join('');
|
|
74
|
+
return ['paragraph', 'heading', 'listItem', 'codeBlock'].includes(node.type) ? `${node.type === 'listItem' ? '- ' : ''}${inner.trim()}\n` : inner;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The lines of a list: "- x", "* x", "1. x", "• x", "[ ] x" — each item a line, its marker gone. */
|
|
78
|
+
function listItems(text) {
|
|
79
|
+
return String(text ?? '').split('\n')
|
|
80
|
+
.map(line => line.replace(/^\s*(?:[-*•+]|\d+[.)]|\[[ xX]?\])\s+/, '').trim())
|
|
81
|
+
.filter(Boolean);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Acceptance criteria in a story's text: the lines under an "Acceptance
|
|
86
|
+
* criteria" heading, else its Given/When/Then scenarios (one each), else none.
|
|
87
|
+
* @returns {{criteria: Array<string>, rest: string}} - and the text without them
|
|
88
|
+
*/
|
|
89
|
+
export function criteriaFrom(text) {
|
|
90
|
+
const s = plainText(text);
|
|
91
|
+
const heading = /^\s*(?:#+\s*|\*\*)?acceptance criteria\b[^\n]*\n/im.exec(s);
|
|
92
|
+
if (heading) {
|
|
93
|
+
const after = s.slice(heading.index + heading[0].length);
|
|
94
|
+
const end = after.search(/\n\s*\n\s*(?:#+\s|\*\*|[A-Z][^\n:]{0,40}:\s*\n)/);
|
|
95
|
+
const block = end < 0 ? after : after.slice(0, end);
|
|
96
|
+
return { criteria: listItems(block), rest: (s.slice(0, heading.index) + (end < 0 ? '' : after.slice(end))).replace(/\n{3,}/g, '\n\n').trim() };
|
|
97
|
+
}
|
|
98
|
+
const scenarios = [];
|
|
99
|
+
let current = null;
|
|
100
|
+
for (const line of s.split('\n')) {
|
|
101
|
+
const t = line.replace(/^\s*(?:[-*•]\s+)?/, '').trim();
|
|
102
|
+
if (/^given\b/i.test(t)) { current = [t]; scenarios.push(current); } else if (current && /^(when|then|and|but)\b/i.test(t)) current.push(t); else current = null;
|
|
103
|
+
}
|
|
104
|
+
if (scenarios.length) return { criteria: scenarios.map(lines => lines.join(' ')), rest: s.split('\n').filter(l => !/^\s*(?:[-*•]\s+)?(given|when|then|and|but)\b/i.test(l)).join('\n').trim() };
|
|
105
|
+
return { criteria: [], rest: s };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** A field that holds only acceptance criteria: as criteriaFrom reads them, else each of its lines one. */
|
|
109
|
+
function criteriaField(text) {
|
|
110
|
+
const found = criteriaFrom(text).criteria;
|
|
111
|
+
return found.length ? found : listItems(plainText(text));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** "As a <who>, I want <what> so that <why>" in a text: its three parts (empty where it says nothing). */
|
|
115
|
+
export function narrativeFrom(text) {
|
|
116
|
+
const m = /\bas an?\s+([^,\n]+?)\s*,?\s+i\s+(?:want|need|would like)\s+([^\n]+?)(?:\s*,?\s+so\s+that\s+([^\n]+?))?\s*(?:[.\n]|$)/i.exec(plainText(text));
|
|
117
|
+
return m ? { as: m[1].trim(), want: m[2].trim(), soThat: (m[3] ?? '').trim() } : { as: '', want: '', soThat: '' };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** A text without its "As a … I want …" line: what is left to describe the story, once its sentence is read (narrativeFrom). */
|
|
121
|
+
export function withoutNarrative(text) {
|
|
122
|
+
return String(text ?? '').replace(/^\s*\**\s*as an?\s[^\n]*\bi\s+(want|need|would like)\b[^\n]*\n?/im, '').trim();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* TrackerExport — epics and stories from what an issue tracker exports: Jira,
|
|
127
|
+
* Azure DevOps or GitLab (TRACKERS), as CSV or as their API's JSON. An issue
|
|
128
|
+
* of an epic type becomes an epic (GitLab's epics are named by their issues'
|
|
129
|
+
* Epic ID and title); the rest become stories under their parent epic, with
|
|
130
|
+
* their status, priority, points, labels and release (fix version, iteration
|
|
131
|
+
* or milestone). Acceptance criteria come from their own field, else from
|
|
132
|
+
* the description (criteriaFrom); "As a … I want … so that …" from the
|
|
133
|
+
* description or the title (narrativeFrom). Each keeps where it came from
|
|
134
|
+
* (`source: {system, key, url?}`), and its id is the tracker's key, so
|
|
135
|
+
* reading a newer export over a model replaces what it had.
|
|
136
|
+
*
|
|
137
|
+
* `read(text)` gives a RequirementsModel spec (`{epics, releases, stories}`).
|
|
138
|
+
*/
|
|
139
|
+
export class TrackerExport {
|
|
140
|
+
/**
|
|
141
|
+
* @param {Object} [options]
|
|
142
|
+
* @param {string} [options.tracker] - 'jira', 'azure', 'gitlab' (default: told from the export)
|
|
143
|
+
* @param {Object<string, Array<string>>} [options.columns] - Column names to look in first, by field (in Jira's JSON, field keys)
|
|
144
|
+
*/
|
|
145
|
+
constructor({ tracker = null, columns = {} } = {}) {
|
|
146
|
+
this.tracker = tracker;
|
|
147
|
+
this.columns = columns;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** What a tracker is called ('Jira'). */
|
|
151
|
+
static label(tracker) {
|
|
152
|
+
return TRACKERS[tracker]?.label ?? 'Tracker';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Which tracker an export's columns (or JSON) are from, or null. */
|
|
156
|
+
static detect(text) {
|
|
157
|
+
const json = TrackerExport._json(text);
|
|
158
|
+
if (json) {
|
|
159
|
+
if (Array.isArray(json.issues) && json.issues[0]?.fields) return 'jira';
|
|
160
|
+
if (Array.isArray(json.value ?? json.workItems) && (json.value ?? json.workItems)[0]?.fields) return 'azure';
|
|
161
|
+
if (Array.isArray(json) && json[0] && 'iid' in json[0]) return 'gitlab';
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const { columns } = parseCsvTable(text);
|
|
165
|
+
const has = new Set(columns.map(c => c.toLowerCase()));
|
|
166
|
+
const found = Object.entries(TRACKERS).find(([, t]) => t.marks.every(m => has.has(m.toLowerCase())));
|
|
167
|
+
return found?.[0] ?? null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** @private — an API's JSON, given as text or already parsed. */
|
|
171
|
+
static _json(text) {
|
|
172
|
+
if (text && typeof text === 'object') return text;
|
|
173
|
+
const s = String(text ?? '').trim();
|
|
174
|
+
if (!s.startsWith('{') && !s.startsWith('[')) return null;
|
|
175
|
+
try { return JSON.parse(s); } catch { return null; }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The export (its text; an API's JSON as text or parsed — TrackerApi) as a requirements model's spec.
|
|
180
|
+
* @returns {{epics: Array, releases: Array, stories: Array, tracker: string|null}}
|
|
181
|
+
*/
|
|
182
|
+
read(text) {
|
|
183
|
+
const tracker = this.tracker ?? TrackerExport.detect(text) ?? 'jira';
|
|
184
|
+
const def = TRACKERS[tracker];
|
|
185
|
+
if (!def) throw new Error(`Unknown tracker: "${tracker}"`);
|
|
186
|
+
const json = TrackerExport._json(text);
|
|
187
|
+
const issues = json ? this._fromJson(tracker, json) : this._fromCsv(def, text);
|
|
188
|
+
return { ...this._model(tracker, def, issues), tracker };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** CSV rows as issues: each field from the first of its columns there. @private */
|
|
192
|
+
_fromCsv(def, text) {
|
|
193
|
+
const { columns, rows } = parseCsvTable(text);
|
|
194
|
+
const lower = new Map(columns.map(c => [c.toLowerCase(), c]));
|
|
195
|
+
const columnOf = (field) => [...(this.columns[field] ?? []), ...(def.columns[field] ?? [])].map(c => lower.get(c.toLowerCase())).find(Boolean);
|
|
196
|
+
const fields = Object.keys({ ...def.columns, ...this.columns, parentName: [] });
|
|
197
|
+
const at = Object.fromEntries(fields.map(f => [f, columnOf(f)]));
|
|
198
|
+
return rows.map(row => Object.fromEntries(fields.map(f => [f, at[f] ? row[at[f]] : ''])));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** An API's JSON as issues, in the CSV's fields. @private */
|
|
202
|
+
_fromJson(tracker, json) {
|
|
203
|
+
if (tracker === 'jira') {
|
|
204
|
+
return (json.issues ?? []).map(({ key, id, fields: f = {} }) => {
|
|
205
|
+
const custom = (names) => Object.entries(f).find(([k]) => names.some(n => k.toLowerCase().includes(n)))?.[1];
|
|
206
|
+
// A field the host named (its custom field's key), else Jira Cloud's usual one.
|
|
207
|
+
const field = (name) => [...(this.columns[name] ?? []), ...(JIRA_FIELDS[name] ?? [])].map(k => f[k]).find(v => v != null && v !== '');
|
|
208
|
+
return {
|
|
209
|
+
key, id: String(id ?? ''), type: f.issuetype?.name ?? '', title: f.summary ?? '', description: f.description ?? '',
|
|
210
|
+
parent: f.parent?.key ?? f.parent?.id ?? '', status: f.status?.name ?? '', priority: f.priority?.name ?? '',
|
|
211
|
+
points: field('points') ?? custom(['storypoint', 'story_point']) ?? '',
|
|
212
|
+
release: (f.fixVersions ?? [])[0]?.name ?? '', labels: (f.labels ?? []).join(' '),
|
|
213
|
+
criteria: field('criteria') ?? custom(['acceptance']) ?? '', url: '',
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (tracker === 'azure') {
|
|
218
|
+
return (json.value ?? json.workItems ?? []).map(({ id, fields: f = {}, url }) => ({
|
|
219
|
+
key: String(id), id: String(id), type: f['System.WorkItemType'] ?? '', title: f['System.Title'] ?? '',
|
|
220
|
+
description: f['System.Description'] ?? '', parent: String(f['System.Parent'] ?? ''), status: f['System.State'] ?? '',
|
|
221
|
+
priority: String(f['Microsoft.VSTS.Common.Priority'] ?? ''),
|
|
222
|
+
points: f['Microsoft.VSTS.Scheduling.StoryPoints'] ?? f['Microsoft.VSTS.Scheduling.Effort'] ?? '',
|
|
223
|
+
release: f['System.IterationPath'] ?? '', labels: f['System.Tags'] ?? '',
|
|
224
|
+
criteria: f['Microsoft.VSTS.Common.AcceptanceCriteria'] ?? '', url: url ?? '',
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
return (Array.isArray(json) ? json : []).map(i => ({
|
|
228
|
+
key: String(i.iid ?? i.id), id: String(i.iid ?? i.id), type: '', title: i.title ?? '', description: i.description ?? '',
|
|
229
|
+
parent: String(i.epic?.id ?? i.epic_iid ?? ''), parentName: i.epic?.title ?? '', status: i.state ?? '', priority: '',
|
|
230
|
+
points: i.weight ?? '', release: i.milestone?.title ?? '', labels: (i.labels ?? []).map(l => l.name ?? l).join(','),
|
|
231
|
+
criteria: '', url: i.web_url ?? '',
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Issues as epics, releases and stories. @private */
|
|
236
|
+
_model(tracker, def, issues) {
|
|
237
|
+
const is = (list, type) => list.some(t => t.toLowerCase() === String(type ?? '').trim().toLowerCase());
|
|
238
|
+
const keyOf = new Map(); // an issue's id or key → its key (Jira's CSV names a parent by its id)
|
|
239
|
+
for (const i of issues) for (const k of [i.key, i.id]) if (k) keyOf.set(String(k), String(i.key || i.id));
|
|
240
|
+
const source = (i, key) => ({ system: tracker, key, ...(i.url && { url: i.url }) });
|
|
241
|
+
|
|
242
|
+
const epics = new Map();
|
|
243
|
+
for (const i of issues) {
|
|
244
|
+
if (!is(def.epicTypes, i.type)) continue;
|
|
245
|
+
const key = String(i.key || i.id);
|
|
246
|
+
epics.set(key, { id: key, name: plainText(i.title), description: plainText(i.description), source: source(i, key) });
|
|
247
|
+
}
|
|
248
|
+
const releases = new Map();
|
|
249
|
+
const stories = [];
|
|
250
|
+
for (const i of issues) {
|
|
251
|
+
if (is(def.epicTypes, i.type) || is(def.skipTypes, i.type) || !String(i.title ?? '').trim()) continue;
|
|
252
|
+
const key = String(i.key || i.id);
|
|
253
|
+
const parent = i.parent ? keyOf.get(String(i.parent)) ?? String(i.parent) : '';
|
|
254
|
+
if (parent && !epics.has(parent) && i.parentName) epics.set(parent, { id: parent, name: plainText(i.parentName), description: '', source: { system: tracker, key: parent } });
|
|
255
|
+
const release = plainText(i.release);
|
|
256
|
+
if (release && !releases.has(release)) releases.set(release, { id: release, name: release.split(/[\\/]/).pop() });
|
|
257
|
+
const own = criteriaField(i.criteria);
|
|
258
|
+
const inText = own.length ? { criteria: [], rest: plainText(i.description) } : criteriaFrom(i.description);
|
|
259
|
+
const said = narrativeFrom(i.description);
|
|
260
|
+
const { as, want, soThat } = said.want ? said : narrativeFrom(i.title);
|
|
261
|
+
stories.push({
|
|
262
|
+
id: key, name: plainText(i.title), epic: parent, release,
|
|
263
|
+
as, want, soThat, description: said.want ? withoutNarrative(inText.rest) : inText.rest,
|
|
264
|
+
criteria: own.length ? own : inText.criteria,
|
|
265
|
+
status: plainText(i.status), priority: plainText(i.priority), points: i.points === '' ? null : i.points,
|
|
266
|
+
labels: String(i.labels ?? '').split(def.labelSeparator).map(l => l.trim()).filter(Boolean),
|
|
267
|
+
source: source(i, key), traces: [],
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// A story whose parent is no epic here (a story's sub-item, an epic not exported) is under none.
|
|
271
|
+
for (const s of stories) if (s.epic && !epics.has(s.epic)) s.epic = '';
|
|
272
|
+
return { epics: [...epics.values()], releases: [...releases.values()], stories };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { RequirementsModel } from './RequirementsModel.js';
|
|
2
|
+
export { RequirementsChecker } from './RequirementsChecker.js';
|
|
3
|
+
export { EpicsCompared, ReleasesCompared, StoriesCompared } from './RequirementsCompared.js';
|
|
4
|
+
export { RequirementsFileFormat } from './RequirementsFileFormat.js';
|
|
5
|
+
export { TrackerExportFileFormat, StoryboardFileFormat } from './RequirementsImportFormats.js';
|
|
6
|
+
export { TrackerExport, TRACKERS, criteriaFrom, narrativeFrom, withoutNarrative } from './TrackerExport.js';
|
|
7
|
+
export { Storyboard } from './Storyboard.js';
|
|
8
|
+
export { TrackerApi } from './TrackerApi.js';
|
|
9
|
+
export { JiraApi } from './JiraApi.js';
|
|
10
|
+
export { AzureDevOpsApi } from './AzureDevOpsApi.js';
|
|
11
|
+
export { GitLabApi } from './GitLabApi.js';
|
|
12
|
+
export { TRACKER_APIS, trackerApi, trackerConnectionFields } from './trackerApis.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { JiraApi } from './JiraApi.js';
|
|
2
|
+
import { AzureDevOpsApi } from './AzureDevOpsApi.js';
|
|
3
|
+
import { GitLabApi } from './GitLabApi.js';
|
|
4
|
+
|
|
5
|
+
/** Each tracker's API, by its key in TRACKERS. */
|
|
6
|
+
export const TRACKER_APIS = Object.freeze({ jira: JiraApi, azure: AzureDevOpsApi, gitlab: GitLabApi });
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The API reader for a connection (`connection.tracker`: 'jira', 'azure',
|
|
10
|
+
* 'gitlab'); `options` as TrackerApi takes them.
|
|
11
|
+
*/
|
|
12
|
+
export function trackerApi(connection, options) {
|
|
13
|
+
const Api = TRACKER_APIS[connection?.tracker];
|
|
14
|
+
if (!Api) throw new Error(`No API reader for tracker "${connection?.tracker ?? ''}" (${Object.keys(TRACKER_APIS).join(', ')})`);
|
|
15
|
+
return new Api(connection, options);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** What each tracker's connection asks for: `[{tracker, label, fields}]`, for a host's form. */
|
|
19
|
+
export function trackerConnectionFields() {
|
|
20
|
+
return Object.entries(TRACKER_APIS).map(([tracker, Api]) => ({ tracker, label: new Api({}).label, fields: Api.fields }));
|
|
21
|
+
}
|
package/src/index.js
CHANGED
|
@@ -166,6 +166,10 @@ import { buildTypeCategoryTree, buildPropertyTree } from './charting/modelTree/b
|
|
|
166
166
|
import { DiagramTabs } from './charting/modelTree/DiagramTabs.js';
|
|
167
167
|
import { DataModelNotation, DataModelLevel, AttributedLevel, ConceptualLevel, LogicalLevel, PhysicalLevel, NotationStyle, RelationshipKind, AssociationKind, GeneralizationKind, EntityRole, FactRole, DimensionRole } from './datamodel.js';
|
|
168
168
|
import { DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping, ServiceDomain, DataModelFileFormat, ServiceLandscape, BianLandscapeReader, BianSemanticApiReader, DataType, EnumerationType, StructuredType, NamingStandard, NameStyle, ClassificationScheme, ClassificationLens, SqlDdl, SqlDdlFileFormat, ModelScorecard, ScorecardCategory, MeasuredCategory } from './charting/datamodel/index.js';
|
|
169
|
+
import { RequirementsModel, RequirementsChecker, RequirementsFileFormat, TrackerExportFileFormat, StoryboardFileFormat, TrackerExport, TRACKERS, Storyboard, TrackerApi, JiraApi, AzureDevOpsApi, GitLabApi, TRACKER_APIS, trackerApi, trackerConnectionFields } from './charting/requirements/index.js';
|
|
170
|
+
import { EditableModel } from './charting/model/EditableModel.js';
|
|
171
|
+
import { ModelEditCommand } from './charting/model/ModelEditCommand.js';
|
|
172
|
+
import { Benchmark } from './charting/model/Benchmark.js';
|
|
169
173
|
import { BianNotation, BianFunctionalPattern, BianActionTerm } from './bian.js';
|
|
170
174
|
import {
|
|
171
175
|
ARCHI_VISUAL_OBJECTS, ARCHI_VIEW_TYPES,
|
|
@@ -224,6 +228,9 @@ export {
|
|
|
224
228
|
buildModelTreePanel, buildAnalysisPanel, buildAnalysisData, buildTypeCategoryTree, buildPropertyTree, DiagramTabs,
|
|
225
229
|
DataModelNotation, DataModelLevel, AttributedLevel, ConceptualLevel, LogicalLevel, PhysicalLevel, NotationStyle, RelationshipKind, AssociationKind, GeneralizationKind, EntityRole, FactRole, DimensionRole,
|
|
226
230
|
DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping, ServiceDomain, DataModelFileFormat, ServiceLandscape, BianLandscapeReader, BianSemanticApiReader, DataType, EnumerationType, StructuredType, NamingStandard, NameStyle, ClassificationScheme, ClassificationLens, SqlDdl, SqlDdlFileFormat, ModelScorecard, ScorecardCategory, MeasuredCategory,
|
|
231
|
+
RequirementsModel, RequirementsChecker, RequirementsFileFormat, TrackerExportFileFormat, StoryboardFileFormat, TrackerExport, TRACKERS, Storyboard,
|
|
232
|
+
TrackerApi, JiraApi, AzureDevOpsApi, GitLabApi, TRACKER_APIS, trackerApi, trackerConnectionFields,
|
|
233
|
+
EditableModel, ModelEditCommand, Benchmark,
|
|
227
234
|
BianNotation, BianFunctionalPattern, BianActionTerm,
|
|
228
235
|
exportElementsCSV, exportRelationshipsCSV, exportModelCSV,
|
|
229
236
|
importElementsCSV, importRelationshipsCSV, importModelCSV,
|