@orchestrator-ui/orchestrator-ui-components 7.5.1 → 7.7.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.
Files changed (58) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-test.log +7 -7
  4. package/CHANGELOG.md +26 -0
  5. package/dist/index.d.ts +542 -53
  6. package/dist/index.js +1297 -1101
  7. package/dist/index.js.map +1 -1
  8. package/jest.config.cjs +4 -1
  9. package/package.json +6 -6
  10. package/src/components/WfoAgent/ExportButton/ExportButton.tsx +5 -11
  11. package/src/components/WfoAgent/WfoAgent/WfoAgent.tsx +79 -31
  12. package/src/components/WfoAgent/WfoAgentChart/WfoAgentLineChart.tsx +2 -2
  13. package/src/components/WfoAgent/WfoAgentChart/WfoAgentPieChart.tsx +2 -2
  14. package/src/components/WfoAgent/WfoAgentTable/WfoAgentTable.tsx +9 -9
  15. package/src/components/WfoAgent/WfoAgentVisualization/WfoAgentVisualization.tsx +2 -2
  16. package/src/components/WfoAgent/WfoPlanProgress/WfoPlanProgress.tsx +107 -0
  17. package/src/components/WfoAgent/WfoPlanProgress/index.ts +1 -0
  18. package/src/components/WfoAgent/WfoPlanProgress/styles.ts +62 -0
  19. package/src/components/WfoAgent/WfoQueryArtifact/WfoQueryArtifact.tsx +40 -0
  20. package/src/components/WfoAgent/WfoQueryArtifact/index.ts +1 -0
  21. package/src/components/WfoAgent/index.ts +2 -0
  22. package/src/components/WfoBadges/WfoEngineStatusBadge/WfoEngineStatusBadge.tsx +3 -1
  23. package/src/components/WfoBadges/WfoVersionIncompatibleBadge/WfoVersionIncompatibleBadge.tsx +7 -6
  24. package/src/components/WfoBadges/WfoWebsocketStatusBadge/WfoWebsocketStatusBadge.tsx +9 -3
  25. package/src/components/WfoKeyValueTable/WfoValueCell.tsx +1 -1
  26. package/src/components/WfoPydanticForm/fields/WfoInteger.tsx +22 -3
  27. package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActions.tsx +3 -2
  28. package/src/components/WfoSubscription/WfoSubscriptionGeneralSections/WfoSubscriptionDetailSection.tsx +34 -3
  29. package/src/components/WfoSubscriptionsList/WfoSubscriptionsList.tsx +9 -9
  30. package/src/components/WfoTable/WfoAdvancedTable/WfoAdvancedTable.tsx +1 -1
  31. package/src/components/WfoTable/WfoFirstPartUUID/WfoFirstPartUUID.tsx +1 -1
  32. package/src/components/WfoTitleWithWebsocketBadge/WfoTitleWithWebsocketBadge.tsx +3 -2
  33. package/src/components/WfoWorkflowSteps/WfoStep/WfoStep.tsx +57 -40
  34. package/src/components/WfoWorkflowSteps/WfoStep/useStepDetailOverride.ts +9 -0
  35. package/src/components/WfoWorkflowSteps/WfoWorkflowStepList/WfoStepListHeader.tsx +0 -1
  36. package/src/configuration/constants.ts +3 -0
  37. package/src/configuration/version.ts +1 -1
  38. package/src/hooks/useAgentPlanEvents.ts +188 -0
  39. package/src/messages/en-GB.json +7 -0
  40. package/src/messages/nl-NL.json +2 -0
  41. package/src/pages/metadata/WfoScheduleTaskFormPage.tsx +153 -35
  42. package/src/rtk/endpoints/agentQueryResults.ts +19 -0
  43. package/src/rtk/endpoints/forms.ts +1 -1
  44. package/src/rtk/endpoints/index.ts +1 -0
  45. package/src/rtk/endpoints/metadata/scheduledTasks.ts +9 -17
  46. package/src/rtk/endpoints/streamMessages.ts +10 -23
  47. package/src/rtk/slices/orchestratorComponentOverride.ts +5 -1
  48. package/src/types/search.ts +19 -4
  49. package/src/utils/compareVersions.spec.ts +5 -0
  50. package/src/utils/compareVersions.ts +55 -23
  51. package/src/components/WfoAgent/ToolProgress/DiscoverFilterPathsDisplay.tsx +0 -99
  52. package/src/components/WfoAgent/ToolProgress/RunSearchDisplay.tsx +0 -34
  53. package/src/components/WfoAgent/ToolProgress/SetFilterTreeDisplay.styles.ts +0 -62
  54. package/src/components/WfoAgent/ToolProgress/SetFilterTreeDisplay.tsx +0 -107
  55. package/src/components/WfoAgent/ToolProgress/StartNewSearchDisplay.tsx +0 -60
  56. package/src/components/WfoAgent/ToolProgress/ToolProgress.tsx +0 -98
  57. package/src/components/WfoAgent/ToolProgress/index.ts +0 -1
  58. package/src/components/WfoAgent/ToolProgress/styles.ts +0 -52
