@opencxh/domain 1.231.0 → 1.233.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/dist/entities/analytics/index.d.ts +1 -0
- package/dist/entities/analytics/picker.d.ts +49 -0
- package/dist/entities/analytics/picker.test.d.ts +1 -0
- package/dist/entities/planning/contracts.d.ts +100 -0
- package/dist/entities/planning/contracts.test.d.ts +1 -0
- package/dist/entities/planning/coverage.d.ts +73 -0
- package/dist/entities/planning/coverage.test.d.ts +1 -0
- package/dist/entities/planning/erlang.d.ts +37 -0
- package/dist/entities/planning/erlang.test.d.ts +1 -0
- package/dist/entities/planning/forecast.d.ts +34 -0
- package/dist/entities/planning/forecast.test.d.ts +1 -0
- package/dist/entities/planning/index.d.ts +7 -0
- package/dist/entities/planning/keys.d.ts +12 -0
- package/dist/entities/planning/kinds.d.ts +56 -0
- package/dist/entities/planning/types.d.ts +188 -0
- package/dist/entities/strategy/index.d.ts +4 -0
- package/dist/entities/strategy/keys.d.ts +32 -0
- package/dist/entities/strategy/levels.d.ts +85 -0
- package/dist/entities/strategy/progress.d.ts +85 -0
- package/dist/entities/strategy/progress.test.d.ts +1 -0
- package/dist/entities/strategy/types.d.ts +227 -0
- package/dist/entities/work/index.d.ts +1 -0
- package/dist/entities/work/item-types.d.ts +59 -0
- package/dist/entities/work/item-types.test.d.ts +1 -0
- package/dist/entities/work/types.d.ts +26 -2
- package/dist/index.cjs +14 -14
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1834 -1387
- package/package.json +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { StrategyHealth, StrategyHealthCategory } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The ladder, and the two vocabularies read off it.
|
|
4
|
+
*
|
|
5
|
+
* Same split as `ladder.ts` + `item-types.ts` in work: a fixed set that is ours, plus the
|
|
6
|
+
* resolution that decides what applies. The difference is that a level carries something a work
|
|
7
|
+
* type does not — an **order** — because the one structural rule in this model is about depth.
|
|
8
|
+
*/
|
|
9
|
+
/** One rung. */
|
|
10
|
+
export interface StrategyLevel {
|
|
11
|
+
key: string;
|
|
12
|
+
/** An i18n key for the fixed three; a name somebody typed for anything else. */
|
|
13
|
+
label: string;
|
|
14
|
+
/**
|
|
15
|
+
* Rank, not depth. A parent's order must be at most the child's, so gaps of ten leave room for
|
|
16
|
+
* an org to slot a rung in between later without renumbering what exists.
|
|
17
|
+
*/
|
|
18
|
+
order: number;
|
|
19
|
+
/**
|
|
20
|
+
* Does this rung carry a measure?
|
|
21
|
+
*
|
|
22
|
+
* It steers the form and nothing else — no query branches on it. A measured area is odd but not
|
|
23
|
+
* wrong, and refusing it here would mean a validation error for something harmless.
|
|
24
|
+
*/
|
|
25
|
+
measurable: boolean;
|
|
26
|
+
/** Lucide icon name. */
|
|
27
|
+
icon?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The three rungs that are **ours**.
|
|
31
|
+
*
|
|
32
|
+
* Labels are i18n keys, for the reason `LOOSE_STATUSES` spells out: a literal string here fixes
|
|
33
|
+
* the language at module load.
|
|
34
|
+
*
|
|
35
|
+
* Three and not more, on purpose. Atlassian Focus lets an admin define several *area* levels
|
|
36
|
+
* (Market → Business unit → Group); here that is the same rung nested under itself, which
|
|
37
|
+
* {@link canParent} allows. One key means one meaning in every report, which a per-org level per
|
|
38
|
+
* customer would not.
|
|
39
|
+
*/
|
|
40
|
+
export declare const FIXED_LEVELS: readonly StrategyLevel[];
|
|
41
|
+
/** The order an unknown level sorts at: below everything declared. */
|
|
42
|
+
export declare const UNKNOWN_LEVEL_ORDER = 999;
|
|
43
|
+
/** How often a check-in is expected when the row does not say. */
|
|
44
|
+
export declare const DEFAULT_CHECK_IN_DAYS = 7;
|
|
45
|
+
/** The rung with this key, or `undefined`. */
|
|
46
|
+
export declare function levelIn(levelKey: string, levels?: readonly StrategyLevel[]): StrategyLevel | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* The rank of a level key — the sort key of the whole tree.
|
|
49
|
+
*
|
|
50
|
+
* An unknown key ranks last rather than first, the same direction `cycleOrder` chose: something
|
|
51
|
+
* nobody has classified belongs after everything that is classified, not on top of it.
|
|
52
|
+
*/
|
|
53
|
+
export declare function levelOrder(levelKey: string, levels?: readonly StrategyLevel[]): number;
|
|
54
|
+
/**
|
|
55
|
+
* May a row of `childLevel` hang under one of `parentLevel`?
|
|
56
|
+
*
|
|
57
|
+
* One predicate instead of a table of allowed pairs. Equal is allowed and that is the point: it
|
|
58
|
+
* is what gives an org several area levels without a second concept. An unknown level ranks last,
|
|
59
|
+
* so it can sit under anything and nothing can sit under it — which is the safe direction for a
|
|
60
|
+
* key this platform does not recognise.
|
|
61
|
+
*/
|
|
62
|
+
export declare function canParent(childLevelKey: string, parentLevelKey: string, levels?: readonly StrategyLevel[]): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* The rung a child of this row gets by default.
|
|
65
|
+
*
|
|
66
|
+
* What the inline "+" on a row creates: one step down the ladder, so a focus area yields a goal
|
|
67
|
+
* and a goal yields a key result. **The deepest rung yields itself** rather than nothing — a key
|
|
68
|
+
* result under a key result is unusual but legal (`canParent` allows equal rungs), and returning
|
|
69
|
+
* `undefined` would mean the affordance disappears exactly where a team is working deepest.
|
|
70
|
+
*
|
|
71
|
+
* An unknown level ranks last, so it also yields itself. That is the safe direction: a level a
|
|
72
|
+
* source invented gets a sibling, not a guess at what sits under it.
|
|
73
|
+
*/
|
|
74
|
+
export declare function nextLevelBelow(levelKey: string, levels?: readonly StrategyLevel[]): string;
|
|
75
|
+
/**
|
|
76
|
+
* Which of our own rungs a source's name means, or `null`.
|
|
77
|
+
*
|
|
78
|
+
* On the **name**, like `fixedTypeForName`: an id is per site, a name is what a person
|
|
79
|
+
* recognises. Nothing here guesses — a level this platform does not know keeps its own key under
|
|
80
|
+
* its own name, which is the honest outcome.
|
|
81
|
+
*/
|
|
82
|
+
export declare function fixedLevelForName(name: string): string | null;
|
|
83
|
+
export declare function healthCategory(health: StrategyHealth): StrategyHealthCategory;
|
|
84
|
+
/** Every health value in one bucket — what a `healthCategories` filter expands to. */
|
|
85
|
+
export declare function healthsInCategory(categories: readonly StrategyHealthCategory[]): StrategyHealth[];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { StrategyHealth, StrategyItem, StrategyMeasure, StrategyProgress } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* How far along a measurement is, 0..1.
|
|
4
|
+
*
|
|
5
|
+
* Measured from `startValue`, not from zero: "from 5h30m down to 2h" is 0% on day one, while a
|
|
6
|
+
* zero baseline would report that same goal as already two-thirds done before anyone did
|
|
7
|
+
* anything.
|
|
8
|
+
*
|
|
9
|
+
* **`direction` and the bounds have to agree, and this is where they are checked.** The maths
|
|
10
|
+
* needs no `direction` at all — a falling goal is one whose target lies below its start, and the
|
|
11
|
+
* division handles that by itself. The field exists for the one case the bounds cannot express
|
|
12
|
+
* (holding a number steady, where start equals target) and for the label on the form. So when the
|
|
13
|
+
* two disagree — "lower is better" with a target *above* the start, which a form will happily
|
|
14
|
+
* accept — the bounds are not a scale at all, and this falls back to the only honest answer:
|
|
15
|
+
* "are we there, yes or no". Dividing anyway is what reported a goal as 100% done the moment its
|
|
16
|
+
* value went the wrong way.
|
|
17
|
+
*
|
|
18
|
+
* A value that cannot be a number is 0, never `NaN`: a `NaN` ratio renders as an empty bar with
|
|
19
|
+
* no width and reads as "not started".
|
|
20
|
+
*/
|
|
21
|
+
export declare function measureRatio(measure: Pick<StrategyMeasure, "direction" | "startValue" | "targetValue">, currentValue: number): number;
|
|
22
|
+
/**
|
|
23
|
+
* Where a linear pace would stand now, 0..1.
|
|
24
|
+
*
|
|
25
|
+
* `undefined` without both dates, and that absence is the contract: a goal with no window has no
|
|
26
|
+
* expected pace, and defaulting to "now over target" would invent one. A window whose end is not
|
|
27
|
+
* after its start is refused for the same reason — it cannot state a pace, only a division.
|
|
28
|
+
*/
|
|
29
|
+
export declare function paceRatio(item: Pick<StrategyItem, "startDate" | "targetDate">, now: number): number | undefined;
|
|
30
|
+
/** How far behind the expected pace counts as "at risk" rather than "on track". */
|
|
31
|
+
export declare const AT_RISK_GAP = 0.1;
|
|
32
|
+
/** And how far behind counts as off track. */
|
|
33
|
+
export declare const OFF_TRACK_GAP = 0.25;
|
|
34
|
+
/**
|
|
35
|
+
* What the numbers would say, if they got a vote.
|
|
36
|
+
*
|
|
37
|
+
* **A suggestion, never a write.** It fills the pre-selected chip in the check-in dialog and
|
|
38
|
+
* nothing else; `health` is only ever set by the person checking in. A goal that is behind
|
|
39
|
+
* because the work lands in December is on track, and no formula knows that.
|
|
40
|
+
*
|
|
41
|
+
* `undefined` when there is nothing to compare against — no ratio, or no pace. A hint with no
|
|
42
|
+
* basis is worse than no hint.
|
|
43
|
+
*/
|
|
44
|
+
export declare function suggestedHealth(ratio?: number, pace?: number): StrategyHealth | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* A parent's ratio, from its children.
|
|
47
|
+
*
|
|
48
|
+
* The mean of the children that **have** a ratio, and the filter is the whole point: an area
|
|
49
|
+
* usually holds a measured objective beside an unmeasured sub-area, and counting the latter as
|
|
50
|
+
* zero would park every area at half of what it is really doing.
|
|
51
|
+
*
|
|
52
|
+
* `undefined` when no child carries a measurement — the honest answer, which the screen renders
|
|
53
|
+
* as "no measurement yet" rather than an empty bar at 0%.
|
|
54
|
+
*
|
|
55
|
+
* Unweighted on purpose. Weighting by anything available here (child count, estimate, level)
|
|
56
|
+
* would be us deciding which bet matters more; an org that wants that says it by splitting the
|
|
57
|
+
* goal.
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* The share of a linked work set that is done, 0..1.
|
|
61
|
+
*
|
|
62
|
+
* `undefined` on an empty set, and that is the whole reason this is not `done / total` at the
|
|
63
|
+
* call site: nothing linked yet is "no measurement", while `0/0` is `NaN` and a hand-written
|
|
64
|
+
* guard would render it as an honest-looking 0%.
|
|
65
|
+
*/
|
|
66
|
+
export declare function workRatio(done: number, total: number): number | undefined;
|
|
67
|
+
export declare function rollupChildren(children: readonly {
|
|
68
|
+
progress?: Pick<StrategyProgress, "ratio">;
|
|
69
|
+
}[]): number | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Is a check-in overdue, and by how much?
|
|
72
|
+
*
|
|
73
|
+
* Counted from the last check-in, or from creation when there has never been one — a goal created
|
|
74
|
+
* three weeks ago and never touched is exactly the one worth nudging. Returns `0` when it is not
|
|
75
|
+
* due yet, so a caller can sort on lateness without a second branch.
|
|
76
|
+
*/
|
|
77
|
+
export declare function checkInOverdueMs(item: Pick<StrategyItem, "checkInEveryDays" | "lastCheckInAt" | "createdAt">, now: number): number;
|
|
78
|
+
/**
|
|
79
|
+
* The sort key of a strategy item's window: its target date, or the far end of time.
|
|
80
|
+
*
|
|
81
|
+
* Same shape and same direction as `cycleOrder`: a row nobody has put a date on belongs *after*
|
|
82
|
+
* everything that is planned, and `?? 0` would put it on top. Never a bare subtraction — one
|
|
83
|
+
* `NaN` from a comparator makes `Array.prototype.sort` implementation-defined for every row.
|
|
84
|
+
*/
|
|
85
|
+
export declare function targetOrder(item: Pick<StrategyItem, "targetDate">): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { MutationAuthor } from '../../platform/author';
|
|
2
|
+
import { ReportFilters } from '../analytics/report';
|
|
3
|
+
import { OwnerScope } from '../scope/types';
|
|
4
|
+
import { WorkItemSource } from '../work/types';
|
|
5
|
+
/**
|
|
6
|
+
* Strategy: the layer above execution — what we are betting on, and whether we are getting there.
|
|
7
|
+
*
|
|
8
|
+
* One entity for a focus area, an objective and a key result, for the same reason `WorkItem` is
|
|
9
|
+
* one entity for a task, a case and a deal: they differ in *words*, not in behaviour. Hierarchy,
|
|
10
|
+
* health, check-ins, linking, access and rollup are identical on all three; the only real
|
|
11
|
+
* difference is that an area leaves {@link StrategyItem.measure} empty. That is a field, not a
|
|
12
|
+
* second table.
|
|
13
|
+
*
|
|
14
|
+
* The rule that bounds this model, in three questions:
|
|
15
|
+
*
|
|
16
|
+
* 1. *Is it a bet, or the work that pays it off?* A bet → a `StrategyItem`. The work → a
|
|
17
|
+
* `WorkItem`, linked here, never copied here.
|
|
18
|
+
* 2. *Is the number judged, or counted?* Judged → {@link StrategyItem.health}, written by a
|
|
19
|
+
* person in a check-in. Counted → {@link StrategyItem.progress}, written only by the rollup.
|
|
20
|
+
* Nothing writes both.
|
|
21
|
+
* 3. *Is the answer a number this platform can already produce?* Then it is a
|
|
22
|
+
* {@link MeasureSource}, not a new column — every app that reports to analytics is a source.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Where something stands, as a person declares it.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately **not** derived from {@link StrategyProgress}. A goal at 40% halfway through the
|
|
28
|
+
* quarter can be perfectly on track (the work lands in December) or already lost (the team was
|
|
29
|
+
* reassigned), and no formula knows which. `suggestedHealth` offers an opinion in the check-in
|
|
30
|
+
* dialog; the owner decides.
|
|
31
|
+
*
|
|
32
|
+
* `achieved` and `missed` are two values rather than one `closed`, and that is the one place this
|
|
33
|
+
* model deviates from `WorkResolution` — which is a free label with no outcome flag, at the stated
|
|
34
|
+
* price that "nothing can compute how much ended well". Answering exactly that is why a goal
|
|
35
|
+
* exists, so the flag belongs here.
|
|
36
|
+
*/
|
|
37
|
+
export type StrategyHealth = "pending" | "on_track" | "at_risk" | "off_track" | "paused" | "achieved" | "missed";
|
|
38
|
+
/**
|
|
39
|
+
* The three buckets every list, counter and report asks for.
|
|
40
|
+
*
|
|
41
|
+
* Same shape and the same reason as `WorkStatusCategory`: the question "is this still running"
|
|
42
|
+
* must be answerable without a configuration lookup.
|
|
43
|
+
*/
|
|
44
|
+
export type StrategyHealthCategory = "open" | "paused" | "closed";
|
|
45
|
+
/** How a measured value is read and formatted. */
|
|
46
|
+
export type MeasureUnit = "number" | "percent" | "currency" | "duration_ms";
|
|
47
|
+
/**
|
|
48
|
+
* Which way is good.
|
|
49
|
+
*
|
|
50
|
+
* Without this a falling number reads as failure, and "bring the first reply time back under two
|
|
51
|
+
* hours" is exactly the goal a team sets first.
|
|
52
|
+
*/
|
|
53
|
+
export type MeasureDirection = "up" | "down";
|
|
54
|
+
/**
|
|
55
|
+
* Where the current value comes from.
|
|
56
|
+
*
|
|
57
|
+
* Four kinds and no fifth: anything else a customer wants to measure is already a metric in the
|
|
58
|
+
* analytics catalogue, and that is what `metric` is for.
|
|
59
|
+
*/
|
|
60
|
+
export type MeasureSource =
|
|
61
|
+
/** Somebody types the number at a check-in. The floor — works with no other app installed. */
|
|
62
|
+
{
|
|
63
|
+
kind: "manual";
|
|
64
|
+
}
|
|
65
|
+
/** The mean of the children that have a ratio. See `rollupChildren`. */
|
|
66
|
+
| {
|
|
67
|
+
kind: "children";
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The share of the linked work that is done.
|
|
71
|
+
*
|
|
72
|
+
* The set is not stored here: it is the relations on this item's scopeKey, which land in
|
|
73
|
+
* `WorkItem.keys` and make "everything under this goal" one indexed query. `count` says what a
|
|
74
|
+
* unit is — one item, or one second of estimated effort. The second is what makes a goal about
|
|
75
|
+
* a big epic and twenty small chores mean anything.
|
|
76
|
+
*/
|
|
77
|
+
| {
|
|
78
|
+
kind: "work";
|
|
79
|
+
count: "items" | "estimate";
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* A counter from the analytics catalogue — this is what makes the layer generic: every app
|
|
83
|
+
* that is already an `analytics-source` becomes a goal source with no code.
|
|
84
|
+
*
|
|
85
|
+
* `window` says which range to count over: `target` is the goal's own start→target window,
|
|
86
|
+
* `rolling_30d` the last thirty days (for a goal about a *rate* that should hold, not a total
|
|
87
|
+
* that should accumulate).
|
|
88
|
+
*/
|
|
89
|
+
| {
|
|
90
|
+
kind: "metric";
|
|
91
|
+
metricId: string;
|
|
92
|
+
filters?: ReportFilters;
|
|
93
|
+
window: "target" | "rolling_30d";
|
|
94
|
+
};
|
|
95
|
+
/** What "achieved" means as a number. Absent on a row that carries no measurement. */
|
|
96
|
+
export interface StrategyMeasure {
|
|
97
|
+
unit: MeasureUnit;
|
|
98
|
+
direction: MeasureDirection;
|
|
99
|
+
/**
|
|
100
|
+
* Where we started, and where we are going.
|
|
101
|
+
*
|
|
102
|
+
* **Both are absent for a `work` source, and that is the point.** What "done" means there is
|
|
103
|
+
* the linked set itself — link a fourteenth item and the target is fourteen. A typed target
|
|
104
|
+
* beside a set that moves is two answers to one question, and the stored one goes stale
|
|
105
|
+
* silently. For `manual` and `children` they are required, enforced on the write path.
|
|
106
|
+
*
|
|
107
|
+
* Not defaulted to zero either: "from 5h30m to 2h" is 0% at the start, while a zero baseline
|
|
108
|
+
* would report that same goal as already two-thirds done on day one.
|
|
109
|
+
*/
|
|
110
|
+
startValue?: number;
|
|
111
|
+
targetValue?: number;
|
|
112
|
+
source: MeasureSource;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The outcome of the last rollup. **Never written by a person** — the check-in writes
|
|
116
|
+
* `currentValue` only when the source is `manual`, and even then through the same path.
|
|
117
|
+
*/
|
|
118
|
+
export interface StrategyProgress {
|
|
119
|
+
currentValue: number;
|
|
120
|
+
/** 0..1, clamped. */
|
|
121
|
+
ratio: number;
|
|
122
|
+
/** Where a linear pace would stand now, 0..1. Absent without both dates — no invented pace. */
|
|
123
|
+
paceRatio?: number;
|
|
124
|
+
/**
|
|
125
|
+
* The counted set, for a `work` source only.
|
|
126
|
+
*
|
|
127
|
+
* Kept beside `ratio` so the screen can say "12 of 20 done" instead of "60%": the fraction is
|
|
128
|
+
* what was actually counted, and the percentage is the derived thing.
|
|
129
|
+
*/
|
|
130
|
+
done?: number;
|
|
131
|
+
total?: number;
|
|
132
|
+
computedAt: number;
|
|
133
|
+
/**
|
|
134
|
+
* The last rollup failed for this row; the value above is stale.
|
|
135
|
+
*
|
|
136
|
+
* A separate field rather than a silent old number: a figure with no date on it is the one
|
|
137
|
+
* thing a strategy screen must not show.
|
|
138
|
+
*/
|
|
139
|
+
problem?: string;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* A strategy item: a focus area, an objective or a key result.
|
|
143
|
+
*
|
|
144
|
+
* Note what is **not** here: no `workItemIds`, no `focusAreaIds`. Everything this row points at
|
|
145
|
+
* runs through the platform's relation store, and a work item linked here carries
|
|
146
|
+
* `strategy_item:<id>` in its own `keys` — so "the work under this goal" is one indexed `$in`
|
|
147
|
+
* in the app that owns the work, not a join this store cannot do.
|
|
148
|
+
*/
|
|
149
|
+
export interface StrategyItem {
|
|
150
|
+
id: string;
|
|
151
|
+
organizationId: string;
|
|
152
|
+
/** A rung of the ladder — see `FIXED_LEVELS`. An unknown key sorts to the bottom. */
|
|
153
|
+
levelKey: string;
|
|
154
|
+
name: string;
|
|
155
|
+
description?: string;
|
|
156
|
+
/** Lucide icon name. Absent = the reader picks one from the level. */
|
|
157
|
+
icon?: string;
|
|
158
|
+
color?: string;
|
|
159
|
+
/** The gate, in exactly `WorkProject`'s shape so one predicate serves both. */
|
|
160
|
+
ownerScope: OwnerScope;
|
|
161
|
+
memberUserIds?: string[];
|
|
162
|
+
memberTeamIds?: string[];
|
|
163
|
+
/**
|
|
164
|
+
* The one accountable person, apart from who may read it.
|
|
165
|
+
*
|
|
166
|
+
* Separate from `ownerScope` on purpose: an org-wide goal is visible to everybody and is still
|
|
167
|
+
* somebody's to answer for. This is also what the check-in reminder is addressed to.
|
|
168
|
+
*/
|
|
169
|
+
ownerUserId?: string;
|
|
170
|
+
parentId?: string;
|
|
171
|
+
/**
|
|
172
|
+
* The scopeKeys of all ancestors, flattened at write time, top to bottom.
|
|
173
|
+
*
|
|
174
|
+
* The same mechanism as `WorkItem.ancestorKeys` and for the same reason: this store cannot
|
|
175
|
+
* recurse, so "everything under this area" has to be one indexed `$in`. The price is the same
|
|
176
|
+
* too — moving a row rewrites its whole subtree, and that is a job, not a request.
|
|
177
|
+
*/
|
|
178
|
+
ancestorKeys?: string[];
|
|
179
|
+
/**
|
|
180
|
+
* Epoch ms. Both optional: an imported area may genuinely have no window, and inventing one
|
|
181
|
+
* would make up the fact the row exists to state. Never sort these with a bare `a - b`.
|
|
182
|
+
*/
|
|
183
|
+
startDate?: number;
|
|
184
|
+
targetDate?: number;
|
|
185
|
+
health: StrategyHealth;
|
|
186
|
+
/**
|
|
187
|
+
* How often a check-in is expected, in days. Absent = `DEFAULT_CHECK_IN_DAYS`.
|
|
188
|
+
*
|
|
189
|
+
* A number rather than a cron-ish rule: the only question anything asks is "is it overdue",
|
|
190
|
+
* and that is `lastCheckInAt + days`.
|
|
191
|
+
*/
|
|
192
|
+
checkInEveryDays?: number;
|
|
193
|
+
lastCheckInAt?: number;
|
|
194
|
+
measure?: StrategyMeasure;
|
|
195
|
+
progress?: StrategyProgress;
|
|
196
|
+
archived?: boolean;
|
|
197
|
+
order?: number;
|
|
198
|
+
/** Ids of this row in source systems (`atlassian-goal:<ari>`). Empty on one created here. */
|
|
199
|
+
externalIds?: string[];
|
|
200
|
+
/** Where it came from, in the shape a work item carries. Only the landing writes it. */
|
|
201
|
+
source?: WorkItemSource;
|
|
202
|
+
createdBy: string;
|
|
203
|
+
createdAt?: number;
|
|
204
|
+
updatedAt?: number;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* One check-in: the rhythm the whole layer runs on.
|
|
208
|
+
*
|
|
209
|
+
* Its own table and not a `work_activity` row — that column means a *work item* id, and two id
|
|
210
|
+
* spaces in one indexed column is the kind of confusion that surfaces months later as the wrong
|
|
211
|
+
* rows in a feed.
|
|
212
|
+
*
|
|
213
|
+
* `value` doubles as the history series: six check-ins are six points, with no separate snapshot
|
|
214
|
+
* job. That is the same call `WorkCycleSummary` makes — write the number when it is stated, not
|
|
215
|
+
* every night.
|
|
216
|
+
*/
|
|
217
|
+
export interface StrategyUpdate {
|
|
218
|
+
id: string;
|
|
219
|
+
organizationId: string;
|
|
220
|
+
itemId: string;
|
|
221
|
+
health: StrategyHealth;
|
|
222
|
+
note?: string;
|
|
223
|
+
/** The measured value at that moment. Absent when the row carries no measure. */
|
|
224
|
+
value?: number;
|
|
225
|
+
author: MutationAuthor;
|
|
226
|
+
createdAt?: number;
|
|
227
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { WorkItemType, WorkProject } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Item kinds — the same shape as the status ladder, and for the same reason.
|
|
4
|
+
*
|
|
5
|
+
* A kind used to be a key out of a fixed set of four, with `WorkProject.typeKeys` narrowing which
|
|
6
|
+
* of them a project offered. That works until a project's kinds come from somewhere else: a Jira
|
|
7
|
+
* project has Story, Epic and whatever the customer invented, and a bare `string[]` has nowhere
|
|
8
|
+
* to put what they are called. Everything not recognisably a bug flattened onto "task".
|
|
9
|
+
*
|
|
10
|
+
* So a kind is now `{ key, label }` per project, exactly like a status — and the two halves of
|
|
11
|
+
* this file are the two halves of `ladder.ts`: the fixed set that is ours, and the resolution that
|
|
12
|
+
* decides which set applies.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The kinds that are **ours**, available to a project that declares none and to loose work.
|
|
16
|
+
*
|
|
17
|
+
* Their labels are **i18n keys, not text**, for the reason `LOOSE_STATUSES` spells out: a literal
|
|
18
|
+
* string here fixes the language at module level. A kind that came from a source carries a name
|
|
19
|
+
* somebody typed instead, and {@link labelOfType} is what keeps those apart.
|
|
20
|
+
*
|
|
21
|
+
* These keys are **reserved**: a source-defined kind that happens to be called "Bug" resolves onto
|
|
22
|
+
* `bug` rather than minting a second key for the same idea, which is what keeps a cross-project
|
|
23
|
+
* filter from listing "Bug" three times. Anything a source calls something else gets a key of its
|
|
24
|
+
* own, namespaced by project.
|
|
25
|
+
*/
|
|
26
|
+
export declare const FIXED_TYPES: readonly WorkItemType[];
|
|
27
|
+
/** The keys {@link FIXED_TYPES} occupies. A project-defined kind may not claim one. */
|
|
28
|
+
export declare const RESERVED_TYPE_KEYS: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* The kinds that apply to this item.
|
|
31
|
+
*
|
|
32
|
+
* A project *without* kinds falls back to ours instead of yielding an empty list — the same
|
|
33
|
+
* reasoning as `ladderFor`: a picker with zero options is a dead end, and a half-created project
|
|
34
|
+
* should not be unusable.
|
|
35
|
+
*/
|
|
36
|
+
export declare function typesFor(project?: WorkProject | null): readonly WorkItemType[];
|
|
37
|
+
/** The kind with this key, within a set. `undefined` on a key the set does not have. */
|
|
38
|
+
export declare function typeIn(typeKey: string, types: readonly WorkItemType[]): WorkItemType | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Which of our own kinds a source's name means, or `null` when it means none of them.
|
|
41
|
+
*
|
|
42
|
+
* Read on the **name** and not on an id, because an id is per site and a name is what a person
|
|
43
|
+
* recognises. A site that renamed "Bug" to "Defect" still gets `bug`; one that invented "Onderhoud"
|
|
44
|
+
* gets nothing from here and keeps its own kind under its own name, which is the honest outcome —
|
|
45
|
+
* guessing would put maintenance work in the bug list.
|
|
46
|
+
*
|
|
47
|
+
* Deliberately no `deal`: no source this platform talks to has the concept, and producing one from
|
|
48
|
+
* a name would be a claim about the work rather than a translation of it.
|
|
49
|
+
*/
|
|
50
|
+
export declare function fixedTypeForName(name: string): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Some of {@link FIXED_TYPES}, by key, in the order given.
|
|
53
|
+
*
|
|
54
|
+
* For a caller that wants to *narrow* to a few of our own kinds — a project template saying "this
|
|
55
|
+
* one is about deals". That narrowing is all `typeKeys: string[]` could ever express, and it is
|
|
56
|
+
* still worth expressing; what it could not do was carry a kind that is not ours. An unknown key
|
|
57
|
+
* is dropped rather than invented.
|
|
58
|
+
*/
|
|
59
|
+
export declare function fixedTypes(...keys: string[]): WorkItemType[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -58,6 +58,25 @@ export interface WorkResolution {
|
|
|
58
58
|
label: string;
|
|
59
59
|
order: number;
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* An item kind: an icon with a label, never a workflow.
|
|
63
|
+
*
|
|
64
|
+
* The same shape as {@link WorkStatus} minus the category, because a kind carries no meaning a
|
|
65
|
+
* query can act on — nothing counts by kind the way `categoryOf` counts by status. `label` is an
|
|
66
|
+
* i18n key for one of `FIXED_TYPES` and a name somebody typed for anything else; `labelOfType`
|
|
67
|
+
* is what tells them apart.
|
|
68
|
+
*/
|
|
69
|
+
export interface WorkItemType {
|
|
70
|
+
key: string;
|
|
71
|
+
label: string;
|
|
72
|
+
/**
|
|
73
|
+
* A hint for the glyph, for a kind that came from a source.
|
|
74
|
+
*
|
|
75
|
+
* Absent = the reader picks one from the key, which is right for ours. Deliberately not a URL:
|
|
76
|
+
* a vendor's icon lives behind that vendor's auth, and a broken image is worse than a shape.
|
|
77
|
+
*/
|
|
78
|
+
icon?: string;
|
|
79
|
+
}
|
|
61
80
|
/**
|
|
62
81
|
* A project: the carrier of a work process.
|
|
63
82
|
*
|
|
@@ -98,8 +117,13 @@ export interface WorkProject {
|
|
|
98
117
|
statuses: WorkStatus[];
|
|
99
118
|
/** Resolutions, free per project. Empty = closing asks for no reason. */
|
|
100
119
|
resolutions?: WorkResolution[];
|
|
101
|
-
/**
|
|
102
|
-
|
|
120
|
+
/**
|
|
121
|
+
* The kinds this project offers, with their names. Empty = the fixed set.
|
|
122
|
+
*
|
|
123
|
+
* Replaced `typeKeys: string[]`, which could only *narrow* a fixed list and had nowhere to put
|
|
124
|
+
* a label — so a project whose kinds come from elsewhere lost every name it had.
|
|
125
|
+
*/
|
|
126
|
+
types?: WorkItemType[];
|
|
103
127
|
/** Keys of the `CustomFieldDef`s enabled on this project, in display order. */
|
|
104
128
|
fieldKeys?: string[];
|
|
105
129
|
/** Where a new item starts. Absent = the first status in `order`. */
|