@ethisyscore/plugin-ui 1.71.5 → 1.72.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 +1 -0
- package/dist/components/connectors/index.cjs +314 -0
- package/dist/components/connectors/index.cjs.map +1 -0
- package/dist/components/connectors/index.d.cts +177 -0
- package/dist/components/connectors/index.d.ts +177 -0
- package/dist/components/connectors/index.js +306 -0
- package/dist/components/connectors/index.js.map +1 -0
- package/package.json +7 -2
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-operation mapping completeness for one connector + module, as returned by the host's
|
|
5
|
+
* `connectors:mappings:completeness` tool.
|
|
6
|
+
*
|
|
7
|
+
* Declared here rather than imported from a plugin's generated contracts because this package sits
|
|
8
|
+
* BELOW the plugins: every plugin that shows a Tool Mappings page had its own copy of this shape,
|
|
9
|
+
* and the copies are what let the surfaces drift apart.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* The distinction that matters, and the one every copy of the banner got wrong at least once:
|
|
13
|
+
* `isToolMapped` and `unmappedRequiredCanonicalFields` are INDEPENDENT. An operation can have a tool
|
|
14
|
+
* mapped and still be incomplete, because the tool's schema does not accept a field the operation
|
|
15
|
+
* requires. Copy that says "not mapped" when it means "fields not mapped" sends the operator to
|
|
16
|
+
* auto-match, which maps tools and cannot close a field gap.
|
|
17
|
+
*/
|
|
18
|
+
interface MappingCompletenessItem {
|
|
19
|
+
/** Canonical operation key, e.g. `list-commits`. */
|
|
20
|
+
operationKey: string;
|
|
21
|
+
/** Human label for the operation, as declared by the module's catalogue. */
|
|
22
|
+
label: string;
|
|
23
|
+
/**
|
|
24
|
+
* Whether the module declares this operation as required. Required gaps block the module from
|
|
25
|
+
* working; optional ones only leave an extra capability switched off.
|
|
26
|
+
*/
|
|
27
|
+
isRequired: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Whether ANY tool is mapped to the operation. False means the operator has not chosen a tool
|
|
30
|
+
* yet; true says nothing about whether the mapping is complete.
|
|
31
|
+
*/
|
|
32
|
+
isToolMapped: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Canonical request fields the operation requires that do not map onto a property the live tool
|
|
35
|
+
* actually accepts. Non-empty means incomplete, whether or not a tool is mapped.
|
|
36
|
+
*/
|
|
37
|
+
unmappedRequiredCanonicalFields: string[];
|
|
38
|
+
}
|
|
39
|
+
/** Response envelope for the completeness read, post-unwrap. */
|
|
40
|
+
interface MappingCompletenessResult {
|
|
41
|
+
operations: MappingCompletenessItem[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface MappingCompletenessBannerProps {
|
|
45
|
+
/**
|
|
46
|
+
* Per-operation completeness for the selected connector + module. The banner splits these into
|
|
47
|
+
* REQUIRED operations with gaps (a blocking warning) and OPTIONAL ones (an informational note);
|
|
48
|
+
* it renders nothing when neither group has gaps.
|
|
49
|
+
*/
|
|
50
|
+
operations: MappingCompletenessItem[];
|
|
51
|
+
/**
|
|
52
|
+
* Opt-in AI assist. Invoked with the operation the operator chose to fix. The consumer wires this
|
|
53
|
+
* to the suggest-translation mutation and decides what to do with the result (open the editor
|
|
54
|
+
* seeded with the suggestion, apply it, and so on).
|
|
55
|
+
*/
|
|
56
|
+
onSuggestWithAi: (operation: MappingCompletenessItem) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Opens the manual mapping editor for an operation. The consumer owns the editor surface and
|
|
59
|
+
* routing; the banner only raises intent.
|
|
60
|
+
*/
|
|
61
|
+
onOpenMappingEditor: (operation: MappingCompletenessItem) => void;
|
|
62
|
+
/**
|
|
63
|
+
* Operation key currently awaiting an AI suggestion, or `null`. Drives the per-row spinner and
|
|
64
|
+
* disabled state so a slow suggestion does not lock the whole banner.
|
|
65
|
+
*/
|
|
66
|
+
suggestingOperationKey?: string | null;
|
|
67
|
+
/** Disables the AI assist affordance entirely, e.g. AI not configured for the organisation. */
|
|
68
|
+
disableSuggest?: boolean;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Connector-setup "mapping completeness" banner, shared by every plugin with a Tool Mappings page.
|
|
72
|
+
*
|
|
73
|
+
* Presentational and props-driven: the consumer fetches completeness and owns the editor, so the
|
|
74
|
+
* same banner serves Repo Monitoring, Timesheets, Error Monitoring and anything added later.
|
|
75
|
+
*
|
|
76
|
+
* It separates two cases:
|
|
77
|
+
*
|
|
78
|
+
* - **Required** operations with unmapped required fields, a warning the operator must act on
|
|
79
|
+
* because the module cannot run them.
|
|
80
|
+
* - **Optional** operations with unmapped required fields, a calm note. These power extra
|
|
81
|
+
* capabilities that stay off until their fields are mapped, and the connector may legitimately
|
|
82
|
+
* expose no suitable tool, so they must never read as a blocking error.
|
|
83
|
+
*
|
|
84
|
+
* Renders nothing when neither group has gaps.
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* **The wording is load-bearing, and was wrong in the copy this was promoted from.** Both groups are
|
|
88
|
+
* selected on `unmappedRequiredCanonicalFields`, which is about FIELD mapping. The original optional
|
|
89
|
+
* alert was titled "Optional operations not mapped" and said they "stay off until mapped", which
|
|
90
|
+
* describes TOOL mapping. Auto-match maps tools and cannot close a field gap, so an operator who
|
|
91
|
+
* pressed auto-match saw the count refuse to move and reported the banner as broken. It was
|
|
92
|
+
* accurate; the text was not. Every string here therefore says "fields", and both alerts say plainly
|
|
93
|
+
* that auto-match will not clear them.
|
|
94
|
+
*
|
|
95
|
+
* A row without the "No tool mapped" chip already HAS a tool and needs its fields mapped. That chip
|
|
96
|
+
* is the only thing distinguishing the two states, which is why the prose points at it.
|
|
97
|
+
*/
|
|
98
|
+
declare function MappingCompletenessBanner({ operations, onSuggestWithAi, onOpenMappingEditor, suggestingOperationKey, disableSuggest, }: MappingCompletenessBannerProps): react.JSX.Element | null;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Every operation with a gap, required or optional. Use it to decide whether to mount a banner at
|
|
102
|
+
* all, without duplicating the predicate.
|
|
103
|
+
*/
|
|
104
|
+
declare function selectIncompleteOperations(operations: MappingCompletenessItem[]): MappingCompletenessItem[];
|
|
105
|
+
/**
|
|
106
|
+
* REQUIRED operations with a gap. These genuinely block the module: the connector cannot run them.
|
|
107
|
+
*/
|
|
108
|
+
declare function selectRequiredIncompleteOperations(operations: MappingCompletenessItem[]): MappingCompletenessItem[];
|
|
109
|
+
/**
|
|
110
|
+
* OPTIONAL operations with a gap. Informational only: the operation powers an extra capability that
|
|
111
|
+
* stays off until its fields are mapped, and the connector may legitimately expose no suitable tool.
|
|
112
|
+
*
|
|
113
|
+
* @remarks
|
|
114
|
+
* Treat these as a note, never a warning. A connector need not expose every operation a module can
|
|
115
|
+
* use, so a correctly configured connector can sit here indefinitely — and a banner that never
|
|
116
|
+
* clears is a banner operators learn to ignore, which costs you the required warnings too.
|
|
117
|
+
*/
|
|
118
|
+
declare function selectOptionalIncompleteOperations(operations: MappingCompletenessItem[]): MappingCompletenessItem[];
|
|
119
|
+
/** A module's readiness, counted over REQUIRED operations only. */
|
|
120
|
+
interface MappingReadiness {
|
|
121
|
+
/** Required operations that are mapped AND have no field gaps. */
|
|
122
|
+
complete: number;
|
|
123
|
+
/** Total required operations declared by the module's catalogue. */
|
|
124
|
+
total: number;
|
|
125
|
+
/** True when every required operation is mapped and complete. */
|
|
126
|
+
isReady: boolean;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Readiness over REQUIRED operations only, for a progress indicator or a "ready" badge.
|
|
130
|
+
*
|
|
131
|
+
* @remarks
|
|
132
|
+
* Optional operations are excluded on purpose. Counting them would leave a properly configured
|
|
133
|
+
* connector permanently short of 100%, for operations the operator was never expected to map. This
|
|
134
|
+
* rule came from `ethisyscore-plugin-timesheets`, which had it right while another plugin was
|
|
135
|
+
* reporting optional field gaps in a standing banner.
|
|
136
|
+
*/
|
|
137
|
+
declare function selectMappingReadiness(operations: MappingCompletenessItem[]): MappingReadiness;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* React-query keys for a connector's tool mappings and the completeness read derived from them.
|
|
141
|
+
*
|
|
142
|
+
* @remarks
|
|
143
|
+
* The completeness key is built as a CHILD of the mappings key, and that nesting is the whole point
|
|
144
|
+
* of shipping these from the SDK rather than letting each plugin declare its own.
|
|
145
|
+
*
|
|
146
|
+
* Completeness is derived state: it is the mappings joined against the live tool schemas. Every write
|
|
147
|
+
* that changes a mapping can change it. When the two keys are siblings, a mutation has to remember to
|
|
148
|
+
* invalidate both, and one plugin duly invalidated only the mappings key — so its "operations aren't
|
|
149
|
+
* fully mapped" banner went stale after every save, clear and auto-match, and stayed stale until the
|
|
150
|
+
* operator navigated away. Nesting means a single `invalidateQueries({ queryKey: mappings })` takes
|
|
151
|
+
* the completeness read with it, and there is nothing left to forget.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* const mappings = connectorMappingsQueryKey(connectorId, moduleKey);
|
|
156
|
+
*
|
|
157
|
+
* useQuery({ queryKey: mappings, queryFn: ... });
|
|
158
|
+
* useQuery({ queryKey: connectorMappingCompletenessQueryKey(connectorId, moduleKey), queryFn: ... });
|
|
159
|
+
*
|
|
160
|
+
* // One call. Covers both, and any future query keyed beneath the mappings key.
|
|
161
|
+
* queryClient.invalidateQueries({ queryKey: mappings });
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
/**
|
|
165
|
+
* Key for the mappings list of one connector + module.
|
|
166
|
+
*
|
|
167
|
+
* `connectorId` is accepted as possibly-undefined because the page renders before a connector is
|
|
168
|
+
* chosen; pair it with `enabled: !!connectorId` rather than branching on the key.
|
|
169
|
+
*/
|
|
170
|
+
declare function connectorMappingsQueryKey(connectorId: string | undefined, moduleKey: string): readonly ["connector-mappings", string | undefined, string];
|
|
171
|
+
/**
|
|
172
|
+
* Key for the completeness read, nested UNDER {@link connectorMappingsQueryKey} so invalidating the
|
|
173
|
+
* mappings prefix always covers it. Do not flatten this into a sibling key.
|
|
174
|
+
*/
|
|
175
|
+
declare function connectorMappingCompletenessQueryKey(connectorId: string | undefined, moduleKey: string): readonly ["connector-mappings", string | undefined, string, "completeness"];
|
|
176
|
+
|
|
177
|
+
export { MappingCompletenessBanner, type MappingCompletenessBannerProps, type MappingCompletenessItem, type MappingCompletenessResult, type MappingReadiness, connectorMappingCompletenessQueryKey, connectorMappingsQueryKey, selectIncompleteOperations, selectMappingReadiness, selectOptionalIncompleteOperations, selectRequiredIncompleteOperations };
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-operation mapping completeness for one connector + module, as returned by the host's
|
|
5
|
+
* `connectors:mappings:completeness` tool.
|
|
6
|
+
*
|
|
7
|
+
* Declared here rather than imported from a plugin's generated contracts because this package sits
|
|
8
|
+
* BELOW the plugins: every plugin that shows a Tool Mappings page had its own copy of this shape,
|
|
9
|
+
* and the copies are what let the surfaces drift apart.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* The distinction that matters, and the one every copy of the banner got wrong at least once:
|
|
13
|
+
* `isToolMapped` and `unmappedRequiredCanonicalFields` are INDEPENDENT. An operation can have a tool
|
|
14
|
+
* mapped and still be incomplete, because the tool's schema does not accept a field the operation
|
|
15
|
+
* requires. Copy that says "not mapped" when it means "fields not mapped" sends the operator to
|
|
16
|
+
* auto-match, which maps tools and cannot close a field gap.
|
|
17
|
+
*/
|
|
18
|
+
interface MappingCompletenessItem {
|
|
19
|
+
/** Canonical operation key, e.g. `list-commits`. */
|
|
20
|
+
operationKey: string;
|
|
21
|
+
/** Human label for the operation, as declared by the module's catalogue. */
|
|
22
|
+
label: string;
|
|
23
|
+
/**
|
|
24
|
+
* Whether the module declares this operation as required. Required gaps block the module from
|
|
25
|
+
* working; optional ones only leave an extra capability switched off.
|
|
26
|
+
*/
|
|
27
|
+
isRequired: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Whether ANY tool is mapped to the operation. False means the operator has not chosen a tool
|
|
30
|
+
* yet; true says nothing about whether the mapping is complete.
|
|
31
|
+
*/
|
|
32
|
+
isToolMapped: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Canonical request fields the operation requires that do not map onto a property the live tool
|
|
35
|
+
* actually accepts. Non-empty means incomplete, whether or not a tool is mapped.
|
|
36
|
+
*/
|
|
37
|
+
unmappedRequiredCanonicalFields: string[];
|
|
38
|
+
}
|
|
39
|
+
/** Response envelope for the completeness read, post-unwrap. */
|
|
40
|
+
interface MappingCompletenessResult {
|
|
41
|
+
operations: MappingCompletenessItem[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface MappingCompletenessBannerProps {
|
|
45
|
+
/**
|
|
46
|
+
* Per-operation completeness for the selected connector + module. The banner splits these into
|
|
47
|
+
* REQUIRED operations with gaps (a blocking warning) and OPTIONAL ones (an informational note);
|
|
48
|
+
* it renders nothing when neither group has gaps.
|
|
49
|
+
*/
|
|
50
|
+
operations: MappingCompletenessItem[];
|
|
51
|
+
/**
|
|
52
|
+
* Opt-in AI assist. Invoked with the operation the operator chose to fix. The consumer wires this
|
|
53
|
+
* to the suggest-translation mutation and decides what to do with the result (open the editor
|
|
54
|
+
* seeded with the suggestion, apply it, and so on).
|
|
55
|
+
*/
|
|
56
|
+
onSuggestWithAi: (operation: MappingCompletenessItem) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Opens the manual mapping editor for an operation. The consumer owns the editor surface and
|
|
59
|
+
* routing; the banner only raises intent.
|
|
60
|
+
*/
|
|
61
|
+
onOpenMappingEditor: (operation: MappingCompletenessItem) => void;
|
|
62
|
+
/**
|
|
63
|
+
* Operation key currently awaiting an AI suggestion, or `null`. Drives the per-row spinner and
|
|
64
|
+
* disabled state so a slow suggestion does not lock the whole banner.
|
|
65
|
+
*/
|
|
66
|
+
suggestingOperationKey?: string | null;
|
|
67
|
+
/** Disables the AI assist affordance entirely, e.g. AI not configured for the organisation. */
|
|
68
|
+
disableSuggest?: boolean;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Connector-setup "mapping completeness" banner, shared by every plugin with a Tool Mappings page.
|
|
72
|
+
*
|
|
73
|
+
* Presentational and props-driven: the consumer fetches completeness and owns the editor, so the
|
|
74
|
+
* same banner serves Repo Monitoring, Timesheets, Error Monitoring and anything added later.
|
|
75
|
+
*
|
|
76
|
+
* It separates two cases:
|
|
77
|
+
*
|
|
78
|
+
* - **Required** operations with unmapped required fields, a warning the operator must act on
|
|
79
|
+
* because the module cannot run them.
|
|
80
|
+
* - **Optional** operations with unmapped required fields, a calm note. These power extra
|
|
81
|
+
* capabilities that stay off until their fields are mapped, and the connector may legitimately
|
|
82
|
+
* expose no suitable tool, so they must never read as a blocking error.
|
|
83
|
+
*
|
|
84
|
+
* Renders nothing when neither group has gaps.
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* **The wording is load-bearing, and was wrong in the copy this was promoted from.** Both groups are
|
|
88
|
+
* selected on `unmappedRequiredCanonicalFields`, which is about FIELD mapping. The original optional
|
|
89
|
+
* alert was titled "Optional operations not mapped" and said they "stay off until mapped", which
|
|
90
|
+
* describes TOOL mapping. Auto-match maps tools and cannot close a field gap, so an operator who
|
|
91
|
+
* pressed auto-match saw the count refuse to move and reported the banner as broken. It was
|
|
92
|
+
* accurate; the text was not. Every string here therefore says "fields", and both alerts say plainly
|
|
93
|
+
* that auto-match will not clear them.
|
|
94
|
+
*
|
|
95
|
+
* A row without the "No tool mapped" chip already HAS a tool and needs its fields mapped. That chip
|
|
96
|
+
* is the only thing distinguishing the two states, which is why the prose points at it.
|
|
97
|
+
*/
|
|
98
|
+
declare function MappingCompletenessBanner({ operations, onSuggestWithAi, onOpenMappingEditor, suggestingOperationKey, disableSuggest, }: MappingCompletenessBannerProps): react.JSX.Element | null;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Every operation with a gap, required or optional. Use it to decide whether to mount a banner at
|
|
102
|
+
* all, without duplicating the predicate.
|
|
103
|
+
*/
|
|
104
|
+
declare function selectIncompleteOperations(operations: MappingCompletenessItem[]): MappingCompletenessItem[];
|
|
105
|
+
/**
|
|
106
|
+
* REQUIRED operations with a gap. These genuinely block the module: the connector cannot run them.
|
|
107
|
+
*/
|
|
108
|
+
declare function selectRequiredIncompleteOperations(operations: MappingCompletenessItem[]): MappingCompletenessItem[];
|
|
109
|
+
/**
|
|
110
|
+
* OPTIONAL operations with a gap. Informational only: the operation powers an extra capability that
|
|
111
|
+
* stays off until its fields are mapped, and the connector may legitimately expose no suitable tool.
|
|
112
|
+
*
|
|
113
|
+
* @remarks
|
|
114
|
+
* Treat these as a note, never a warning. A connector need not expose every operation a module can
|
|
115
|
+
* use, so a correctly configured connector can sit here indefinitely — and a banner that never
|
|
116
|
+
* clears is a banner operators learn to ignore, which costs you the required warnings too.
|
|
117
|
+
*/
|
|
118
|
+
declare function selectOptionalIncompleteOperations(operations: MappingCompletenessItem[]): MappingCompletenessItem[];
|
|
119
|
+
/** A module's readiness, counted over REQUIRED operations only. */
|
|
120
|
+
interface MappingReadiness {
|
|
121
|
+
/** Required operations that are mapped AND have no field gaps. */
|
|
122
|
+
complete: number;
|
|
123
|
+
/** Total required operations declared by the module's catalogue. */
|
|
124
|
+
total: number;
|
|
125
|
+
/** True when every required operation is mapped and complete. */
|
|
126
|
+
isReady: boolean;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Readiness over REQUIRED operations only, for a progress indicator or a "ready" badge.
|
|
130
|
+
*
|
|
131
|
+
* @remarks
|
|
132
|
+
* Optional operations are excluded on purpose. Counting them would leave a properly configured
|
|
133
|
+
* connector permanently short of 100%, for operations the operator was never expected to map. This
|
|
134
|
+
* rule came from `ethisyscore-plugin-timesheets`, which had it right while another plugin was
|
|
135
|
+
* reporting optional field gaps in a standing banner.
|
|
136
|
+
*/
|
|
137
|
+
declare function selectMappingReadiness(operations: MappingCompletenessItem[]): MappingReadiness;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* React-query keys for a connector's tool mappings and the completeness read derived from them.
|
|
141
|
+
*
|
|
142
|
+
* @remarks
|
|
143
|
+
* The completeness key is built as a CHILD of the mappings key, and that nesting is the whole point
|
|
144
|
+
* of shipping these from the SDK rather than letting each plugin declare its own.
|
|
145
|
+
*
|
|
146
|
+
* Completeness is derived state: it is the mappings joined against the live tool schemas. Every write
|
|
147
|
+
* that changes a mapping can change it. When the two keys are siblings, a mutation has to remember to
|
|
148
|
+
* invalidate both, and one plugin duly invalidated only the mappings key — so its "operations aren't
|
|
149
|
+
* fully mapped" banner went stale after every save, clear and auto-match, and stayed stale until the
|
|
150
|
+
* operator navigated away. Nesting means a single `invalidateQueries({ queryKey: mappings })` takes
|
|
151
|
+
* the completeness read with it, and there is nothing left to forget.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* const mappings = connectorMappingsQueryKey(connectorId, moduleKey);
|
|
156
|
+
*
|
|
157
|
+
* useQuery({ queryKey: mappings, queryFn: ... });
|
|
158
|
+
* useQuery({ queryKey: connectorMappingCompletenessQueryKey(connectorId, moduleKey), queryFn: ... });
|
|
159
|
+
*
|
|
160
|
+
* // One call. Covers both, and any future query keyed beneath the mappings key.
|
|
161
|
+
* queryClient.invalidateQueries({ queryKey: mappings });
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
/**
|
|
165
|
+
* Key for the mappings list of one connector + module.
|
|
166
|
+
*
|
|
167
|
+
* `connectorId` is accepted as possibly-undefined because the page renders before a connector is
|
|
168
|
+
* chosen; pair it with `enabled: !!connectorId` rather than branching on the key.
|
|
169
|
+
*/
|
|
170
|
+
declare function connectorMappingsQueryKey(connectorId: string | undefined, moduleKey: string): readonly ["connector-mappings", string | undefined, string];
|
|
171
|
+
/**
|
|
172
|
+
* Key for the completeness read, nested UNDER {@link connectorMappingsQueryKey} so invalidating the
|
|
173
|
+
* mappings prefix always covers it. Do not flatten this into a sibling key.
|
|
174
|
+
*/
|
|
175
|
+
declare function connectorMappingCompletenessQueryKey(connectorId: string | undefined, moduleKey: string): readonly ["connector-mappings", string | undefined, string, "completeness"];
|
|
176
|
+
|
|
177
|
+
export { MappingCompletenessBanner, type MappingCompletenessBannerProps, type MappingCompletenessItem, type MappingCompletenessResult, type MappingReadiness, connectorMappingCompletenessQueryKey, connectorMappingsQueryKey, selectIncompleteOperations, selectMappingReadiness, selectOptionalIncompleteOperations, selectRequiredIncompleteOperations };
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { FormControl, InputLabel, FilledInput, InputAdornment, IconButton, Box, Alert, AlertTitle, Typography, Stack, Chip, Button, CircularProgress } from '@mui/material';
|
|
2
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { memo, useState, useId, useRef, useEffect, useCallback } from 'react';
|
|
4
|
+
|
|
5
|
+
// src/components/ui/index.ts
|
|
6
|
+
function NiCross({ className, variant = "outlined", size = "medium" }) {
|
|
7
|
+
const iconSize = sizeHelper(size);
|
|
8
|
+
const iconStrokeWidth = strokeSizeHelper(iconSize);
|
|
9
|
+
if (variant === "outlined") {
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
"svg",
|
|
12
|
+
{
|
|
13
|
+
width: iconSize,
|
|
14
|
+
height: iconSize,
|
|
15
|
+
className,
|
|
16
|
+
viewBox: "0 0 24 24",
|
|
17
|
+
fill: "none",
|
|
18
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
19
|
+
children: /* @__PURE__ */ jsx(
|
|
20
|
+
"path",
|
|
21
|
+
{
|
|
22
|
+
d: "M8.00016 8L12.0001 12M12.0001 12L16 16.0002M12.0001 12L16 8.00015M12.0001 12L8 16.0002",
|
|
23
|
+
stroke: "currentColor",
|
|
24
|
+
strokeWidth: iconStrokeWidth,
|
|
25
|
+
strokeLinecap: "round"
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
} else {
|
|
31
|
+
return /* @__PURE__ */ jsx(
|
|
32
|
+
"svg",
|
|
33
|
+
{
|
|
34
|
+
width: iconSize,
|
|
35
|
+
height: iconSize,
|
|
36
|
+
className,
|
|
37
|
+
viewBox: "0 0 24 24",
|
|
38
|
+
fill: "none",
|
|
39
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
40
|
+
children: /* @__PURE__ */ jsx(
|
|
41
|
+
"path",
|
|
42
|
+
{
|
|
43
|
+
d: "M15.4698 7.46963C15.7626 7.17684 16.2374 7.17684 16.5303 7.46963C16.8232 7.7625 16.8231 8.23728 16.5303 8.53018L13.0606 11.9999L16.5303 15.4696C16.8232 15.7625 16.8231 16.2373 16.5303 16.5302C16.2557 16.8048 15.821 16.8223 15.5264 16.5819L15.4698 16.5302L12 13.0604L8.53032 16.5302C8.23743 16.8231 7.76267 16.8231 7.46977 16.5302C7.17697 16.2373 7.17691 15.7625 7.46977 15.4696L10.9395 11.9999L7.46977 8.53018L7.41801 8.47354C7.1778 8.17898 7.19527 7.74421 7.46977 7.46963C7.76264 7.17676 8.23742 7.17682 8.53032 7.46963L12 10.9394L15.4698 7.46963Z",
|
|
44
|
+
fill: "currentColor"
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function NiSearch({
|
|
52
|
+
className,
|
|
53
|
+
variant = "outlined",
|
|
54
|
+
size = "medium",
|
|
55
|
+
oneTone = false
|
|
56
|
+
}) {
|
|
57
|
+
const iconSize = sizeHelper(size);
|
|
58
|
+
const iconStrokeWidth = strokeSizeHelper(iconSize);
|
|
59
|
+
if (variant === "outlined") {
|
|
60
|
+
return /* @__PURE__ */ jsxs(
|
|
61
|
+
"svg",
|
|
62
|
+
{
|
|
63
|
+
width: iconSize,
|
|
64
|
+
height: iconSize,
|
|
65
|
+
className,
|
|
66
|
+
viewBox: "0 0 24 24",
|
|
67
|
+
fill: "none",
|
|
68
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
69
|
+
children: [
|
|
70
|
+
/* @__PURE__ */ jsx(
|
|
71
|
+
"path",
|
|
72
|
+
{
|
|
73
|
+
opacity: oneTone ? 1 : 0.6,
|
|
74
|
+
d: "M22 22L18.5 18.5",
|
|
75
|
+
stroke: "currentColor",
|
|
76
|
+
strokeWidth: iconStrokeWidth,
|
|
77
|
+
strokeLinecap: "round"
|
|
78
|
+
}
|
|
79
|
+
),
|
|
80
|
+
/* @__PURE__ */ jsx("circle", { cx: "11.5", cy: "11.5", r: "9.5", stroke: "currentColor", strokeWidth: iconStrokeWidth })
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
85
|
+
return /* @__PURE__ */ jsxs(
|
|
86
|
+
"svg",
|
|
87
|
+
{
|
|
88
|
+
width: iconSize,
|
|
89
|
+
height: iconSize,
|
|
90
|
+
className,
|
|
91
|
+
viewBox: "0 0 24 24",
|
|
92
|
+
fill: "none",
|
|
93
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
94
|
+
children: [
|
|
95
|
+
/* @__PURE__ */ jsx(
|
|
96
|
+
"path",
|
|
97
|
+
{
|
|
98
|
+
opacity: oneTone ? 1 : 0.4,
|
|
99
|
+
d: "M19.0303 17.9697C18.7374 17.6768 18.2627 17.6768 17.9698 17.9697C17.6769 18.2626 17.6769 18.7373 17.9698 19.0302L21.4698 22.5302C21.7627 22.8231 22.2374 22.8231 22.5303 22.5302C22.8232 22.2373 22.8232 21.7626 22.5303 21.4697L19.0303 17.9697Z",
|
|
100
|
+
fill: "currentColor"
|
|
101
|
+
}
|
|
102
|
+
),
|
|
103
|
+
/* @__PURE__ */ jsx(
|
|
104
|
+
"path",
|
|
105
|
+
{
|
|
106
|
+
d: "M11.5 1.25C17.1609 1.25 21.75 5.83908 21.75 11.5C21.75 17.1609 17.1609 21.75 11.5 21.75C5.83908 21.75 1.25 17.1609 1.25 11.5C1.25 5.83908 5.83908 1.25 11.5 1.25Z",
|
|
107
|
+
fill: "currentColor"
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
var sizeHelper = (size) => {
|
|
116
|
+
if (typeof size === "number") {
|
|
117
|
+
return size;
|
|
118
|
+
} else if (size === "large") {
|
|
119
|
+
return 24;
|
|
120
|
+
} else if (size === "small" || size === "tiny") {
|
|
121
|
+
return 16;
|
|
122
|
+
} else {
|
|
123
|
+
return 20;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
var strokeSizeHelper = (size) => {
|
|
127
|
+
if (size === 32) {
|
|
128
|
+
return 1.15;
|
|
129
|
+
} else if (size >= 20) {
|
|
130
|
+
return 1.5;
|
|
131
|
+
} else {
|
|
132
|
+
return 1.75;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
memo(function DebouncedSearchInput2({
|
|
136
|
+
value = "",
|
|
137
|
+
onChange,
|
|
138
|
+
label = "Search",
|
|
139
|
+
delay = 400,
|
|
140
|
+
className
|
|
141
|
+
}) {
|
|
142
|
+
const [localValue, setLocalValue] = useState(value);
|
|
143
|
+
const inputId = useId();
|
|
144
|
+
const onChangeRef = useRef(onChange);
|
|
145
|
+
onChangeRef.current = onChange;
|
|
146
|
+
const lastReportedRef = useRef(value);
|
|
147
|
+
const isFirstRender = useRef(true);
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
setLocalValue(value);
|
|
150
|
+
lastReportedRef.current = value;
|
|
151
|
+
}, [value]);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (isFirstRender.current) {
|
|
154
|
+
isFirstRender.current = false;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (localValue === lastReportedRef.current) return;
|
|
158
|
+
const timer = setTimeout(() => {
|
|
159
|
+
lastReportedRef.current = localValue;
|
|
160
|
+
onChangeRef.current(localValue);
|
|
161
|
+
}, delay);
|
|
162
|
+
return () => clearTimeout(timer);
|
|
163
|
+
}, [localValue, delay]);
|
|
164
|
+
const handleClear = useCallback(() => {
|
|
165
|
+
setLocalValue("");
|
|
166
|
+
lastReportedRef.current = "";
|
|
167
|
+
onChangeRef.current("");
|
|
168
|
+
}, []);
|
|
169
|
+
const classes = (...parts) => parts.filter(Boolean).join(" ");
|
|
170
|
+
return /* @__PURE__ */ jsxs(FormControl, { variant: "filled", size: "medium", className: classes("surface mb-0 w-full", className), children: [
|
|
171
|
+
/* @__PURE__ */ jsx(InputLabel, { htmlFor: inputId, children: label }),
|
|
172
|
+
/* @__PURE__ */ jsx(
|
|
173
|
+
FilledInput,
|
|
174
|
+
{
|
|
175
|
+
id: inputId,
|
|
176
|
+
value: localValue,
|
|
177
|
+
onChange: (e) => setLocalValue(e.target.value),
|
|
178
|
+
endAdornment: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
179
|
+
/* @__PURE__ */ jsx(InputAdornment, { position: "end", className: classes(localValue === "" && "hidden"), children: /* @__PURE__ */ jsx(IconButton, { edge: "end", onClick: handleClear, "aria-label": "Clear search", children: /* @__PURE__ */ jsx(NiCross, { size: "medium", className: "text-text-disabled" }) }) }),
|
|
180
|
+
/* @__PURE__ */ jsx(InputAdornment, { position: "end", className: classes(localValue !== "" && "hidden"), "aria-hidden": true, children: /* @__PURE__ */ jsx(IconButton, { edge: "end", disabled: true, tabIndex: -1, children: /* @__PURE__ */ jsx(NiSearch, { size: "medium", className: "text-text-disabled" }) }) })
|
|
181
|
+
] })
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
] });
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// src/components/connectors/mappingCompleteness.ts
|
|
188
|
+
function hasUnmappedRequiredField(op) {
|
|
189
|
+
return op.unmappedRequiredCanonicalFields.length > 0;
|
|
190
|
+
}
|
|
191
|
+
function selectIncompleteOperations(operations) {
|
|
192
|
+
return operations.filter(hasUnmappedRequiredField);
|
|
193
|
+
}
|
|
194
|
+
function selectRequiredIncompleteOperations(operations) {
|
|
195
|
+
return operations.filter((op) => op.isRequired && hasUnmappedRequiredField(op));
|
|
196
|
+
}
|
|
197
|
+
function selectOptionalIncompleteOperations(operations) {
|
|
198
|
+
return operations.filter((op) => !op.isRequired && hasUnmappedRequiredField(op));
|
|
199
|
+
}
|
|
200
|
+
function selectMappingReadiness(operations) {
|
|
201
|
+
const required = operations.filter((op) => op.isRequired);
|
|
202
|
+
const complete = required.filter(
|
|
203
|
+
(op) => op.isToolMapped && op.unmappedRequiredCanonicalFields.length === 0
|
|
204
|
+
).length;
|
|
205
|
+
return {
|
|
206
|
+
complete,
|
|
207
|
+
total: required.length,
|
|
208
|
+
isReady: required.length > 0 && complete === required.length
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function MappingCompletenessBanner({
|
|
212
|
+
operations,
|
|
213
|
+
onSuggestWithAi,
|
|
214
|
+
onOpenMappingEditor,
|
|
215
|
+
suggestingOperationKey = null,
|
|
216
|
+
disableSuggest = false
|
|
217
|
+
}) {
|
|
218
|
+
const required = selectRequiredIncompleteOperations(operations);
|
|
219
|
+
const optional = selectOptionalIncompleteOperations(operations);
|
|
220
|
+
if (required.length === 0 && optional.length === 0) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const renderRow = (op) => {
|
|
224
|
+
const isSuggesting = suggestingOperationKey === op.operationKey;
|
|
225
|
+
return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(
|
|
226
|
+
Stack,
|
|
227
|
+
{
|
|
228
|
+
direction: { xs: "column", sm: "row" },
|
|
229
|
+
spacing: 1,
|
|
230
|
+
alignItems: { xs: "flex-start", sm: "center" },
|
|
231
|
+
justifyContent: "space-between",
|
|
232
|
+
children: [
|
|
233
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
234
|
+
/* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", sx: { flexWrap: "wrap" }, children: [
|
|
235
|
+
/* @__PURE__ */ jsx(Typography, { variant: "subtitle2", children: op.label }),
|
|
236
|
+
op.isRequired && /* @__PURE__ */ jsx(Chip, { label: "Required", size: "small", color: "error", variant: "outlined" }),
|
|
237
|
+
!op.isToolMapped && /* @__PURE__ */ jsx(Chip, { label: "No tool mapped", size: "small", color: "warning", variant: "outlined" })
|
|
238
|
+
] }),
|
|
239
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "text.secondary", children: [
|
|
240
|
+
"Unmapped required fields: ",
|
|
241
|
+
op.unmappedRequiredCanonicalFields.join(", ")
|
|
242
|
+
] })
|
|
243
|
+
] }),
|
|
244
|
+
/* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { flexShrink: 0 }, children: [
|
|
245
|
+
/* @__PURE__ */ jsx(
|
|
246
|
+
Button,
|
|
247
|
+
{
|
|
248
|
+
size: "small",
|
|
249
|
+
variant: "outlined",
|
|
250
|
+
color: "primary",
|
|
251
|
+
disabled: disableSuggest || isSuggesting || !op.isToolMapped,
|
|
252
|
+
title: !op.isToolMapped ? "Map a tool to this operation before suggesting a field translation" : void 0,
|
|
253
|
+
startIcon: isSuggesting ? /* @__PURE__ */ jsx(CircularProgress, { size: 14, color: "inherit" }) : void 0,
|
|
254
|
+
onClick: () => onSuggestWithAi(op),
|
|
255
|
+
children: isSuggesting ? "Suggesting\u2026" : "Suggest with AI"
|
|
256
|
+
}
|
|
257
|
+
),
|
|
258
|
+
/* @__PURE__ */ jsx(
|
|
259
|
+
Button,
|
|
260
|
+
{
|
|
261
|
+
size: "small",
|
|
262
|
+
variant: "text",
|
|
263
|
+
color: "primary",
|
|
264
|
+
onClick: () => onOpenMappingEditor(op),
|
|
265
|
+
children: "Open mapping editor"
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
] })
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
) }, op.operationKey);
|
|
272
|
+
};
|
|
273
|
+
return /* @__PURE__ */ jsxs(Box, { sx: { mb: 2 }, children: [
|
|
274
|
+
required.length > 0 && /* @__PURE__ */ jsxs(Alert, { severity: "warning", sx: { mb: optional.length > 0 ? 2 : 0 }, children: [
|
|
275
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "Required operations aren't fully mapped" }),
|
|
276
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "body2", sx: { mb: 1.5 }, children: [
|
|
277
|
+
required.length === 1 ? "1 required operation has" : `${required.length} required operations have`,
|
|
278
|
+
" ",
|
|
279
|
+
`fields that aren't mapped to this connector's tools yet. Ethisys Core can't run these reliably until every required field is mapped. Auto-match picks the TOOL, not its fields, so it won't clear these on its own: use "Suggest with AI" or the mapping editor per operation.`
|
|
280
|
+
] }),
|
|
281
|
+
/* @__PURE__ */ jsx(Stack, { spacing: 1.5, children: required.map(renderRow) })
|
|
282
|
+
] }),
|
|
283
|
+
optional.length > 0 && /* @__PURE__ */ jsxs(Alert, { severity: "info", children: [
|
|
284
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "Optional operations aren't fully mapped" }),
|
|
285
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "body2", sx: { mb: 1.5 }, children: [
|
|
286
|
+
optional.length === 1 ? "1 optional operation has fields that aren't" : `${optional.length} optional operations have fields that aren't`,
|
|
287
|
+
" ",
|
|
288
|
+
`mapped to this connector's tools. These power extra capabilities (e.g. pull-request quality and check-run insights) and stay off until their fields are mapped \u2014 safe to leave if you don't need them, or if this connector doesn't provide a matching tool. Each row below lists exactly which fields are missing; a row without a "No tool mapped" chip already HAS a tool and needs its fields mapped, which auto-match does not do.`
|
|
289
|
+
] }),
|
|
290
|
+
/* @__PURE__ */ jsx(Stack, { spacing: 1.5, children: optional.map(renderRow) })
|
|
291
|
+
] })
|
|
292
|
+
] });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/components/connectors/mappingQueryKeys.ts
|
|
296
|
+
var CONNECTOR_MAPPINGS_ROOT = "connector-mappings";
|
|
297
|
+
function connectorMappingsQueryKey(connectorId, moduleKey) {
|
|
298
|
+
return [CONNECTOR_MAPPINGS_ROOT, connectorId, moduleKey];
|
|
299
|
+
}
|
|
300
|
+
function connectorMappingCompletenessQueryKey(connectorId, moduleKey) {
|
|
301
|
+
return [...connectorMappingsQueryKey(connectorId, moduleKey), "completeness"];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export { MappingCompletenessBanner, connectorMappingCompletenessQueryKey, connectorMappingsQueryKey, selectIncompleteOperations, selectMappingReadiness, selectOptionalIncompleteOperations, selectRequiredIncompleteOperations };
|
|
305
|
+
//# sourceMappingURL=index.js.map
|
|
306
|
+
//# sourceMappingURL=index.js.map
|