@@ -1,27 +1,33 @@
1
- import React from 'react';
1
+ import React, { useCallback, useMemo } from 'react';
2
2
 
3
3
  import _ from 'lodash';
4
4
  import { useTranslations } from 'next-intl';
5
5
  import { useRouter } from 'next/router';
6
- import {
7
- PydanticForm,
8
- PydanticFormApiResponseType,
9
- PydanticFormFieldFormat,
10
- PydanticFormFieldType,
11
- } from 'pydantic-forms';
12
- import type {
13
- PydanticFormApiProvider,
14
- PydanticFormDefinitionResponse,
15
- PydanticFormSuccessResponse,
16
- RawJsonProperties,
17
- } from 'pydantic-forms';
18
-
19
- import { Footer, PATH_METADATA_SCHEDULED_TASKS, WfoContentHeader, WfoLoading } from '@/components';
6
+ import { PydanticForm } from 'pydantic-forms';
7
+ import type { PydanticFormApiProvider } from 'pydantic-forms';
8
+ import { PydanticFormApiResponseType, PydanticFormFieldFormat, PydanticFormFieldType } from 'pydantic-forms';
9
+ import type { PydanticFormDefinitionResponse, PydanticFormSuccessResponse, RawJsonProperties } from 'pydantic-forms';
10
+
11
+ import { PATH_METADATA_SCHEDULED_TASKS } from '@/components';
12
+ import { WfoContentHeader, WfoLoading } from '@/components';
13
+ import { Footer } from '@/components/WfoPydanticForm/Footer';
20
14
  import { NUMBER_OF_ITEMS_REPRESENTING_ALL_ITEMS } from '@/configuration';
21
- import { useGetPydanticFormsConfig, useShowToastMessage } from '@/hooks';
22
- import type { CronKwargs, ScheduledTaskPostPayload } from '@/rtk';
23
- import { useCreateScheduledTaskMutation, useGetTasksQuery } from '@/rtk';
24
- import { Intervals, TaskDefinition, TaskType, ToastTypes } from '@/types';
15
+ import { useShowToastMessage } from '@/hooks';
16
+ import { useGetPydanticFormsConfig } from '@/hooks/useGetPydanticFormsConfig';
17
+ import {
18
+ HttpStatus,
19
+ ScheduledTaskPostPayload,
20
+ isFetchBaseQueryError,
21
+ isRecord,
22
+ useCreateScheduledTaskMutation,
23
+ } from '@/rtk';
24
+ import type { CronKwargs } from '@/rtk';
25
+ import { useGetTasksQuery } from '@/rtk';
26
+ import { useStartFormMutation } from '@/rtk/endpoints/forms';
27
+ import { useGetVersionsQuery } from '@/rtk/endpoints/versions';
28
+ import { ToastTypes } from '@/types';
29
+ import { Intervals, TaskDefinition, TaskType } from '@/types';
30
+ import { compareVersions } from '@/utils/compareVersions';
25
31
 
