@ethisyscore/plugin-ui 1.93.0 → 1.95.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 +2 -1
- package/dist/components/connectors/index.cjs +656 -2
- package/dist/components/connectors/index.cjs.map +1 -1
- package/dist/components/connectors/index.d.cts +135 -373
- package/dist/components/connectors/index.d.ts +135 -373
- package/dist/components/connectors/index.js +652 -5
- package/dist/components/connectors/index.js.map +1 -1
- package/dist/components/ui/index.cjs +4 -0
- package/dist/components/ui/index.d.cts +31 -1
- package/dist/components/ui/index.d.ts +31 -1
- package/dist/components/ui/index.js +1 -1
- package/dist/connector-mappings/index.cjs +1061 -0
- package/dist/connector-mappings/index.cjs.map +1 -0
- package/dist/connector-mappings/index.d.cts +1274 -0
- package/dist/connector-mappings/index.d.ts +1274 -0
- package/dist/connector-mappings/index.js +1020 -0
- package/dist/connector-mappings/index.js.map +1 -0
- package/dist/mappingQueryKeys-BcbA0TGy.d.cts +473 -0
- package/dist/mappingQueryKeys-BcbA0TGy.d.ts +473 -0
- package/dist/mcpService-4h3sKDpW.d.cts +35 -0
- package/dist/mcpService-4h3sKDpW.d.ts +35 -0
- package/dist/platform-react/index.cjs +52 -4
- package/dist/platform-react/index.cjs.map +1 -1
- package/dist/platform-react/index.d.cts +30 -35
- package/dist/platform-react/index.d.ts +30 -35
- package/dist/platform-react/index.js +53 -6
- package/dist/platform-react/index.js.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,1061 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var reactQuery = require('@tanstack/react-query');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
|
|
6
|
+
// src/connector-mappings/ConnectorMappingService.ts
|
|
7
|
+
function pluginTargetSurfaceUnavailable(method, detail) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
`ConnectorMappingService.${method} is not available. ${detail} Subclass this service with your own implementation, or leave the plugin-target surface unmounted (isPluginTargetSurfaceAvailable is false on this implementation).`
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
var ConnectorMappingService = class {
|
|
13
|
+
constructor(invoker) {
|
|
14
|
+
this.invoker = invoker;
|
|
15
|
+
}
|
|
16
|
+
invoker;
|
|
17
|
+
/** Single seam every method goes through, so a subclass can wrap logging or retries in one place. */
|
|
18
|
+
async tool(name, args = {}) {
|
|
19
|
+
return await this.invoker(name, args);
|
|
20
|
+
}
|
|
21
|
+
async getMappings(connectorId, moduleKey) {
|
|
22
|
+
const response = await this.tool("connectors:mappings:list", {
|
|
23
|
+
connectorId,
|
|
24
|
+
moduleKey
|
|
25
|
+
});
|
|
26
|
+
return response?.items ?? [];
|
|
27
|
+
}
|
|
28
|
+
async updateMapping(connectorId, moduleKey, operationKey, toolName, rowVersion, requestFieldMap, responseFieldPaths, staticRequestFields) {
|
|
29
|
+
const response = await this.tool("connectors:mappings:set", {
|
|
30
|
+
connectorId,
|
|
31
|
+
moduleKey,
|
|
32
|
+
operationKey,
|
|
33
|
+
toolName,
|
|
34
|
+
rowVersion,
|
|
35
|
+
// Coalesced to explicit nulls rather than left `undefined`. The host treats both the same
|
|
36
|
+
// way, but an explicit null states on the wire that the caller MEANT no override, which is
|
|
37
|
+
// what makes a request log readable when a translation goes missing.
|
|
38
|
+
requestFieldMap: requestFieldMap ?? null,
|
|
39
|
+
responseFieldPaths: responseFieldPaths ?? null,
|
|
40
|
+
staticRequestFields: staticRequestFields ?? null
|
|
41
|
+
});
|
|
42
|
+
return response?.rowVersion ?? null;
|
|
43
|
+
}
|
|
44
|
+
async deleteMapping(connectorId, moduleKey, operationKey, rowVersion) {
|
|
45
|
+
await this.tool("connectors:mappings:delete", {
|
|
46
|
+
connectorId,
|
|
47
|
+
moduleKey,
|
|
48
|
+
operationKey,
|
|
49
|
+
rowVersion
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async autoMatch(connectorId, moduleKey) {
|
|
53
|
+
const response = await this.tool(
|
|
54
|
+
"connectors:mappings:auto-match",
|
|
55
|
+
{ connectorId, moduleKey }
|
|
56
|
+
);
|
|
57
|
+
return {
|
|
58
|
+
items: response?.items ?? [],
|
|
59
|
+
aiAvailable: response?.aiAvailable ?? false,
|
|
60
|
+
aiUnavailableReason: response?.aiUnavailableReason ?? null,
|
|
61
|
+
unmatchedRequiredCount: response?.unmatchedRequiredCount ?? 0,
|
|
62
|
+
conflictReason: response?.conflictReason ?? null
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async getMappingCompleteness(connectorId, moduleKey) {
|
|
66
|
+
const response = await this.tool(
|
|
67
|
+
"connectors:mappings:completeness",
|
|
68
|
+
{ connectorId, moduleKey }
|
|
69
|
+
);
|
|
70
|
+
return response ?? { operations: [] };
|
|
71
|
+
}
|
|
72
|
+
async suggestMappingTranslation(connectorId, moduleKey, body) {
|
|
73
|
+
const response = await this.tool(
|
|
74
|
+
"connectors:mappings:suggest-translation",
|
|
75
|
+
{
|
|
76
|
+
connectorId,
|
|
77
|
+
moduleKey,
|
|
78
|
+
operationKey: body.operationKey,
|
|
79
|
+
toolName: body.toolName,
|
|
80
|
+
// The host declares SampleArgs non-nullable; an absent sample is an empty object, not null.
|
|
81
|
+
sampleArgs: body.sampleArgs ?? {}
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
return response ?? {
|
|
85
|
+
success: false,
|
|
86
|
+
error: "The host returned no suggestion.",
|
|
87
|
+
// Empty objects, not null: the contract types these as `Record<string, string>`, and an
|
|
88
|
+
// empty map is the accurate statement — no rewrites proposed.
|
|
89
|
+
requestFieldMap: {},
|
|
90
|
+
responseFieldPaths: {}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async testMapping(connectorId, moduleKey, body) {
|
|
94
|
+
const response = await this.tool("connectors:mappings:test", {
|
|
95
|
+
connectorId,
|
|
96
|
+
moduleKey,
|
|
97
|
+
operationKey: body.operationKey,
|
|
98
|
+
toolName: body.toolName,
|
|
99
|
+
requestFieldMap: body.requestFieldMap,
|
|
100
|
+
responseFieldPaths: body.responseFieldPaths,
|
|
101
|
+
staticRequestFields: body.staticRequestFields,
|
|
102
|
+
sampleArgs: body.sampleArgs
|
|
103
|
+
});
|
|
104
|
+
return response ?? {
|
|
105
|
+
success: false,
|
|
106
|
+
error: "The host returned no result for the mapping test.",
|
|
107
|
+
rawResponseJson: null,
|
|
108
|
+
extractedFields: []
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// -------------------------------------------------------------------------
|
|
112
|
+
// IConnectorEnumerationService — optional, see the interface's remarks
|
|
113
|
+
// -------------------------------------------------------------------------
|
|
114
|
+
async listConnectors() {
|
|
115
|
+
const response = await this.tool(
|
|
116
|
+
"settings:list-connectors",
|
|
117
|
+
{}
|
|
118
|
+
);
|
|
119
|
+
return (response?.items ?? []).map((c) => ({
|
|
120
|
+
id: c.id,
|
|
121
|
+
name: c.name,
|
|
122
|
+
slug: c.slug ?? null,
|
|
123
|
+
isEnabled: c.isEnabled
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
async getModuleProvider(moduleKey) {
|
|
127
|
+
const response = await this.tool("connectors:mappings:get-provider", {
|
|
128
|
+
moduleKey
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
targetKind: response?.targetKind ?? null,
|
|
132
|
+
connectorId: response?.connectorId ?? null,
|
|
133
|
+
targetExtensionId: response?.targetExtensionId ?? null,
|
|
134
|
+
rowVersion: response?.rowVersion ?? null
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async setModuleProvider(moduleKey, targetKind, connectorId, targetExtensionId, rowVersion) {
|
|
138
|
+
const response = await this.tool("connectors:mappings:set-provider", {
|
|
139
|
+
moduleKey,
|
|
140
|
+
targetKind,
|
|
141
|
+
connectorId,
|
|
142
|
+
targetExtensionId,
|
|
143
|
+
rowVersion
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
targetKind: response?.targetKind ?? targetKind,
|
|
147
|
+
connectorId: response?.connectorId ?? null,
|
|
148
|
+
targetExtensionId: response?.targetExtensionId ?? null,
|
|
149
|
+
rowVersion: response?.rowVersion ?? null
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
async listConnectorTools(connectorId) {
|
|
153
|
+
const response = await this.tool(
|
|
154
|
+
"settings:get-connector",
|
|
155
|
+
{ id: connectorId }
|
|
156
|
+
);
|
|
157
|
+
return response?.tools ?? [];
|
|
158
|
+
}
|
|
159
|
+
// -------------------------------------------------------------------------
|
|
160
|
+
// The PLUGIN-TARGET surface - declared, and deliberately not reachable yet
|
|
161
|
+
//
|
|
162
|
+
// The per-module PROVIDER choice above is reachable: CoreConnect-Api #1329 added
|
|
163
|
+
// `connectors:mappings:get-provider` and `connectors:mappings:set-provider`, so an operator can
|
|
164
|
+
// record that a module is served by an installed app. What they still cannot do from a plugin UI
|
|
165
|
+
// is everything BELOW that choice:
|
|
166
|
+
//
|
|
167
|
+
// - the candidate list has no endpoint in ANY transport, so who is on offer has to be supplied to
|
|
168
|
+
// this package rather than fetched by it;
|
|
169
|
+
// - `connectors:mappings:list` takes a connectorId and its rows carry no TargetKind or
|
|
170
|
+
// TargetExtensionId, so there is no connector-less read for a plugin-served module;
|
|
171
|
+
// - `SetPluginTargetMapping` exists as a REST route only
|
|
172
|
+
// (`PUT /api/v1/connectors/modules/{moduleKey}/operations/{operationKey}/plugin-target`), and a
|
|
173
|
+
// plugin's UI reaches the host over MCP, so the per-operation plugin-target write cannot be
|
|
174
|
+
// called from here.
|
|
175
|
+
//
|
|
176
|
+
// Implemented as refusals rather than omitted so the shape a real transport must satisfy is
|
|
177
|
+
// written down in one place, and so the gap is a message an operator's log can carry rather than
|
|
178
|
+
// a method that silently is not there. `isPluginTargetSurfaceAvailable` is the flag a caller
|
|
179
|
+
// reads; none of the methods below is called while it is false.
|
|
180
|
+
// -------------------------------------------------------------------------
|
|
181
|
+
/**
|
|
182
|
+
* Whether this transport can serve the plugin-target methods below. `false` here, and the three
|
|
183
|
+
* methods throw, for the reasons in the block comment above.
|
|
184
|
+
*
|
|
185
|
+
* A property and not a guess: "the method exists" and "the method works" are different facts, and
|
|
186
|
+
* a caller that probed by calling and catching would turn a known gap into a request, an error
|
|
187
|
+
* toast and a support ticket.
|
|
188
|
+
*/
|
|
189
|
+
isPluginTargetSurfaceAvailable = false;
|
|
190
|
+
async listPluginProviders(moduleKey) {
|
|
191
|
+
pluginTargetSurfaceUnavailable(
|
|
192
|
+
"listPluginProviders",
|
|
193
|
+
"The host exposes no candidate listing in any transport. Its routable-tool listing is documented as unsafe to show an end user verbatim because it applies no caller-permission filter, and the write path additionally refuses any tool the consuming application did not declare in its manifest - so the candidate set is a server-side decision, not a client-side filter of a broader list."
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
async getPluginTargetMappings(moduleKey) {
|
|
197
|
+
pluginTargetSurfaceUnavailable(
|
|
198
|
+
"getPluginTargetMappings",
|
|
199
|
+
"The host's mapping list read is scoped BY connector (connectors:mappings:list takes a connectorId) and its rows carry no TargetKind or TargetExtensionId, so a plugin-target row has neither a connector to scope it by nor a way to be recognised in the result. No connector-less list exists."
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
async setPluginTargetMapping(moduleKey, args) {
|
|
203
|
+
pluginTargetSurfaceUnavailable(
|
|
204
|
+
"setPluginTargetMapping",
|
|
205
|
+
"The host serves PUT /api/v1/connectors/modules/{moduleKey}/operations/{operationKey}/plugin-target over REST only; there is no connectors:mappings:* tool for it, and a plugin's UI reaches the host over MCP."
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
var CONNECTOR_MAPPING_TOOLS = [
|
|
210
|
+
"connectors:mappings:list",
|
|
211
|
+
"connectors:mappings:set",
|
|
212
|
+
"connectors:mappings:delete",
|
|
213
|
+
"connectors:mappings:auto-match",
|
|
214
|
+
"connectors:mappings:completeness",
|
|
215
|
+
"connectors:mappings:suggest-translation",
|
|
216
|
+
"connectors:mappings:test",
|
|
217
|
+
"connectors:mappings:get-provider",
|
|
218
|
+
"connectors:mappings:set-provider"
|
|
219
|
+
];
|
|
220
|
+
var CONNECTOR_ENUMERATION_TOOLS = [
|
|
221
|
+
"settings:list-connectors",
|
|
222
|
+
"settings:get-connector"
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
// src/components/connectors/mappingQueryKeys.ts
|
|
226
|
+
var CONNECTOR_MAPPINGS_ROOT = "connector-mappings";
|
|
227
|
+
function connectorMappingsQueryKey(connectorId, moduleKey) {
|
|
228
|
+
return [CONNECTOR_MAPPINGS_ROOT, connectorId, moduleKey];
|
|
229
|
+
}
|
|
230
|
+
function connectorMappingsRootQueryKey() {
|
|
231
|
+
return [CONNECTOR_MAPPINGS_ROOT];
|
|
232
|
+
}
|
|
233
|
+
function pluginTargetMappingsQueryKey(moduleKey) {
|
|
234
|
+
return [CONNECTOR_MAPPINGS_ROOT, "plugin-target", moduleKey];
|
|
235
|
+
}
|
|
236
|
+
function connectorMappingCompletenessQueryKey(connectorId, moduleKey) {
|
|
237
|
+
return [...connectorMappingsQueryKey(connectorId, moduleKey), "completeness"];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/components/connectors/mappingCompleteness.ts
|
|
241
|
+
function hasUnmappedRequiredField(op) {
|
|
242
|
+
return op.unmappedRequiredCanonicalFields.length > 0;
|
|
243
|
+
}
|
|
244
|
+
function selectIncompleteOperations(operations) {
|
|
245
|
+
return operations.filter(hasUnmappedRequiredField);
|
|
246
|
+
}
|
|
247
|
+
function selectRequiredIncompleteOperations(operations) {
|
|
248
|
+
return operations.filter((op) => op.isRequired && hasUnmappedRequiredField(op));
|
|
249
|
+
}
|
|
250
|
+
function selectOptionalIncompleteOperations(operations) {
|
|
251
|
+
return operations.filter((op) => !op.isRequired && hasUnmappedRequiredField(op));
|
|
252
|
+
}
|
|
253
|
+
function selectMappingReadiness(operations) {
|
|
254
|
+
const required = operations.filter((op) => op.isRequired);
|
|
255
|
+
const complete = required.filter(
|
|
256
|
+
(op) => op.isToolMapped && op.unmappedRequiredCanonicalFields.length === 0
|
|
257
|
+
).length;
|
|
258
|
+
return {
|
|
259
|
+
complete,
|
|
260
|
+
total: required.length,
|
|
261
|
+
isReady: required.length > 0 && complete === required.length
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function useAuthenticatedQuery(options) {
|
|
265
|
+
const result = reactQuery.useQuery(options);
|
|
266
|
+
return {
|
|
267
|
+
...result,
|
|
268
|
+
isLoading: result.isLoading,
|
|
269
|
+
isAuthLoading: false
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/connector-mappings/useConnectorMappings.ts
|
|
274
|
+
function useConnectorMappings({
|
|
275
|
+
service,
|
|
276
|
+
connectorId,
|
|
277
|
+
moduleKey
|
|
278
|
+
}) {
|
|
279
|
+
const query = useAuthenticatedQuery({
|
|
280
|
+
queryKey: connectorMappingsQueryKey(connectorId, moduleKey),
|
|
281
|
+
queryFn: () => service.getMappings(connectorId, moduleKey),
|
|
282
|
+
enabled: !!connectorId && !!moduleKey,
|
|
283
|
+
meta: { errorTitle: "Error loading tool mappings" }
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
mappings: query.data ?? [],
|
|
287
|
+
isLoading: query.isLoading,
|
|
288
|
+
isFetching: query.isFetching,
|
|
289
|
+
dataUpdatedAt: query.dataUpdatedAt,
|
|
290
|
+
error: query.error ?? void 0,
|
|
291
|
+
refetch: () => void query.refetch()
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function useConnectorMappingCompleteness({
|
|
295
|
+
service,
|
|
296
|
+
connectorId,
|
|
297
|
+
moduleKey
|
|
298
|
+
}) {
|
|
299
|
+
const query = useAuthenticatedQuery({
|
|
300
|
+
queryKey: connectorMappingCompletenessQueryKey(connectorId, moduleKey),
|
|
301
|
+
queryFn: () => service.getMappingCompleteness(connectorId, moduleKey),
|
|
302
|
+
enabled: !!connectorId && !!moduleKey,
|
|
303
|
+
meta: { errorTitle: "Error loading mapping completeness" }
|
|
304
|
+
});
|
|
305
|
+
const operations = query.data?.operations ?? [];
|
|
306
|
+
return {
|
|
307
|
+
operations,
|
|
308
|
+
readiness: selectMappingReadiness(operations),
|
|
309
|
+
isLoading: query.isLoading,
|
|
310
|
+
isFetching: query.isFetching
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/components/shared/toast.ts
|
|
315
|
+
function dispatchToast(title, description, variant) {
|
|
316
|
+
if (typeof window === "undefined" || title.length === 0) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
window.dispatchEvent(new CustomEvent("ethisys:toast", { detail: { title, description, variant } }));
|
|
320
|
+
}
|
|
321
|
+
function showSuccessToast(title, description) {
|
|
322
|
+
dispatchToast(title, description, "success");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/connector-mappings/types.ts
|
|
326
|
+
var MAPPING_SOURCE = {
|
|
327
|
+
keyword: "keyword",
|
|
328
|
+
ai: "ai",
|
|
329
|
+
manual: "manual"
|
|
330
|
+
};
|
|
331
|
+
var AI_UNAVAILABLE_REASON = {
|
|
332
|
+
Timeout: "timeout",
|
|
333
|
+
ProviderError: "provider_error",
|
|
334
|
+
ParseError: "parse_error",
|
|
335
|
+
ContextTooLarge: "context_too_large",
|
|
336
|
+
NoConnectorConfigured: "no_connector_configured"
|
|
337
|
+
};
|
|
338
|
+
var AUTO_MATCH_CONFLICT_REASON = {
|
|
339
|
+
AnotherAutoMatchInProgress: "another_auto_match_in_progress"
|
|
340
|
+
};
|
|
341
|
+
var MAPPING_TARGET_KIND = {
|
|
342
|
+
connector: "connector",
|
|
343
|
+
plugin: "plugin"
|
|
344
|
+
};
|
|
345
|
+
var MODULE_PROVIDER_KIND = {
|
|
346
|
+
Connector: "Connector",
|
|
347
|
+
Plugin: "Plugin"
|
|
348
|
+
};
|
|
349
|
+
var MAPPING_TARGET_KIND_WIRE = {
|
|
350
|
+
connector: MODULE_PROVIDER_KIND.Connector,
|
|
351
|
+
plugin: MODULE_PROVIDER_KIND.Plugin
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// src/connector-mappings/useConnectorMappingMutations.ts
|
|
355
|
+
function resolveTranslationMember(supplied, stored) {
|
|
356
|
+
if (supplied !== void 0) return supplied;
|
|
357
|
+
return stored ?? null;
|
|
358
|
+
}
|
|
359
|
+
function summarise(response) {
|
|
360
|
+
const items = response?.items ?? [];
|
|
361
|
+
return {
|
|
362
|
+
aiAvailable: response?.aiAvailable ?? false,
|
|
363
|
+
aiUnavailableReason: response?.aiUnavailableReason ?? null,
|
|
364
|
+
unmatchedRequiredCount: response?.unmatchedRequiredCount ?? 0,
|
|
365
|
+
matchedByKeyword: items.filter((i) => i.source === MAPPING_SOURCE.keyword).length,
|
|
366
|
+
matchedByAi: items.filter((i) => i.source === MAPPING_SOURCE.ai).length,
|
|
367
|
+
conflictReason: response?.conflictReason ?? null
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function useConnectorMappingMutations({
|
|
371
|
+
service,
|
|
372
|
+
connectorId,
|
|
373
|
+
moduleKey,
|
|
374
|
+
onMappingSaved,
|
|
375
|
+
showSuccessToast: showSuccessToast2 = showSuccessToast
|
|
376
|
+
}) {
|
|
377
|
+
const queryClient = reactQuery.useQueryClient();
|
|
378
|
+
const [autoMatchSummary, setAutoMatchSummary] = react.useState(null);
|
|
379
|
+
const scopeKey = `${connectorId ?? ""}|${moduleKey}`;
|
|
380
|
+
const summaryScopeRef = react.useRef(scopeKey);
|
|
381
|
+
react.useEffect(() => {
|
|
382
|
+
if (summaryScopeRef.current === scopeKey) return;
|
|
383
|
+
summaryScopeRef.current = scopeKey;
|
|
384
|
+
setAutoMatchSummary(null);
|
|
385
|
+
}, [scopeKey]);
|
|
386
|
+
const queryKey = react.useMemo(
|
|
387
|
+
() => connectorMappingsQueryKey(connectorId, moduleKey),
|
|
388
|
+
[connectorId, moduleKey]
|
|
389
|
+
);
|
|
390
|
+
const invalidateMappingsAndCompleteness = react.useCallback(() => {
|
|
391
|
+
void queryClient.invalidateQueries({ queryKey });
|
|
392
|
+
}, [queryClient, queryKey]);
|
|
393
|
+
const autoMatch = reactQuery.useMutation({
|
|
394
|
+
mutationFn: () => service.autoMatch(connectorId, moduleKey),
|
|
395
|
+
meta: { errorTitle: "Auto-match failed" },
|
|
396
|
+
onSuccess: (response) => {
|
|
397
|
+
setAutoMatchSummary(summarise(response));
|
|
398
|
+
invalidateMappingsAndCompleteness();
|
|
399
|
+
const matched = (response?.items ?? []).filter((i) => i.mappedToolName).length;
|
|
400
|
+
if (connectorId && matched > 0) {
|
|
401
|
+
onMappingSaved?.(connectorId, moduleKey);
|
|
402
|
+
}
|
|
403
|
+
showSuccessToast2("Auto-match complete", `Matched ${matched} operations.`);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
const updateMapping = reactQuery.useMutation({
|
|
407
|
+
mutationFn: (args) => service.updateMapping(
|
|
408
|
+
connectorId,
|
|
409
|
+
moduleKey,
|
|
410
|
+
args.operationKey,
|
|
411
|
+
args.toolName,
|
|
412
|
+
args.rowVersion,
|
|
413
|
+
args.requestFieldMap,
|
|
414
|
+
args.responseFieldPaths,
|
|
415
|
+
args.staticRequestFields
|
|
416
|
+
),
|
|
417
|
+
meta: { errorTitle: "Update failed" },
|
|
418
|
+
onSuccess: () => {
|
|
419
|
+
invalidateMappingsAndCompleteness();
|
|
420
|
+
if (connectorId) {
|
|
421
|
+
onMappingSaved?.(connectorId, moduleKey);
|
|
422
|
+
}
|
|
423
|
+
showSuccessToast2("Mapping updated", "The tool mapping was saved.");
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
const deleteMapping = reactQuery.useMutation({
|
|
427
|
+
mutationFn: (args) => service.deleteMapping(connectorId, moduleKey, args.operationKey, args.rowVersion),
|
|
428
|
+
meta: { errorTitle: "Remove failed" },
|
|
429
|
+
onSuccess: () => {
|
|
430
|
+
invalidateMappingsAndCompleteness();
|
|
431
|
+
showSuccessToast2("Mapping removed", "The tool mapping was cleared.");
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
const triggerAutoMatch = react.useCallback(() => {
|
|
435
|
+
if (!connectorId) return;
|
|
436
|
+
autoMatch.mutate();
|
|
437
|
+
}, [autoMatch, connectorId]);
|
|
438
|
+
const updateOrDelete = react.useCallback(
|
|
439
|
+
(args, callbacks) => {
|
|
440
|
+
if (!connectorId) return;
|
|
441
|
+
if (args.toolName === "") {
|
|
442
|
+
if (args.rowVersion === "") return;
|
|
443
|
+
deleteMapping.mutate(
|
|
444
|
+
{ operationKey: args.operationKey, rowVersion: args.rowVersion },
|
|
445
|
+
callbacks
|
|
446
|
+
);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const rows = queryClient.getQueryData(queryKey) ?? [];
|
|
450
|
+
const current = rows.find((m) => m.operationKey === args.operationKey);
|
|
451
|
+
updateMapping.mutate(
|
|
452
|
+
{
|
|
453
|
+
...args,
|
|
454
|
+
requestFieldMap: resolveTranslationMember(args.requestFieldMap, current?.requestFieldMap),
|
|
455
|
+
responseFieldPaths: resolveTranslationMember(
|
|
456
|
+
args.responseFieldPaths,
|
|
457
|
+
current?.responseFieldPaths
|
|
458
|
+
),
|
|
459
|
+
staticRequestFields: resolveTranslationMember(
|
|
460
|
+
args.staticRequestFields,
|
|
461
|
+
current?.staticRequestFields
|
|
462
|
+
)
|
|
463
|
+
},
|
|
464
|
+
callbacks
|
|
465
|
+
);
|
|
466
|
+
},
|
|
467
|
+
[connectorId, updateMapping, deleteMapping, queryClient, queryKey]
|
|
468
|
+
);
|
|
469
|
+
return {
|
|
470
|
+
triggerAutoMatch,
|
|
471
|
+
isAutoMatching: autoMatch.isPending,
|
|
472
|
+
updateMapping: updateOrDelete,
|
|
473
|
+
isUpdating: updateMapping.isPending || deleteMapping.isPending,
|
|
474
|
+
autoMatchSummary
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function useSuggestMappingTranslation(service) {
|
|
478
|
+
return reactQuery.useMutation({
|
|
479
|
+
mutationFn: async (vars) => {
|
|
480
|
+
const result = await service.suggestMappingTranslation(
|
|
481
|
+
vars.connectorId,
|
|
482
|
+
vars.moduleKey,
|
|
483
|
+
vars.body
|
|
484
|
+
);
|
|
485
|
+
if (!result.success) {
|
|
486
|
+
throw new Error(
|
|
487
|
+
result.error ?? "The AI couldn't suggest a translation for this operation."
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
return result;
|
|
491
|
+
},
|
|
492
|
+
meta: { errorTitle: "Suggest translation failed" }
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
function useTestMapping(service) {
|
|
496
|
+
return reactQuery.useMutation({
|
|
497
|
+
mutationFn: (vars) => service.testMapping(vars.connectorId, vars.moduleKey, vars.body),
|
|
498
|
+
meta: { errorTitle: "Mapping test failed" }
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
function testUnavailable(error) {
|
|
502
|
+
return { success: false, error, rawResponseJson: null, extractedFields: [] };
|
|
503
|
+
}
|
|
504
|
+
function useMappingTranslationEditor({
|
|
505
|
+
service,
|
|
506
|
+
connectorId,
|
|
507
|
+
moduleKey,
|
|
508
|
+
mappings,
|
|
509
|
+
updateMapping
|
|
510
|
+
}) {
|
|
511
|
+
const [editor, setEditor] = react.useState(null);
|
|
512
|
+
const scopeKey = `${connectorId ?? ""}|${moduleKey}`;
|
|
513
|
+
const editorScopeRef = react.useRef(scopeKey);
|
|
514
|
+
react.useEffect(() => {
|
|
515
|
+
if (editorScopeRef.current === scopeKey) return;
|
|
516
|
+
editorScopeRef.current = scopeKey;
|
|
517
|
+
setEditor(null);
|
|
518
|
+
}, [scopeKey]);
|
|
519
|
+
const suggest = useSuggestMappingTranslation(service);
|
|
520
|
+
const test = useTestMapping(service);
|
|
521
|
+
const onEditTranslation = react.useCallback(
|
|
522
|
+
(operationKey, current) => {
|
|
523
|
+
setEditor({ operationKey, current });
|
|
524
|
+
},
|
|
525
|
+
[]
|
|
526
|
+
);
|
|
527
|
+
const onCancel = react.useCallback(() => setEditor(null), []);
|
|
528
|
+
const onOpenMappingEditor = react.useCallback(
|
|
529
|
+
(operation, seed) => {
|
|
530
|
+
const row = mappings.find((m) => m.operationKey === operation.operationKey);
|
|
531
|
+
setEditor({
|
|
532
|
+
operationKey: operation.operationKey,
|
|
533
|
+
current: seed ?? {
|
|
534
|
+
requestFieldMap: row?.requestFieldMap ?? null,
|
|
535
|
+
responseFieldPaths: row?.responseFieldPaths ?? null,
|
|
536
|
+
staticRequestFields: row?.staticRequestFields ?? null
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
},
|
|
540
|
+
[mappings]
|
|
541
|
+
);
|
|
542
|
+
const onSuggestWithAi = react.useCallback(
|
|
543
|
+
(operation) => {
|
|
544
|
+
if (!connectorId) return;
|
|
545
|
+
const row = mappings.find((m) => m.operationKey === operation.operationKey);
|
|
546
|
+
suggest.mutate(
|
|
547
|
+
{
|
|
548
|
+
connectorId,
|
|
549
|
+
moduleKey,
|
|
550
|
+
body: { operationKey: operation.operationKey, toolName: row?.mappedToolName ?? "" }
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
onSuccess: (result) => {
|
|
554
|
+
if (!result.success) return;
|
|
555
|
+
onOpenMappingEditor(operation, {
|
|
556
|
+
requestFieldMap: result.requestFieldMap,
|
|
557
|
+
responseFieldPaths: result.responseFieldPaths,
|
|
558
|
+
// Preserved, not proposed. See the remarks above.
|
|
559
|
+
staticRequestFields: row?.staticRequestFields ?? null
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
);
|
|
564
|
+
},
|
|
565
|
+
// `moduleKey` is a real dependency, not a module-scope constant: omitting it would capture
|
|
566
|
+
// whichever module was selected when this callback was created, so a multi-module consumer that
|
|
567
|
+
// switched module and then asked for a suggestion would get advice on the previous catalogue.
|
|
568
|
+
[connectorId, moduleKey, mappings, suggest, onOpenMappingEditor]
|
|
569
|
+
);
|
|
570
|
+
const onSave = react.useCallback(
|
|
571
|
+
(requestFieldMap, responseFieldPaths, staticRequestFields) => {
|
|
572
|
+
if (!editor) return;
|
|
573
|
+
const row = mappings.find((m) => m.operationKey === editor.operationKey);
|
|
574
|
+
if (!row || !row.mappedToolName) {
|
|
575
|
+
setEditor(null);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
updateMapping({
|
|
579
|
+
operationKey: row.operationKey,
|
|
580
|
+
toolName: row.mappedToolName,
|
|
581
|
+
rowVersion: row.rowVersion ?? "",
|
|
582
|
+
// Passed through verbatim, INCLUDING nulls. The editor delivers `null` for a side whose
|
|
583
|
+
// every input the operator emptied, and that is an explicit "clear this" the fill-in in
|
|
584
|
+
// `useConnectorMappingMutations` must not overwrite.
|
|
585
|
+
requestFieldMap,
|
|
586
|
+
responseFieldPaths,
|
|
587
|
+
staticRequestFields
|
|
588
|
+
});
|
|
589
|
+
setEditor(null);
|
|
590
|
+
},
|
|
591
|
+
[editor, mappings, updateMapping]
|
|
592
|
+
);
|
|
593
|
+
const onTestMapping = react.useCallback(
|
|
594
|
+
async (request) => {
|
|
595
|
+
if (!editor || !connectorId) {
|
|
596
|
+
return testUnavailable("No connector or operation selected.");
|
|
597
|
+
}
|
|
598
|
+
const row = mappings.find((m) => m.operationKey === editor.operationKey);
|
|
599
|
+
if (!row?.mappedToolName) {
|
|
600
|
+
return testUnavailable("No tool mapped to this operation.");
|
|
601
|
+
}
|
|
602
|
+
return test.mutateAsync({
|
|
603
|
+
connectorId,
|
|
604
|
+
moduleKey,
|
|
605
|
+
body: {
|
|
606
|
+
operationKey: editor.operationKey,
|
|
607
|
+
toolName: row.mappedToolName,
|
|
608
|
+
requestFieldMap: request.requestFieldMap,
|
|
609
|
+
responseFieldPaths: request.responseFieldPaths,
|
|
610
|
+
// The editor's DRAFT static params, not the persisted row's, so the dry-run exercises the
|
|
611
|
+
// unsaved discriminator the operator is actually testing.
|
|
612
|
+
staticRequestFields: request.staticRequestFields ?? null,
|
|
613
|
+
sampleArgs: request.sampleArgs
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
},
|
|
617
|
+
// Same reason as `onSuggestWithAi`: a stale `moduleKey` here would dry-run the edit against a
|
|
618
|
+
// different catalogue and report the result as if it were this one's.
|
|
619
|
+
[editor, connectorId, moduleKey, mappings, test]
|
|
620
|
+
);
|
|
621
|
+
const editorRow = editor ? mappings.find((m) => m.operationKey === editor.operationKey) : void 0;
|
|
622
|
+
return {
|
|
623
|
+
editorRow,
|
|
624
|
+
isOpen: editor !== null,
|
|
625
|
+
onEditTranslation,
|
|
626
|
+
onOpenMappingEditor,
|
|
627
|
+
onSuggestWithAi,
|
|
628
|
+
suggestingOperationKey: suggest.isPending ? suggest.variables?.body.operationKey ?? null : null,
|
|
629
|
+
editorProps: {
|
|
630
|
+
// Always `true`: the consumer gates the MOUNT on `isOpen`, which is what makes the editor's
|
|
631
|
+
// lazy state initialisation reseed from the row each time it opens.
|
|
632
|
+
open: true,
|
|
633
|
+
operationLabel: editorRow?.label,
|
|
634
|
+
toolName: editorRow?.mappedToolName,
|
|
635
|
+
canonicalRequestFields: editorRow?.canonicalRequestFields,
|
|
636
|
+
canonicalResponseFields: editorRow?.canonicalResponseFields,
|
|
637
|
+
requestFieldMap: editor?.current.requestFieldMap ?? null,
|
|
638
|
+
responseFieldPaths: editor?.current.responseFieldPaths ?? null,
|
|
639
|
+
staticRequestFields: editor?.current.staticRequestFields ?? null,
|
|
640
|
+
onSave,
|
|
641
|
+
onCancel,
|
|
642
|
+
onTestMapping
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
function useAutoMatchOnConnectorSwitch({
|
|
647
|
+
connectorId,
|
|
648
|
+
moduleKey,
|
|
649
|
+
mappings,
|
|
650
|
+
isLoading,
|
|
651
|
+
isAutoMatching,
|
|
652
|
+
dataUpdatedAt,
|
|
653
|
+
triggerAutoMatch,
|
|
654
|
+
initiallySelected = false
|
|
655
|
+
}) {
|
|
656
|
+
const connectorChangedAtRef = react.useRef(initiallySelected ? Date.now() : 0);
|
|
657
|
+
const autoMatchedRef = react.useRef(null);
|
|
658
|
+
const scopeKey = `${connectorId ?? ""}|${moduleKey}`;
|
|
659
|
+
const markConnectorChanged = react.useCallback(() => {
|
|
660
|
+
connectorChangedAtRef.current = Date.now();
|
|
661
|
+
}, []);
|
|
662
|
+
react.useEffect(() => {
|
|
663
|
+
if (!connectorId) return;
|
|
664
|
+
if (isLoading || isAutoMatching) return;
|
|
665
|
+
if (dataUpdatedAt <= connectorChangedAtRef.current) return;
|
|
666
|
+
if (autoMatchedRef.current === scopeKey) return;
|
|
667
|
+
if (mappings.length > 0 && mappings.some((m) => m.mappedToolName)) return;
|
|
668
|
+
autoMatchedRef.current = scopeKey;
|
|
669
|
+
triggerAutoMatch();
|
|
670
|
+
}, [connectorId, scopeKey, mappings, isLoading, isAutoMatching, dataUpdatedAt, triggerAutoMatch]);
|
|
671
|
+
return { markConnectorChanged };
|
|
672
|
+
}
|
|
673
|
+
var STORAGE_KEY_PREFIX = "cc-connector-mapping-banner-dismissed";
|
|
674
|
+
function readDismissed(storageKey) {
|
|
675
|
+
if (!storageKey) return false;
|
|
676
|
+
try {
|
|
677
|
+
return localStorage.getItem(storageKey) === "true";
|
|
678
|
+
} catch {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function useMappingBannerDismissal(organisationId, moduleKey) {
|
|
683
|
+
const storageKey = organisationId ? `${STORAGE_KEY_PREFIX}:${organisationId}:${moduleKey}` : null;
|
|
684
|
+
const [dismissed, setDismissed] = react.useState(() => readDismissed(storageKey));
|
|
685
|
+
const appliedKeyRef = react.useRef(storageKey);
|
|
686
|
+
react.useEffect(() => {
|
|
687
|
+
if (appliedKeyRef.current === storageKey) return;
|
|
688
|
+
appliedKeyRef.current = storageKey;
|
|
689
|
+
setDismissed(readDismissed(storageKey));
|
|
690
|
+
}, [storageKey]);
|
|
691
|
+
const dismiss = react.useCallback(() => {
|
|
692
|
+
if (!storageKey) return;
|
|
693
|
+
try {
|
|
694
|
+
localStorage.setItem(storageKey, "true");
|
|
695
|
+
} catch {
|
|
696
|
+
}
|
|
697
|
+
setDismissed(true);
|
|
698
|
+
}, [storageKey]);
|
|
699
|
+
const reset = react.useCallback(() => {
|
|
700
|
+
if (!storageKey) return;
|
|
701
|
+
try {
|
|
702
|
+
localStorage.removeItem(storageKey);
|
|
703
|
+
} catch {
|
|
704
|
+
}
|
|
705
|
+
setDismissed(false);
|
|
706
|
+
}, [storageKey]);
|
|
707
|
+
return { dismissed, dismiss, reset };
|
|
708
|
+
}
|
|
709
|
+
function moduleProviderQueryKey(moduleKey) {
|
|
710
|
+
return ["connector-mappings", "module-provider", moduleKey];
|
|
711
|
+
}
|
|
712
|
+
var UNCONFIGURED = {
|
|
713
|
+
targetKind: null,
|
|
714
|
+
connectorId: null,
|
|
715
|
+
targetExtensionId: null,
|
|
716
|
+
rowVersion: null
|
|
717
|
+
};
|
|
718
|
+
function useModuleProvider({
|
|
719
|
+
service,
|
|
720
|
+
moduleKey,
|
|
721
|
+
onProviderChanged
|
|
722
|
+
}) {
|
|
723
|
+
const queryClient = reactQuery.useQueryClient();
|
|
724
|
+
const queryKey = react.useMemo(() => moduleProviderQueryKey(moduleKey), [moduleKey]);
|
|
725
|
+
const query = useAuthenticatedQuery({
|
|
726
|
+
queryKey,
|
|
727
|
+
queryFn: () => service.getModuleProvider(moduleKey),
|
|
728
|
+
enabled: !!moduleKey,
|
|
729
|
+
meta: { errorTitle: "Error loading the module's provider" }
|
|
730
|
+
});
|
|
731
|
+
const provider = query.data ?? UNCONFIGURED;
|
|
732
|
+
const save = reactQuery.useMutation({
|
|
733
|
+
mutationFn: (next) => service.setModuleProvider(
|
|
734
|
+
moduleKey,
|
|
735
|
+
next.targetKind,
|
|
736
|
+
next.connectorId,
|
|
737
|
+
next.targetExtensionId,
|
|
738
|
+
provider.rowVersion
|
|
739
|
+
),
|
|
740
|
+
meta: { errorTitle: "Could not change the module's provider" },
|
|
741
|
+
onSuccess: (stored) => {
|
|
742
|
+
queryClient.setQueryData(queryKey, stored);
|
|
743
|
+
onProviderChanged?.(stored);
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
const selectConnector = react.useCallback(
|
|
747
|
+
(connectorId) => save.mutate({
|
|
748
|
+
// An empty id is the CLEAR, and it must reach the host as one rather than being dropped
|
|
749
|
+
// here: leaving the previous selection in place is exactly the "connector is stuck forever"
|
|
750
|
+
// behaviour this surface has had to fix once already.
|
|
751
|
+
targetKind: MODULE_PROVIDER_KIND.Connector,
|
|
752
|
+
connectorId: connectorId || null,
|
|
753
|
+
targetExtensionId: null
|
|
754
|
+
}),
|
|
755
|
+
[save]
|
|
756
|
+
);
|
|
757
|
+
const selectExtension = react.useCallback(
|
|
758
|
+
(extensionId) => save.mutate({
|
|
759
|
+
targetKind: MODULE_PROVIDER_KIND.Plugin,
|
|
760
|
+
connectorId: null,
|
|
761
|
+
targetExtensionId: extensionId || null
|
|
762
|
+
}),
|
|
763
|
+
[save]
|
|
764
|
+
);
|
|
765
|
+
return {
|
|
766
|
+
provider,
|
|
767
|
+
// Only a CONNECTOR selection produces a connector id. A module served by a plugin has no
|
|
768
|
+
// connector, and reporting one would make the mapping surface fetch rows for a connector the
|
|
769
|
+
// operator did not choose.
|
|
770
|
+
connectorId: provider.targetKind === MODULE_PROVIDER_KIND.Connector ? provider.connectorId ?? "" : "",
|
|
771
|
+
isLoading: query.isLoading,
|
|
772
|
+
selectConnector,
|
|
773
|
+
selectExtension,
|
|
774
|
+
isSaving: save.isPending,
|
|
775
|
+
error: query.error ?? save.error ?? void 0
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// src/connector-mappings/targetSelection.ts
|
|
780
|
+
function toWireTargetKind(kind) {
|
|
781
|
+
return kind === MAPPING_TARGET_KIND.plugin ? MAPPING_TARGET_KIND_WIRE.plugin : MAPPING_TARGET_KIND_WIRE.connector;
|
|
782
|
+
}
|
|
783
|
+
function fromWireTargetKind(value) {
|
|
784
|
+
if (value === MAPPING_TARGET_KIND_WIRE.connector) return MAPPING_TARGET_KIND.connector;
|
|
785
|
+
if (value === MAPPING_TARGET_KIND_WIRE.plugin) return MAPPING_TARGET_KIND.plugin;
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
function isSameTarget(existing, next) {
|
|
789
|
+
if (!existing) return false;
|
|
790
|
+
if (existing.targetKind !== next.kind) return false;
|
|
791
|
+
if (!existing.mappedToolName) return false;
|
|
792
|
+
if (existing.mappedToolName !== next.toolName) return false;
|
|
793
|
+
return next.kind === MAPPING_TARGET_KIND.connector ? existing.connectorId === next.connectorId : existing.targetExtensionId === next.targetExtensionId;
|
|
794
|
+
}
|
|
795
|
+
function buildMappingUpdate({
|
|
796
|
+
existing,
|
|
797
|
+
next,
|
|
798
|
+
translation
|
|
799
|
+
}) {
|
|
800
|
+
const sameTarget = isSameTarget(existing, next);
|
|
801
|
+
const decide = (member) => {
|
|
802
|
+
const supplied = translation?.[member];
|
|
803
|
+
if (supplied !== void 0) return supplied;
|
|
804
|
+
if (!sameTarget) return null;
|
|
805
|
+
return existing?.[member] ?? null;
|
|
806
|
+
};
|
|
807
|
+
const update = {
|
|
808
|
+
targetKind: next.kind,
|
|
809
|
+
toolName: next.toolName,
|
|
810
|
+
requestFieldMap: decide("requestFieldMap"),
|
|
811
|
+
responseFieldPaths: decide("responseFieldPaths"),
|
|
812
|
+
staticRequestFields: decide("staticRequestFields")
|
|
813
|
+
};
|
|
814
|
+
if (next.kind === MAPPING_TARGET_KIND.connector) {
|
|
815
|
+
update.connectorId = next.connectorId;
|
|
816
|
+
} else {
|
|
817
|
+
update.targetExtensionId = next.targetExtensionId;
|
|
818
|
+
}
|
|
819
|
+
return update;
|
|
820
|
+
}
|
|
821
|
+
function providerOf(target) {
|
|
822
|
+
return target.kind === MAPPING_TARGET_KIND.connector ? { kind: MAPPING_TARGET_KIND.connector, connectorId: target.connectorId } : { kind: MAPPING_TARGET_KIND.plugin, targetExtensionId: target.targetExtensionId };
|
|
823
|
+
}
|
|
824
|
+
function providerIdOf(provider) {
|
|
825
|
+
return provider.kind === MAPPING_TARGET_KIND.connector ? provider.connectorId : provider.targetExtensionId;
|
|
826
|
+
}
|
|
827
|
+
function useConnectorToolMappings({
|
|
828
|
+
service,
|
|
829
|
+
connectorId,
|
|
830
|
+
moduleKey,
|
|
831
|
+
moduleLabel,
|
|
832
|
+
connectors,
|
|
833
|
+
isConnectorsLoading = false,
|
|
834
|
+
availableTools,
|
|
835
|
+
onConnectorChange,
|
|
836
|
+
initiallySelected = false,
|
|
837
|
+
organisationId,
|
|
838
|
+
aiProvidersHref,
|
|
839
|
+
connectorSettingsHref,
|
|
840
|
+
withHeader,
|
|
841
|
+
onMappingSaved,
|
|
842
|
+
showSuccessToast: showSuccessToast2,
|
|
843
|
+
providerSelectionEnabled = false,
|
|
844
|
+
pluginProviders,
|
|
845
|
+
isPluginProvidersLoading = false
|
|
846
|
+
}) {
|
|
847
|
+
const {
|
|
848
|
+
provider: storedProvider,
|
|
849
|
+
connectorId: storedConnectorId,
|
|
850
|
+
isLoading: isProviderLoading,
|
|
851
|
+
selectConnector,
|
|
852
|
+
selectExtension
|
|
853
|
+
} = useModuleProvider({
|
|
854
|
+
service,
|
|
855
|
+
moduleKey: providerSelectionEnabled ? moduleKey : ""
|
|
856
|
+
});
|
|
857
|
+
const [pendingKind, setPendingKind] = react.useState(null);
|
|
858
|
+
const pendingKindModuleRef = react.useRef(moduleKey);
|
|
859
|
+
if (pendingKindModuleRef.current !== moduleKey) {
|
|
860
|
+
pendingKindModuleRef.current = moduleKey;
|
|
861
|
+
if (pendingKind !== null) setPendingKind(null);
|
|
862
|
+
}
|
|
863
|
+
const storedKind = fromWireTargetKind(storedProvider.targetKind);
|
|
864
|
+
const providerKind = providerSelectionEnabled ? pendingKind ?? storedKind ?? void 0 : void 0;
|
|
865
|
+
const isPluginMode = providerKind === MAPPING_TARGET_KIND.plugin;
|
|
866
|
+
const selectedPluginExtensionId = storedKind === MAPPING_TARGET_KIND.plugin ? storedProvider.targetExtensionId ?? "" : "";
|
|
867
|
+
const effectiveConnectorId = providerSelectionEnabled ? storedConnectorId : connectorId;
|
|
868
|
+
const selectedConnectorId = isPluginMode ? void 0 : effectiveConnectorId || void 0;
|
|
869
|
+
const scope = { service, connectorId: selectedConnectorId, moduleKey };
|
|
870
|
+
const { mappings, isLoading, dataUpdatedAt } = useConnectorMappings(scope);
|
|
871
|
+
const { operations: completenessOperations, readiness } = useConnectorMappingCompleteness(scope);
|
|
872
|
+
const { triggerAutoMatch, isAutoMatching, updateMapping, isUpdating, autoMatchSummary } = useConnectorMappingMutations({ ...scope, onMappingSaved, showSuccessToast: showSuccessToast2 });
|
|
873
|
+
const editor = useMappingTranslationEditor({
|
|
874
|
+
service,
|
|
875
|
+
connectorId: selectedConnectorId,
|
|
876
|
+
moduleKey,
|
|
877
|
+
mappings,
|
|
878
|
+
updateMapping
|
|
879
|
+
});
|
|
880
|
+
const { dismissed, dismiss, reset } = useMappingBannerDismissal(organisationId, moduleKey);
|
|
881
|
+
const { markConnectorChanged } = useAutoMatchOnConnectorSwitch({
|
|
882
|
+
connectorId: selectedConnectorId,
|
|
883
|
+
moduleKey,
|
|
884
|
+
mappings,
|
|
885
|
+
isLoading,
|
|
886
|
+
isAutoMatching,
|
|
887
|
+
dataUpdatedAt,
|
|
888
|
+
triggerAutoMatch,
|
|
889
|
+
initiallySelected
|
|
890
|
+
});
|
|
891
|
+
const handleProviderKindChange = react.useCallback(
|
|
892
|
+
(kind) => {
|
|
893
|
+
setPendingKind(kind);
|
|
894
|
+
},
|
|
895
|
+
[]
|
|
896
|
+
);
|
|
897
|
+
const handlePluginProviderChange = react.useCallback(
|
|
898
|
+
(extensionId) => {
|
|
899
|
+
selectExtension(extensionId);
|
|
900
|
+
},
|
|
901
|
+
[selectExtension]
|
|
902
|
+
);
|
|
903
|
+
const handleConnectorChange = react.useCallback(
|
|
904
|
+
(id) => {
|
|
905
|
+
onConnectorChange(id);
|
|
906
|
+
markConnectorChanged();
|
|
907
|
+
if (providerSelectionEnabled) selectConnector(id);
|
|
908
|
+
},
|
|
909
|
+
// `onConnectorChange` is a real dependency: a consumer that persists per organisation rebinds
|
|
910
|
+
// its setter once identity resolves, and an empty dep array would capture the pre-identity one
|
|
911
|
+
// and stop writing through.
|
|
912
|
+
[onConnectorChange, markConnectorChanged, providerSelectionEnabled, selectConnector]
|
|
913
|
+
);
|
|
914
|
+
const handleUpdateMapping = react.useCallback(
|
|
915
|
+
(operationKey, toolName, rowVersion) => {
|
|
916
|
+
if (!selectedConnectorId) return;
|
|
917
|
+
updateMapping({ operationKey, toolName, rowVersion });
|
|
918
|
+
},
|
|
919
|
+
[selectedConnectorId, updateMapping]
|
|
920
|
+
);
|
|
921
|
+
const handleAutoMatch = react.useCallback(() => {
|
|
922
|
+
if (!selectedConnectorId) return;
|
|
923
|
+
triggerAutoMatch();
|
|
924
|
+
}, [selectedConnectorId, triggerAutoMatch]);
|
|
925
|
+
const previousScopeRef = react.useRef({
|
|
926
|
+
connectorId: selectedConnectorId || void 0,
|
|
927
|
+
moduleKey
|
|
928
|
+
});
|
|
929
|
+
react.useEffect(() => {
|
|
930
|
+
const previous = previousScopeRef.current;
|
|
931
|
+
const current = { connectorId: selectedConnectorId || void 0, moduleKey };
|
|
932
|
+
previousScopeRef.current = current;
|
|
933
|
+
if (previous.moduleKey !== current.moduleKey) return;
|
|
934
|
+
if (previous.connectorId === current.connectorId) return;
|
|
935
|
+
reset();
|
|
936
|
+
}, [selectedConnectorId, moduleKey, reset]);
|
|
937
|
+
const effectiveAvailableTools = react.useMemo(() => {
|
|
938
|
+
if (!isPluginMode) return availableTools;
|
|
939
|
+
const chosen = (pluginProviders ?? []).find(
|
|
940
|
+
(p) => p.extensionId === selectedPluginExtensionId
|
|
941
|
+
);
|
|
942
|
+
return (chosen?.tools ?? []).map((t) => ({
|
|
943
|
+
name: t.toolName,
|
|
944
|
+
description: t.description ?? ""
|
|
945
|
+
}));
|
|
946
|
+
}, [isPluginMode, availableTools, pluginProviders, selectedPluginExtensionId]);
|
|
947
|
+
const providerOptions = react.useMemo(
|
|
948
|
+
() => (pluginProviders ?? []).map((p) => ({ extensionId: p.extensionId, name: p.name })),
|
|
949
|
+
[pluginProviders]
|
|
950
|
+
);
|
|
951
|
+
const surfaceIsLoading = isLoading || isConnectorsLoading || isProviderLoading || isPluginProvidersLoading;
|
|
952
|
+
return {
|
|
953
|
+
view: {
|
|
954
|
+
isLoading: surfaceIsLoading,
|
|
955
|
+
moduleKey,
|
|
956
|
+
moduleLabel,
|
|
957
|
+
withHeader,
|
|
958
|
+
connectors,
|
|
959
|
+
selectedConnectorId: selectedConnectorId ?? "",
|
|
960
|
+
onConnectorChange: handleConnectorChange,
|
|
961
|
+
// Spread conditionally: for a consumer that has not opted in these keys are ABSENT, not
|
|
962
|
+
// undefined, so the view's `providerPickerEnabled` check sees exactly what a pre-provider
|
|
963
|
+
// consumer passes.
|
|
964
|
+
...providerSelectionEnabled ? {
|
|
965
|
+
providerKind,
|
|
966
|
+
onProviderKindChange: handleProviderKindChange,
|
|
967
|
+
pluginProviders: providerOptions,
|
|
968
|
+
selectedPluginExtensionId,
|
|
969
|
+
onPluginProviderChange: handlePluginProviderChange
|
|
970
|
+
} : {},
|
|
971
|
+
mappings,
|
|
972
|
+
availableTools: effectiveAvailableTools,
|
|
973
|
+
onUpdateMapping: handleUpdateMapping,
|
|
974
|
+
onAutoMatch: handleAutoMatch,
|
|
975
|
+
isUpdating,
|
|
976
|
+
isAutoMatching,
|
|
977
|
+
bannerState: dismissed ? null : autoMatchSummary,
|
|
978
|
+
onDismissBanner: dismiss,
|
|
979
|
+
aiProvidersHref,
|
|
980
|
+
connectorSettingsHref,
|
|
981
|
+
onEditTranslation: editor.onEditTranslation
|
|
982
|
+
},
|
|
983
|
+
banner: {
|
|
984
|
+
operations: completenessOperations,
|
|
985
|
+
onSuggestWithAi: editor.onSuggestWithAi,
|
|
986
|
+
onOpenMappingEditor: editor.onOpenMappingEditor,
|
|
987
|
+
suggestingOperationKey: editor.suggestingOperationKey,
|
|
988
|
+
// False whenever the consumer has not opted in, not merely when a choice exists. A page with
|
|
989
|
+
// no provider control on it must not be told it has failed to use one. Also false while the
|
|
990
|
+
// read is in flight, so the banner does not accuse the operator during a page load.
|
|
991
|
+
isProviderUnconfigured: providerSelectionEnabled && !isProviderLoading && storedKind === null
|
|
992
|
+
},
|
|
993
|
+
editor: editor.editorProps,
|
|
994
|
+
isEditorOpen: editor.isOpen,
|
|
995
|
+
editorRow: editor.editorRow,
|
|
996
|
+
mappings,
|
|
997
|
+
completenessOperations,
|
|
998
|
+
readiness,
|
|
999
|
+
isLoading: surfaceIsLoading
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/connector-mappings/connectorOptions.ts
|
|
1004
|
+
function mapConnectorsToOptions(connectors) {
|
|
1005
|
+
return connectors.map((c) => ({ id: c.id, name: c.name }));
|
|
1006
|
+
}
|
|
1007
|
+
function mapConnectorToolsToOptions(tools) {
|
|
1008
|
+
return tools.filter((t) => t.isEnabled).map((t) => ({ name: t.toolName, description: t.description ?? "" }));
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/components/connectors/providerLabels.ts
|
|
1012
|
+
var PROVIDER_CONTROL_LABEL = "Served by";
|
|
1013
|
+
var PROVIDER_OPTION_LABEL = {
|
|
1014
|
+
plugin: "This organisation's apps",
|
|
1015
|
+
connector: "External system"
|
|
1016
|
+
};
|
|
1017
|
+
var PLUGIN_PROVIDER_CONTROL_LABEL = "App";
|
|
1018
|
+
var CONNECTOR_CONTROL_LABEL = "Connector";
|
|
1019
|
+
|
|
1020
|
+
exports.AI_UNAVAILABLE_REASON = AI_UNAVAILABLE_REASON;
|
|
1021
|
+
exports.AUTO_MATCH_CONFLICT_REASON = AUTO_MATCH_CONFLICT_REASON;
|
|
1022
|
+
exports.CONNECTOR_CONTROL_LABEL = CONNECTOR_CONTROL_LABEL;
|
|
1023
|
+
exports.CONNECTOR_ENUMERATION_TOOLS = CONNECTOR_ENUMERATION_TOOLS;
|
|
1024
|
+
exports.CONNECTOR_MAPPING_TOOLS = CONNECTOR_MAPPING_TOOLS;
|
|
1025
|
+
exports.ConnectorMappingService = ConnectorMappingService;
|
|
1026
|
+
exports.MAPPING_SOURCE = MAPPING_SOURCE;
|
|
1027
|
+
exports.MAPPING_TARGET_KIND = MAPPING_TARGET_KIND;
|
|
1028
|
+
exports.MAPPING_TARGET_KIND_WIRE = MAPPING_TARGET_KIND_WIRE;
|
|
1029
|
+
exports.MODULE_PROVIDER_KIND = MODULE_PROVIDER_KIND;
|
|
1030
|
+
exports.PLUGIN_PROVIDER_CONTROL_LABEL = PLUGIN_PROVIDER_CONTROL_LABEL;
|
|
1031
|
+
exports.PROVIDER_CONTROL_LABEL = PROVIDER_CONTROL_LABEL;
|
|
1032
|
+
exports.PROVIDER_OPTION_LABEL = PROVIDER_OPTION_LABEL;
|
|
1033
|
+
exports.buildMappingUpdate = buildMappingUpdate;
|
|
1034
|
+
exports.connectorMappingCompletenessQueryKey = connectorMappingCompletenessQueryKey;
|
|
1035
|
+
exports.connectorMappingsQueryKey = connectorMappingsQueryKey;
|
|
1036
|
+
exports.connectorMappingsRootQueryKey = connectorMappingsRootQueryKey;
|
|
1037
|
+
exports.fromWireTargetKind = fromWireTargetKind;
|
|
1038
|
+
exports.isSameTarget = isSameTarget;
|
|
1039
|
+
exports.mapConnectorToolsToOptions = mapConnectorToolsToOptions;
|
|
1040
|
+
exports.mapConnectorsToOptions = mapConnectorsToOptions;
|
|
1041
|
+
exports.moduleProviderQueryKey = moduleProviderQueryKey;
|
|
1042
|
+
exports.pluginTargetMappingsQueryKey = pluginTargetMappingsQueryKey;
|
|
1043
|
+
exports.providerIdOf = providerIdOf;
|
|
1044
|
+
exports.providerOf = providerOf;
|
|
1045
|
+
exports.selectIncompleteOperations = selectIncompleteOperations;
|
|
1046
|
+
exports.selectMappingReadiness = selectMappingReadiness;
|
|
1047
|
+
exports.selectOptionalIncompleteOperations = selectOptionalIncompleteOperations;
|
|
1048
|
+
exports.selectRequiredIncompleteOperations = selectRequiredIncompleteOperations;
|
|
1049
|
+
exports.toWireTargetKind = toWireTargetKind;
|
|
1050
|
+
exports.useAutoMatchOnConnectorSwitch = useAutoMatchOnConnectorSwitch;
|
|
1051
|
+
exports.useConnectorMappingCompleteness = useConnectorMappingCompleteness;
|
|
1052
|
+
exports.useConnectorMappingMutations = useConnectorMappingMutations;
|
|
1053
|
+
exports.useConnectorMappings = useConnectorMappings;
|
|
1054
|
+
exports.useConnectorToolMappings = useConnectorToolMappings;
|
|
1055
|
+
exports.useMappingBannerDismissal = useMappingBannerDismissal;
|
|
1056
|
+
exports.useMappingTranslationEditor = useMappingTranslationEditor;
|
|
1057
|
+
exports.useModuleProvider = useModuleProvider;
|
|
1058
|
+
exports.useSuggestMappingTranslation = useSuggestMappingTranslation;
|
|
1059
|
+
exports.useTestMapping = useTestMapping;
|
|
1060
|
+
//# sourceMappingURL=index.cjs.map
|
|
1061
|
+
//# sourceMappingURL=index.cjs.map
|