26
32
  type CreateScheduleFormStep1 = {
27
33
  workflowId: TaskDefinition['workflowId'];
@@ -50,7 +56,8 @@ type CreateScheduleFormStep2 =
50
56
 
51
57
  type CreateScheduleFormInput = [CreateScheduleFormStep1, CreateScheduleFormStep2];
52
58
 
53
- export const WfoScheduleTaskFormPage = () => {
59
+ // TODO: remove after 5.0.0 releases and rename WfoScheduleTaskFormPageBackend to WfoScheduleTaskFormPage
60
+ export const WfoScheduleTaskFormPageHardCoded = () => {
54
61
  const t = useTranslations('metadata.scheduleTaskForm');
55
62
  const { showToastMessage } = useShowToastMessage();
56
63
 
@@ -256,13 +263,15 @@ export const WfoScheduleTaskFormPage = () => {
256
263
 
257
264
  if (userInputStep1.taskType === TaskType.DATE) {
258
265
  return {
259
- type: userInputStep1.taskType,
260
- workflowId: task.workflowId,
261
- workflowDescription: task.description,
262
- workflowName: task.name,
263
- kwargs: {
266
+ scheduled_type: 'create',
267
+ workflow_id: task.workflowId,
268
+ workflow_name: task.name,
269
+ name: task.description,
270
+ trigger: userInputStep1.taskType,
271
+ trigger_kwargs: {
264
272
  run_date: startDate,
265
273
  },
274
+ user_inputs: [],
266
275
  };
267
276
  } else if (userInputStep1.taskType === TaskType.INTERVAL) {
268
277
  const step2Input = userInputStep2 as CreateScheduleFormStep2Interval;
@@ -273,24 +282,28 @@ export const WfoScheduleTaskFormPage = () => {
273
282
  }
274
283
 
275
284
  return {
276
- type: userInputStep1.taskType,
277
- workflowId: task.workflowId,
278
- workflowDescription: task.description,
279
- workflowName: task.name,
280
- kwargs: {
285
+ scheduled_type: 'create',
286
+ workflow_id: task.workflowId,
287
+ workflow_name: task.name,
288
+ name: task.description,
289
+ trigger: userInputStep1.taskType,
290
+ trigger_kwargs: {
281
291
  start_date: startDate,
282
292
  ...intervalArg,
283
293
  },
294
+ user_inputs: [],
284
295
  };
285
296
  } else if (userInputStep1.taskType === TaskType.CRON) {
286
297
  const step2Input = userInputStep2 as CreateScheduleFormStep2Cron;
287
298
  // minute hour day month weekday
288
299
  return {
289
- type: userInputStep1.taskType,
290
- workflowId: task.workflowId,
291
- workflowDescription: task.description,
292
- workflowName: task.name,
293
- kwargs: getCronKwargs(step2Input.cron, startDate),
300
+ scheduled_type: 'create',
301
+ workflow_id: task.workflowId,
302
+ workflow_name: task.name,
303
+ name: task.description,
304
+ trigger: userInputStep1.taskType,
305
+ trigger_kwargs: getCronKwargs(step2Input.cron, startDate),
306
+ user_inputs: [],
294
307
  };
295
308
  }
296
309
  throw new Error('Unknown or missing task type');
@@ -420,3 +433,108 @@ export const WfoScheduleTaskFormPage = () => {
420
433
  </>
421
434
  );
422
435
  };
436
+
437
+ const START_SCHEDULE_PAYLOAD = {};
438
+
439
+ export const WfoScheduleTaskFormPageBackend = () => {
440
+ const { showToastMessage } = useShowToastMessage();
441
+
442
+ const generateFormId = useMemo(() => {
443
+ return `${JSON.stringify(START_SCHEDULE_PAYLOAD)}`;
444
+ }, []);
445
+
446
+ const [startForm] = useStartFormMutation();
447
+ const [createScheduledTask, mutationState] = useCreateScheduledTaskMutation();
448
+ const router = useRouter();
449
+
450
+ const onSuccess = useCallback(
451
+ async (_fieldValues: object, req: object) => {
452
+ const request = req as {
453
+ status: HttpStatus;
454
+ data: ScheduledTaskPostPayload;
455
+ };
456
+ if (request?.data?.workflow_id) {
457
+ const resp = await createScheduledTask(request.data);
458
+ if (!resp?.error) {
459
+ router.replace(PATH_METADATA_SCHEDULED_TASKS);
460
+ }
461
+ }
462
+ },
463
+ [router, createScheduledTask],
464
+ );
465
+
466
+ const getPydanticFormProvider = useCallback(() => {
467
+ const pydanticFormProvider: PydanticFormApiProvider = async ({ requestBody = [], formKey }) => {
468
+ const userInputs =
469
+ _.isEmpty(START_SCHEDULE_PAYLOAD) ? [...requestBody] : [{ ...START_SCHEDULE_PAYLOAD }, ...requestBody];
470
+
471
+ const response = startForm({
472
+ formKey,
473
+ userInputs,
474
+ });
475
+ return response
476
+ .then(({ error, data }) => {
477
+ return new Promise<Record<string, unknown>>((resolve) => {
478
+ if (isFetchBaseQueryError(error) && isRecord(error.data)) {
479
+ if (error.status === HttpStatus.FormNotComplete) {
480
+ resolve(error.data);
481
+ } else if (error.status === HttpStatus.BadRequest) {
482
+ resolve({
483
+ ...error.data,
484
+ status: error.status,
485
+ });
486
+ }
487
+ } else if (data) {
488
+ resolve({
489
+ data,
490
+ status: HttpStatus.Created,
491
+ });
492
+ }
493
+
494
+ resolve({});
495
+ });
496
+ })
497
+ .catch((error) => {
498
+ return new Promise<Record<string, object>>((resolve, reject) => {
499
+ if (error.status === HttpStatus.FormNotComplete) {
500
+ resolve(error.data);
501
+ }
502
+ reject(error);
503
+ });
504
+ });
505
+ };
506
+
507
+ return pydanticFormProvider;
508
+ }, [startForm]);
509
+
510
+ const config = useGetPydanticFormsConfig(getPydanticFormProvider, (props) => <Footer {...props} />);
511
+
512
+ const handleCancel = useCallback(() => {
513
+ const pfBasePath = PATH_METADATA_SCHEDULED_TASKS;
514
+ router.replace(pfBasePath);
515
+ }, [router]);
516
+
517
+ if (mutationState.isError) {
518
+ showToastMessage(ToastTypes.ERROR, '', 'Error while saving scheduled task');
519
+ console.error('Error saving scheduled task', mutationState);
520
+ return undefined;
521
+ }
522
+
523
+ return (
524
+ <PydanticForm
525
+ formKey="configure_schedule"
526
+ formId={generateFormId}
527
+ onSuccess={onSuccess}
528
+ onCancel={handleCancel}
529
+ config={config}
530
+ />
531
+ );
532
+ };
533
+
534
+ export const WfoScheduleTaskFormPage = () => {
535
+ const { data } = useGetVersionsQuery();
536
+ const coreVersion = data?.version.applicationVersions[0].split(' ')[1] ?? '';
537
+
538
+ const isCompatible = compareVersions(coreVersion, '5.0.0a7') !== -1;
539
+ return isCompatible ? <WfoScheduleTaskFormPageBackend /> : <WfoScheduleTaskFormPageHardCoded />;
540
+ };
@@ -0,0 +1,19 @@
1
+ import { SEARCH_QUERY_RESULTS_ENDPOINT } from '@/configuration';
2
+ import { BaseQueryTypes, orchestratorApi } from '@/rtk';
3
+ import { QueryResultsData } from '@/types';
4
+
5
+ const agentQueryResultsApi = orchestratorApi.injectEndpoints({
6
+ endpoints: (builder) => ({
7
+ getAgentQueryResults: builder.query<QueryResultsData, string>({
8
+ query: (queryId) => ({
9
+ url: `${SEARCH_QUERY_RESULTS_ENDPOINT}/${queryId}/results`,
10
+ method: 'GET',
11
+ }),
12
+ extraOptions: {
13
+ baseQueryType: BaseQueryTypes.fetch,
14
+ },
15
+ }),
16
+ }),
17
+ });
18
+
19
+ export const { useGetAgentQueryResultsQuery } = agentQueryResultsApi;
@@ -2,7 +2,7 @@ import { BaseQueryTypes, orchestratorApi } from '@/rtk';
2
2
 
3
3
  const PROCESS_ENDPOINT = 'processes';
4
4
  const RESUME_ENDPOINT = 'resume';
5
- const FORMS_ENDPOINT = 'surf/forms/'; // It is still being used by example-wfo-ui
5
+ const FORMS_ENDPOINT = 'forms/'; // It is still being used by example-wfo-ui
6
6
 
7
7
  const formsApi = orchestratorApi.injectEndpoints({
8
8
  endpoints: (build) => ({
@@ -20,3 +20,4 @@ export * from './forms';
20
20
  export * from './fileUpload';
21
21
  export * from './search';
22
22
  export * from './availability';
23
+ export * from './agentQueryResults';
@@ -7,7 +7,6 @@ import {
7
7
  GraphqlQueryVariables,
8
8
  ScheduledTaskDefinition,
9
9
  ScheduledTasksDefinitionsResult,
10
- TaskType,
11
10
  } from '@/types';
12
11
 
13
12
  export const scheduledTasks = `
@@ -41,7 +40,7 @@ export type ScheduledTasksResponse = {
41
40
  schedules: ScheduledTaskDefinition[];
42
41
  } & BaseGraphQlResult;
43
42
 
44
- /*
43
+ /*
45
44
  https://apscheduler.readthedocs.io/en/3.x/modules/triggers/interval.html#module-apscheduler.triggers.interval
46
45
  Possible parameters:
47
46
  weeks (int) – number of weeks to wait
@@ -105,11 +104,13 @@ export type CronKwargs = {
105
104
  type Kwargs = DateKwargs | InterValKwargs | CronKwargs;
106
105
 
107
106
  export type ScheduledTaskPostPayload = {
108
- workflowId: string;
109
- workflowName: string;
110
- workflowDescription: string;
111
- type: TaskType;
112
- kwargs: Kwargs;
107
+ workflow_id: string;
108
+ workflow_name: string;
109
+ name: string;
110
+ trigger: string;
111
+ trigger_kwargs: Kwargs;
112
+ scheduled_type: string;
113
+ user_inputs: object[];
113
114
  };
114
115
 
115
116
  const scheduledTasksApi = orchestratorApi.injectEndpoints({
@@ -149,22 +150,13 @@ const scheduledTasksApi = orchestratorApi.injectEndpoints({
149
150
  }),
150
151
  createScheduledTask: builder.mutation<unknown, ScheduledTaskPostPayload>({
151
152
  query: (payload) => {
152
- const scheduleTaskPayload = {
153
- scheduled_type: 'create',
154
- name: payload.workflowDescription,
155
- workflow_name: payload.workflowName,
156
- workflow_id: payload.workflowId,
157
- trigger: payload.type,
158
- trigger_kwargs: payload.kwargs,
159
- };
160
-
161
153
  return {
162
154
  url: METADATA_SCHEDULES_ENDPOINT,
163
155
  method: 'POST',
164
156
  headers: {
165
157
  'Content-Type': 'application/json',
166
158
  },
167
- body: scheduleTaskPayload,
159
+ body: payload,
168
160
  };
169
161
  },
170
162
  extraOptions: {
@@ -1,8 +1,7 @@
1
1
  import { debounce } from 'lodash';
2
2
 
3
3
  import { getWebSocket, orchestratorApi } from '@/rtk';
4
- import type { RootState } from '@/rtk/store';
5
- import { CacheTag, CacheTagType } from '@/types';
4
+ import { CacheTag } from '@/types';
6
5
 
7
6
  const PING_INTERVAL_MS = 30000;
8
7
  const NO_PONG_RECEIVED_TIMEOUT_MS = 35000;
@@ -29,32 +28,26 @@ enum MessageTypes {
29
28
  * - It invalidates the cache entry with the tag received in the message event
30
29
  * - WfoWebsocketStatusBadge contains logic that handles automatic reconnection and their circumstances
31
30
  */
31
+
32
32
  const streamMessagesApi = orchestratorApi.injectEndpoints({
33
33
  endpoints: (build) => ({
34
- streamMessages: build.query<boolean, void>({
34
+ streamMessages: build.query<boolean, string>({
35
35
  queryFn: () => {
36
36
  return { data: true };
37
37
  },
38
- async onCacheEntryAdded(_, { cacheDataLoaded, cacheEntryRemoved, dispatch, getState, updateCachedData }) {
38
+ async onCacheEntryAdded(wsEndpoint, { cacheDataLoaded, cacheEntryRemoved, dispatch, updateCachedData }) {
39
39
  const cleanUp = () => {
40
40
  clearInterval(pingInterval);
41
41
  updateCachedData(() => false);
42
42
  };
43
43
 
44
44
  const invalidateTag = (cacheTag: CacheTag) => {
45
- if (validCacheTags.includes(cacheTag.type)) {
46
- const cacheInvalidationAction = orchestratorApi.util.invalidateTags([cacheTag]);
47
- dispatch(cacheInvalidationAction);
48
- } else {
49
- console.error(`Trying to invalidate a cache entry with an unknown tag: ${cacheTag.type}`);
50
- }
45
+ const cacheInvalidationAction = orchestratorApi.util.invalidateTags([cacheTag]);
46
+ dispatch(cacheInvalidationAction);
51
47
  };
52
48
 
53
49
  await cacheDataLoaded;
54
50
  let initialConnection = true;
55
- const state = getState() as RootState;
56
- const { orchestratorWebsocketUrl } = state.orchestratorConfig;
57
- const validCacheTags = Object.values(CacheTagType);
58
51
 
59
52
  const getDebounce = (delay: number) => {
60
53
  return debounce(() => {
@@ -68,8 +61,7 @@ const streamMessagesApi = orchestratorApi.injectEndpoints({
68
61
  const closeConnectionAfterFirstPing = getDebounce(INITIAL_CONNECTION_CHECK_INTERVAL_MS);
69
62
  const debounceClosingConnection = getDebounce(NO_PONG_RECEIVED_TIMEOUT_MS);
70
63
 
71
- // Starts the websocket
72
- const webSocket = await getWebSocket(orchestratorWebsocketUrl);
64
+ const webSocket = await getWebSocket(wsEndpoint);
73
65
 
74
66
  const sendPing = () => {
75
67
  if (webSocket.readyState === WebSocket.OPEN) {
@@ -82,9 +74,7 @@ const streamMessagesApi = orchestratorApi.injectEndpoints({
82
74
  // run less frequently at the discretion of the browser causing the websocket to disconnect
83
75
  // sometimes. WfoWebsocketStatusBadge contains logic to reconnect based on the pageVisibility api
84
76
  // to handle that situation.
85
- const pingInterval = setInterval(() => {
86
- sendPing();
87
- }, PING_INTERVAL_MS);
77
+ const pingInterval = setInterval(sendPing, PING_INTERVAL_MS);
88
78
 
89
79
  webSocket.onopen = () => {
90
80
  // Check the connection right after it is established
@@ -106,6 +96,7 @@ const streamMessagesApi = orchestratorApi.injectEndpoints({
106
96
  }
107
97
  return;
108
98
  }
99
+
109
100
  const message = JSON.parse(data) as WebSocketMessage;
110
101
  if (message.name === MessageTypes.invalidateCache) {
111
102
  invalidateTag(message.value);
@@ -114,17 +105,13 @@ const streamMessagesApi = orchestratorApi.injectEndpoints({
114
105
  }
115
106
  });
116
107
 
117
- webSocket.onerror = (event) => {
118
- console.error('WebSocket error', event);
119
- };
120
-
108
+ webSocket.onerror = (event) => console.error('WebSocket error', event);
121
109
  webSocket.onclose = () => {
122
110
  console.error('WebSocket closed');
123
111
  cleanUp();
124
112
  };
125
113
 
126
114
  await cacheEntryRemoved;
127
-
128
115
  webSocket.close();
129
116
  },
130
117
  }),
@@ -2,7 +2,7 @@ import { ReactElement, ReactNode } from 'react';
2
2
 
3
3
  import { Slice, createSlice } from '@reduxjs/toolkit';
4
4
 
5
- import { FieldValue, RenderableFieldValue, SubscriptionDetail } from '@/types';
5
+ import { FieldValue, RenderableFieldValue, Step, SubscriptionDetail } from '@/types';
6
6
 
7
7
  export type ValueOverrideFunction = (
8
8
  fieldValue: FieldValue | RenderableFieldValue,
@@ -19,6 +19,10 @@ export type OrchestratorComponentOverride = {
19
19
  startPage?: {
20
20
  summaryCardConfigurationOverride?: (defaultItems: ReactElement[]) => ReactElement[];
21
21
  };
22
+ stepDetail?: {
23
+ stepHeader?: React.JSXElementConstructor<{ step?: Step }>;
24
+ stepBody?: React.JSXElementConstructor<{ step?: Step }>;
25
+ };
22
26
  subscriptionDetail?: {
23
27
  valueOverrides?: ValueOverrideConfiguration;
24
28
  generalSectionConfigurationOverride?: (
@@ -156,7 +156,7 @@ export type PathInfo = {
156
156
 
157
157
  /** ---------- Agent visualization types ---------- */
158
158
 
159
- export type AggregationResult = {
159
+ export type ResultRow = {
160
160
  group_values: Record<string, string>;
161
161
  aggregations: Record<string, number>;
162
162
  };
@@ -172,11 +172,26 @@ export enum VisualizationType {
172
172
  TABLE = 'table',
173
173
  }
174
174
 
175
- export type AggregationResultsData = {
176
- results: AggregationResult[];
177
- total_groups: number;
175
+ export type QueryResultsData = {
176
+ results: ResultRow[];
177
+ total_results: number;
178
178
  metadata: SearchMetadata;
179
179
  visualization_type: {
180
180
  type: VisualizationType;
181
181
  };
182
182
  };
183
+
184
+ export type QueryArtifact = {
185
+ query_id: string;
186
+ total_results: number;
187
+ visualization_type: {
188
+ type: VisualizationType;
189
+ };
190
+ description: string;
191
+ };
192
+
193
+ export type ExportArtifact = {
194
+ query_id: string;
195
+ download_url: string;
196
+ description: string;
197
+ };
@@ -48,4 +48,9 @@ describe('isOrchestratorUiVersionCompatible', () => {
48
48
  expect(getOrchestratorCoreVersionIfNotCompatible('1.0.0', '2.10.0', TEST_VERSIONS)).toBe(null); // falls back to first MAPPED_VERSION
49
49
  expect(getOrchestratorCoreVersionIfNotCompatible('1.0.0', '2.9.9', TEST_VERSIONS)).toBe('2.10.0');
50
50
  });
51
+
52
+ test('handles prerelease orchestratorCoreVersion (e.g. 5.0.0a7)', () => {
53
+ expect(getOrchestratorCoreVersionIfNotCompatible('3.10.0', '5.0.0a7', TEST_VERSIONS)).toBe(null);
54
+ expect(getOrchestratorCoreVersionIfNotCompatible('3.10.0', '3.1.1a1', TEST_VERSIONS)).toBe('3.1.1');
55
+ });
51
56
  });
@@ -1,33 +1,62 @@
1
1
  import { MappedVersion } from '@/types';
2
2
 
3
- const compareVersions = (versionString1: string, versionString2: string): number => {
4
- const splittedVersion1 = versionString1.split('.');
5
- const splittedVersion2 = versionString2.split('.');
6
-
7
- for (let i = 0; i < splittedVersion1.length; i++) {
8
- if (parseInt(splittedVersion1[i]) > parseInt(splittedVersion2[i])) {
9
- return 1;
10
- } else if (parseInt(splittedVersion1[i]) < parseInt(splittedVersion2[i])) {
11
- return -1;
3
+ export const compareVersions = (v1: string, v2: string): number => {
4
+ const parse = (v: string) => {
5
+ const parts = v.split('.');
6
+
7
+ const major = parseInt(parts[0]);
8
+ const minor = parseInt(parts[1]);
9
+
10
+ const patchPart = parts[2] ?? '';
11
+
12
+ let patch = 0;
13
+ let suffix = '';
14
+
15
+ // extract numeric patch and suffix manually
16
+ for (let i = 0; i < patchPart.length; i++) {
17
+ const char = patchPart[i];
18
+
19
+ if (char >= '0' && char <= '9') {
20
+ patch = patch * 10 + Number(char);
21
+ } else {
22
+ suffix = patchPart.slice(i);
23
+ break;
24
+ }
12
25
  }
13
- }
14
- return 0;
26
+
27
+ return { major, minor, patch, suffix };
28
+ };
29
+
30
+ const a = parse(v1);
31
+ const b = parse(v2);
32
+
33
+ // compare numeric parts
34
+ if (a.major !== b.major) return a.major > b.major ? 1 : -1;
35
+ if (a.minor !== b.minor) return a.minor > b.minor ? 1 : -1;
36
+ if (a.patch !== b.patch) return a.patch > b.patch ? 1 : -1;
37
+
38
+ // compare suffix
39
+ if (a.suffix === b.suffix) return 0;
40
+ if (!a.suffix) return 1; // stable > pre-release
41
+ if (!b.suffix) return -1;
42
+
43
+ return a.suffix > b.suffix ? 1 : -1;
15
44
  };
16
45
 
17
46
  const findMinimumOrchestratorCoreVersion = (
18
47
  orchestratorUiVersion: string,
19
48
  versionMappings: MappedVersion[],
20
- ): string => {
21
- const orchestratorUiVersions = versionMappings.map((version) => version.orchestratorUiVersion);
22
- let mappedVersion = versionMappings[0];
49
+ ): string | null => {
50
+ // sort mappings descending by UI version. This is done just in case the input versionMappings are not sorted
51
+ const sorted = [...versionMappings].sort((a, b) => compareVersions(b.orchestratorUiVersion, a.orchestratorUiVersion));
23
52
 
24
- for (let i = 0; i < orchestratorUiVersions.length; i++) {
25
- if ([0, 1].includes(compareVersions(orchestratorUiVersion, orchestratorUiVersions[i]))) {
26
- mappedVersion = versionMappings[i];
53
+ for (const mapping of sorted) {
54
+ if (compareVersions(orchestratorUiVersion, mapping.orchestratorUiVersion) >= 0) {
55
+ return mapping.minimumOrchestratorCoreVersion;
27
56
  }
28
57
  }
29
58
 
30
- return mappedVersion.minimumOrchestratorCoreVersion;
59
+ return sorted[sorted.length - 1]?.minimumOrchestratorCoreVersion ?? null;
31
60
  };
32
61
 
33
62
  export const getOrchestratorCoreVersionIfNotCompatible = (
@@ -35,10 +64,13 @@ export const getOrchestratorCoreVersionIfNotCompatible = (
35
64
  orchestratorCoreVersion: string,
36
65
  versionMappings: MappedVersion[],
37
66
  ): string | null => {
38
- const minimumOrchestratorCoreVersion = findMinimumOrchestratorCoreVersion(orchestratorUiVersion, versionMappings);
39
- const comparison = compareVersions(orchestratorCoreVersion, minimumOrchestratorCoreVersion);
40
- if (comparison === -1) {
41
- return minimumOrchestratorCoreVersion;
67
+ const minimumVersion = findMinimumOrchestratorCoreVersion(orchestratorUiVersion, versionMappings);
68
+
69
+ if (!minimumVersion) {
70
+ return null;
42
71
  }
43
- return null;
72
+
73
+ const isCompatible = compareVersions(orchestratorCoreVersion, minimumVersion) !== -1;
74
+
75
+ return isCompatible ? null : minimumVersion;
44
76
  };