@mohsinonxrm/pptb-fetchxml-studio 1.0.0 → 1.0.1
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/LICENSE +661 -0
- package/dist/assets/index-DD9fzkbz.js +232 -0
- package/dist/index.html +70 -0
- package/index.html +0 -1
- package/npm-shrinkwrap.json +6329 -0
- package/package.json +95 -51
- package/CHANGELOG.md +0 -120
- package/eslint.config.js +0 -23
- package/public/vite.svg +0 -1
- package/src/app/AppShell.tsx +0 -1733
- package/src/assets/20px-Microsoft_Office_Excel_(2025/342/200/223present).svg.png +0 -0
- package/src/assets/react.svg +0 -1
- package/src/features/fetchxml/api/dataverseMetadata.ts +0 -192
- package/src/features/fetchxml/api/excelExport.ts +0 -318
- package/src/features/fetchxml/api/formattedValues.ts +0 -107
- package/src/features/fetchxml/api/pptbClient.ts +0 -2635
- package/src/features/fetchxml/model/displaySettings.ts +0 -18
- package/src/features/fetchxml/model/fetchxml.ts +0 -346
- package/src/features/fetchxml/model/fetchxmlIntellisense.ts +0 -583
- package/src/features/fetchxml/model/fetchxmlParser.ts +0 -708
- package/src/features/fetchxml/model/layoutxml.ts +0 -372
- package/src/features/fetchxml/model/nodes.ts +0 -255
- package/src/features/fetchxml/model/operators.ts +0 -624
- package/src/features/fetchxml/model/treeUtils.ts +0 -174
- package/src/features/fetchxml/state/builderStore.tsx +0 -1160
- package/src/features/fetchxml/state/cache.ts +0 -393
- package/src/features/fetchxml/ui/Dialogs/BulkDeleteDialog.tsx +0 -352
- package/src/features/fetchxml/ui/Dialogs/DeleteConfirmDialog.tsx +0 -396
- package/src/features/fetchxml/ui/Dialogs/SaveViewDialog.tsx +0 -612
- package/src/features/fetchxml/ui/Dialogs/SolutionPicker.tsx +0 -114
- package/src/features/fetchxml/ui/Dialogs/WorkflowPickerDialog.tsx +0 -437
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/AttributeEditor.tsx +0 -214
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ConditionEditor.tsx +0 -533
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FetchOptionsEditor.tsx +0 -296
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FilterEditor.tsx +0 -106
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/LinkEntityEditor.tsx +0 -433
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/OrderEditor.tsx +0 -161
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ValueOfPicker.tsx +0 -194
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel.tsx +0 -225
- package/src/features/fetchxml/ui/LeftPane/TreeView.tsx +0 -604
- package/src/features/fetchxml/ui/RightPane/AddColumnsPanel.tsx +0 -800
- package/src/features/fetchxml/ui/RightPane/ColumnConfigDialog.tsx +0 -170
- package/src/features/fetchxml/ui/RightPane/DataGridCellRenderers.tsx +0 -356
- package/src/features/fetchxml/ui/RightPane/EditColumnsPanel.tsx +0 -372
- package/src/features/fetchxml/ui/RightPane/FetchXmlEditor.tsx +0 -399
- package/src/features/fetchxml/ui/RightPane/FormattedValueUtils.ts +0 -189
- package/src/features/fetchxml/ui/RightPane/LayoutXmlViewer.tsx +0 -171
- package/src/features/fetchxml/ui/RightPane/PreviewTabs.tsx +0 -471
- package/src/features/fetchxml/ui/RightPane/ResultsCommandBar.tsx +0 -430
- package/src/features/fetchxml/ui/RightPane/ResultsGrid.tsx +0 -983
- package/src/features/fetchxml/ui/RightPane/XmlTextArea.tsx +0 -65
- package/src/features/fetchxml/ui/Settings/SettingsDrawer.tsx +0 -175
- package/src/features/fetchxml/ui/Toolbar/EntitySelector.tsx +0 -788
- package/src/features/fetchxml/ui/Toolbar/LoadViewPicker.tsx +0 -274
- package/src/features/fetchxml/ui/Toolbar/SaveViewButton.tsx +0 -174
- package/src/main.tsx +0 -11
- package/src/shared/components/AttributePicker.tsx +0 -138
- package/src/shared/components/BetweenInputPicker.tsx +0 -120
- package/src/shared/components/BooleanValuePicker.tsx +0 -165
- package/src/shared/components/DateTimeInputPicker.tsx +0 -170
- package/src/shared/components/DynamicValueList.tsx +0 -128
- package/src/shared/components/ExcelIcon.tsx +0 -30
- package/src/shared/components/MultiValuePicker.tsx +0 -178
- package/src/shared/components/NumericInputPicker.tsx +0 -156
- package/src/shared/components/OptionSetValuePicker.tsx +0 -150
- package/src/shared/components/RelationshipPicker.tsx +0 -268
- package/src/shared/contexts/ThemeContext.tsx +0 -19
- package/src/shared/hooks/useAccessMode.ts +0 -77
- package/src/shared/hooks/useLazyMetadata.ts +0 -132
- package/src/shared/hooks/usePptbContext.ts +0 -170
- package/src/shared/hooks/usePublisherFilter.ts +0 -214
- package/src/shared/hooks/useSolutionFilter.ts +0 -162
- package/src/shared/utils/debug.ts +0 -87
- package/tsconfig.app.json +0 -28
- package/tsconfig.json +0 -7
- package/tsconfig.node.json +0 -26
- package/vite.config.ts +0 -7
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SolutionPicker - Dropdown for selecting an unmanaged solution
|
|
3
|
-
* Used when saving system views to allow adding them to a specific solution
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useMemo } from "react";
|
|
7
|
-
import { Combobox, Option, Field, makeStyles, tokens, Text } from "@fluentui/react-components";
|
|
8
|
-
import type { Solution } from "../../api/pptbClient";
|
|
9
|
-
|
|
10
|
-
const useStyles = makeStyles({
|
|
11
|
-
container: {
|
|
12
|
-
display: "flex",
|
|
13
|
-
flexDirection: "column",
|
|
14
|
-
gap: "4px",
|
|
15
|
-
},
|
|
16
|
-
combobox: {
|
|
17
|
-
width: "100%",
|
|
18
|
-
},
|
|
19
|
-
hint: {
|
|
20
|
-
fontSize: "12px",
|
|
21
|
-
color: tokens.colorNeutralForeground3,
|
|
22
|
-
},
|
|
23
|
-
solutionOption: {
|
|
24
|
-
display: "flex",
|
|
25
|
-
flexDirection: "column",
|
|
26
|
-
gap: "2px",
|
|
27
|
-
},
|
|
28
|
-
solutionName: {
|
|
29
|
-
fontWeight: 500,
|
|
30
|
-
},
|
|
31
|
-
solutionUniqueName: {
|
|
32
|
-
fontSize: "11px",
|
|
33
|
-
color: tokens.colorNeutralForeground3,
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
interface SolutionPickerProps {
|
|
38
|
-
/** Available unmanaged solutions */
|
|
39
|
-
solutions: Solution[];
|
|
40
|
-
/** Currently selected solution */
|
|
41
|
-
selectedSolution: Solution | null;
|
|
42
|
-
/** Selection change callback */
|
|
43
|
-
onSolutionChange: (solution: Solution | null) => void;
|
|
44
|
-
/** Disabled state */
|
|
45
|
-
disabled?: boolean;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function SolutionPicker({
|
|
49
|
-
solutions,
|
|
50
|
-
selectedSolution,
|
|
51
|
-
onSolutionChange,
|
|
52
|
-
disabled = false,
|
|
53
|
-
}: SolutionPickerProps) {
|
|
54
|
-
const styles = useStyles();
|
|
55
|
-
|
|
56
|
-
// Sort solutions by friendly name
|
|
57
|
-
const sortedSolutions = useMemo(() => {
|
|
58
|
-
return [...solutions].sort((a, b) =>
|
|
59
|
-
(a.friendlyname || a.uniquename).localeCompare(b.friendlyname || b.uniquename)
|
|
60
|
-
);
|
|
61
|
-
}, [solutions]);
|
|
62
|
-
|
|
63
|
-
// Handle selection change
|
|
64
|
-
const handleOptionSelect = (_: unknown, data: { optionValue?: string; optionText?: string }) => {
|
|
65
|
-
if (data.optionValue === "__none__") {
|
|
66
|
-
onSolutionChange(null);
|
|
67
|
-
} else {
|
|
68
|
-
const solution = solutions.find((s) => s.solutionid === data.optionValue);
|
|
69
|
-
onSolutionChange(solution || null);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<div className={styles.container}>
|
|
75
|
-
<Field label="Add to Solution" hint="Select a solution to include the view in">
|
|
76
|
-
<Combobox
|
|
77
|
-
className={styles.combobox}
|
|
78
|
-
placeholder="Select solution (optional)"
|
|
79
|
-
value={selectedSolution?.friendlyname || ""}
|
|
80
|
-
onOptionSelect={handleOptionSelect}
|
|
81
|
-
disabled={disabled}
|
|
82
|
-
>
|
|
83
|
-
<Option value="__none__" text="">
|
|
84
|
-
<span style={{ fontStyle: "italic", color: tokens.colorNeutralForeground3 }}>
|
|
85
|
-
None - don't add to any solution
|
|
86
|
-
</span>
|
|
87
|
-
</Option>
|
|
88
|
-
{sortedSolutions.map((solution) => (
|
|
89
|
-
<Option
|
|
90
|
-
key={solution.solutionid}
|
|
91
|
-
value={solution.solutionid}
|
|
92
|
-
text={solution.friendlyname || solution.uniquename}
|
|
93
|
-
>
|
|
94
|
-
<div className={styles.solutionOption}>
|
|
95
|
-
<span className={styles.solutionName}>
|
|
96
|
-
{solution.friendlyname || solution.uniquename}
|
|
97
|
-
</span>
|
|
98
|
-
<span className={styles.solutionUniqueName}>
|
|
99
|
-
{solution.uniquename}
|
|
100
|
-
{solution.version && ` (v${solution.version})`}
|
|
101
|
-
</span>
|
|
102
|
-
</div>
|
|
103
|
-
</Option>
|
|
104
|
-
))}
|
|
105
|
-
</Combobox>
|
|
106
|
-
</Field>
|
|
107
|
-
{solutions.length === 0 && (
|
|
108
|
-
<Text className={styles.hint}>
|
|
109
|
-
No unmanaged solutions available. The view will be created in the Default solution.
|
|
110
|
-
</Text>
|
|
111
|
-
)}
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
@@ -1,437 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workflow Picker Dialog
|
|
3
|
-
* Allows user to select and run an on-demand workflow on selected records
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useCallback, useState, useEffect } from "react";
|
|
7
|
-
import {
|
|
8
|
-
Dialog,
|
|
9
|
-
DialogSurface,
|
|
10
|
-
DialogTitle,
|
|
11
|
-
DialogBody,
|
|
12
|
-
DialogContent,
|
|
13
|
-
DialogActions,
|
|
14
|
-
Button,
|
|
15
|
-
Text,
|
|
16
|
-
Spinner,
|
|
17
|
-
Radio,
|
|
18
|
-
RadioGroup,
|
|
19
|
-
ProgressBar,
|
|
20
|
-
makeStyles,
|
|
21
|
-
tokens,
|
|
22
|
-
MessageBar,
|
|
23
|
-
MessageBarBody,
|
|
24
|
-
MessageBarTitle,
|
|
25
|
-
} from "@fluentui/react-components";
|
|
26
|
-
import { Play20Regular, CheckmarkCircle20Regular, Warning20Regular } from "@fluentui/react-icons";
|
|
27
|
-
import type { WorkflowInfo, WorkflowBatchProgress } from "../../api/pptbClient";
|
|
28
|
-
|
|
29
|
-
const useStyles = makeStyles({
|
|
30
|
-
content: {
|
|
31
|
-
display: "flex",
|
|
32
|
-
flexDirection: "column",
|
|
33
|
-
gap: tokens.spacingVerticalM,
|
|
34
|
-
},
|
|
35
|
-
workflowList: {
|
|
36
|
-
maxHeight: "300px",
|
|
37
|
-
overflowY: "auto",
|
|
38
|
-
border: `1px solid ${tokens.colorNeutralStroke1}`,
|
|
39
|
-
borderRadius: tokens.borderRadiusMedium,
|
|
40
|
-
padding: tokens.spacingVerticalS,
|
|
41
|
-
},
|
|
42
|
-
workflowOption: {
|
|
43
|
-
display: "flex",
|
|
44
|
-
flexDirection: "column",
|
|
45
|
-
padding: tokens.spacingVerticalXS,
|
|
46
|
-
},
|
|
47
|
-
workflowDescription: {
|
|
48
|
-
marginLeft: "28px", // Align with radio label
|
|
49
|
-
color: tokens.colorNeutralForeground3,
|
|
50
|
-
fontSize: tokens.fontSizeBase200,
|
|
51
|
-
},
|
|
52
|
-
progressSection: {
|
|
53
|
-
display: "flex",
|
|
54
|
-
flexDirection: "column",
|
|
55
|
-
gap: tokens.spacingVerticalS,
|
|
56
|
-
},
|
|
57
|
-
progressStats: {
|
|
58
|
-
display: "flex",
|
|
59
|
-
justifyContent: "space-between",
|
|
60
|
-
alignItems: "center",
|
|
61
|
-
},
|
|
62
|
-
eta: {
|
|
63
|
-
color: tokens.colorNeutralForeground3,
|
|
64
|
-
fontSize: tokens.fontSizeBase200,
|
|
65
|
-
},
|
|
66
|
-
resultSection: {
|
|
67
|
-
display: "flex",
|
|
68
|
-
flexDirection: "column",
|
|
69
|
-
gap: tokens.spacingVerticalS,
|
|
70
|
-
},
|
|
71
|
-
successRow: {
|
|
72
|
-
display: "flex",
|
|
73
|
-
alignItems: "center",
|
|
74
|
-
gap: tokens.spacingHorizontalS,
|
|
75
|
-
color: tokens.colorPaletteGreenForeground1,
|
|
76
|
-
},
|
|
77
|
-
errorRow: {
|
|
78
|
-
display: "flex",
|
|
79
|
-
alignItems: "center",
|
|
80
|
-
gap: tokens.spacingHorizontalS,
|
|
81
|
-
color: tokens.colorPaletteRedForeground1,
|
|
82
|
-
},
|
|
83
|
-
emptyState: {
|
|
84
|
-
display: "flex",
|
|
85
|
-
flexDirection: "column",
|
|
86
|
-
alignItems: "center",
|
|
87
|
-
justifyContent: "center",
|
|
88
|
-
padding: tokens.spacingVerticalXXL,
|
|
89
|
-
color: tokens.colorNeutralForeground3,
|
|
90
|
-
},
|
|
91
|
-
loadingState: {
|
|
92
|
-
display: "flex",
|
|
93
|
-
alignItems: "center",
|
|
94
|
-
justifyContent: "center",
|
|
95
|
-
padding: tokens.spacingVerticalXL,
|
|
96
|
-
gap: tokens.spacingHorizontalS,
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
export interface WorkflowPickerDialogProps {
|
|
101
|
-
/** Whether the dialog is open */
|
|
102
|
-
open: boolean;
|
|
103
|
-
/** Number of records selected */
|
|
104
|
-
recordCount: number;
|
|
105
|
-
/** Entity display name */
|
|
106
|
-
entityDisplayName?: string;
|
|
107
|
-
/** Pre-selected workflow (from menu selection) */
|
|
108
|
-
preSelectedWorkflow?: WorkflowInfo;
|
|
109
|
-
/** Called when dialog is closed */
|
|
110
|
-
onClose: () => void;
|
|
111
|
-
/** Called to fetch available workflows */
|
|
112
|
-
onFetchWorkflows: () => Promise<WorkflowInfo[]>;
|
|
113
|
-
/** Called when workflow execution is confirmed */
|
|
114
|
-
onExecute: (
|
|
115
|
-
workflow: WorkflowInfo,
|
|
116
|
-
onProgress: (progress: WorkflowBatchProgress) => void
|
|
117
|
-
) => Promise<{ succeeded: number; failed: number; errors: string[] }>;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
type DialogState = "loading" | "select" | "executing" | "complete" | "error";
|
|
121
|
-
|
|
122
|
-
export function WorkflowPickerDialog({
|
|
123
|
-
open,
|
|
124
|
-
recordCount,
|
|
125
|
-
entityDisplayName,
|
|
126
|
-
preSelectedWorkflow,
|
|
127
|
-
onClose,
|
|
128
|
-
onFetchWorkflows,
|
|
129
|
-
onExecute,
|
|
130
|
-
}: WorkflowPickerDialogProps) {
|
|
131
|
-
const styles = useStyles();
|
|
132
|
-
const [state, setState] = useState<DialogState>("loading");
|
|
133
|
-
const [workflows, setWorkflows] = useState<WorkflowInfo[]>([]);
|
|
134
|
-
const [selectedWorkflowId, setSelectedWorkflowId] = useState<string | null>(null);
|
|
135
|
-
const [error, setError] = useState<string | null>(null);
|
|
136
|
-
const [progress, setProgress] = useState<WorkflowBatchProgress | null>(null);
|
|
137
|
-
const [result, setResult] = useState<{
|
|
138
|
-
succeeded: number;
|
|
139
|
-
failed: number;
|
|
140
|
-
errors: string[];
|
|
141
|
-
} | null>(null);
|
|
142
|
-
|
|
143
|
-
// Load workflows when dialog opens
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
if (open) {
|
|
146
|
-
setState("loading");
|
|
147
|
-
setSelectedWorkflowId(null);
|
|
148
|
-
setError(null);
|
|
149
|
-
setResult(null);
|
|
150
|
-
setProgress(null);
|
|
151
|
-
|
|
152
|
-
onFetchWorkflows()
|
|
153
|
-
.then((wfs) => {
|
|
154
|
-
setWorkflows(wfs);
|
|
155
|
-
setState("select");
|
|
156
|
-
// Auto-select: preSelectedWorkflow takes priority, then single workflow
|
|
157
|
-
if (preSelectedWorkflow) {
|
|
158
|
-
// Verify the pre-selected workflow exists in the fetched list
|
|
159
|
-
const found = wfs.find((w) => w.workflowid === preSelectedWorkflow.workflowid);
|
|
160
|
-
if (found) {
|
|
161
|
-
setSelectedWorkflowId(found.workflowid);
|
|
162
|
-
}
|
|
163
|
-
} else if (wfs.length === 1) {
|
|
164
|
-
setSelectedWorkflowId(wfs[0].workflowid);
|
|
165
|
-
}
|
|
166
|
-
})
|
|
167
|
-
.catch((err) => {
|
|
168
|
-
setError(err instanceof Error ? err.message : "Failed to load workflows");
|
|
169
|
-
setState("error");
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}, [open, onFetchWorkflows, preSelectedWorkflow]);
|
|
173
|
-
|
|
174
|
-
const handleExecute = useCallback(async () => {
|
|
175
|
-
const workflow = workflows.find((w) => w.workflowid === selectedWorkflowId);
|
|
176
|
-
if (!workflow) return;
|
|
177
|
-
|
|
178
|
-
setState("executing");
|
|
179
|
-
setProgress(null);
|
|
180
|
-
setError(null);
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
const res = await onExecute(workflow, (progressUpdate) => {
|
|
184
|
-
// Progress now comes from executeWorkflowBatch with batch info and ETA
|
|
185
|
-
setProgress(progressUpdate);
|
|
186
|
-
});
|
|
187
|
-
setResult(res);
|
|
188
|
-
setState("complete");
|
|
189
|
-
} catch (err) {
|
|
190
|
-
setError(err instanceof Error ? err.message : "Failed to execute workflow");
|
|
191
|
-
setState("error");
|
|
192
|
-
}
|
|
193
|
-
}, [workflows, selectedWorkflowId, onExecute]);
|
|
194
|
-
|
|
195
|
-
const handleClose = useCallback(() => {
|
|
196
|
-
if (state !== "executing") {
|
|
197
|
-
onClose();
|
|
198
|
-
}
|
|
199
|
-
}, [state, onClose]);
|
|
200
|
-
|
|
201
|
-
const selectedWorkflow = workflows.find((w) => w.workflowid === selectedWorkflowId);
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Intelligently format ETA based on duration:
|
|
205
|
-
* - < 1 minute: show seconds
|
|
206
|
-
* - < 1 hour: show minutes and seconds
|
|
207
|
-
* - < 12 hours: show hours and minutes
|
|
208
|
-
* - >= 12 hours: show estimated completion date/time
|
|
209
|
-
*/
|
|
210
|
-
const formatEta = (seconds: number): string => {
|
|
211
|
-
if (seconds < 60) {
|
|
212
|
-
return `~${seconds}s remaining`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const minutes = Math.floor(seconds / 60);
|
|
216
|
-
const hours = Math.floor(minutes / 60);
|
|
217
|
-
|
|
218
|
-
if (minutes < 60) {
|
|
219
|
-
const secs = seconds % 60;
|
|
220
|
-
return `~${minutes}m ${secs}s remaining`;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (hours < 12) {
|
|
224
|
-
const mins = minutes % 60;
|
|
225
|
-
return `~${hours}h ${mins}m remaining`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// >= 12 hours: show estimated completion date/time
|
|
229
|
-
const completionDate = new Date(Date.now() + seconds * 1000);
|
|
230
|
-
const isToday = completionDate.toDateString() === new Date().toDateString();
|
|
231
|
-
const isTomorrow =
|
|
232
|
-
completionDate.toDateString() === new Date(Date.now() + 86400000).toDateString();
|
|
233
|
-
|
|
234
|
-
const timeStr = completionDate.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
235
|
-
|
|
236
|
-
if (isToday) {
|
|
237
|
-
return `Est. completion: today at ${timeStr}`;
|
|
238
|
-
} else if (isTomorrow) {
|
|
239
|
-
return `Est. completion: tomorrow at ${timeStr}`;
|
|
240
|
-
} else {
|
|
241
|
-
const dateStr = completionDate.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
242
|
-
return `Est. completion: ${dateStr} at ${timeStr}`;
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
const renderContent = () => {
|
|
247
|
-
switch (state) {
|
|
248
|
-
case "loading":
|
|
249
|
-
return (
|
|
250
|
-
<div className={styles.loadingState}>
|
|
251
|
-
<Spinner size="small" />
|
|
252
|
-
<Text>Loading available workflows...</Text>
|
|
253
|
-
</div>
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
case "select":
|
|
257
|
-
if (workflows.length === 0) {
|
|
258
|
-
return (
|
|
259
|
-
<div className={styles.emptyState}>
|
|
260
|
-
<Text weight="semibold">No workflows available</Text>
|
|
261
|
-
<Text size={200}>
|
|
262
|
-
There are no on-demand workflows configured for {entityDisplayName || "this entity"}
|
|
263
|
-
.
|
|
264
|
-
</Text>
|
|
265
|
-
</div>
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return (
|
|
270
|
-
<>
|
|
271
|
-
<Text>
|
|
272
|
-
Select a workflow to run on {recordCount} {recordCount === 1 ? "record" : "records"}:
|
|
273
|
-
</Text>
|
|
274
|
-
|
|
275
|
-
<div className={styles.workflowList}>
|
|
276
|
-
<RadioGroup
|
|
277
|
-
value={selectedWorkflowId || ""}
|
|
278
|
-
onChange={(_, data) => setSelectedWorkflowId(data.value)}
|
|
279
|
-
>
|
|
280
|
-
{workflows.map((wf) => (
|
|
281
|
-
<div key={wf.workflowid} className={styles.workflowOption}>
|
|
282
|
-
<Radio value={wf.workflowid} label={wf.name} />
|
|
283
|
-
{wf.description && (
|
|
284
|
-
<Text className={styles.workflowDescription}>{wf.description}</Text>
|
|
285
|
-
)}
|
|
286
|
-
</div>
|
|
287
|
-
))}
|
|
288
|
-
</RadioGroup>
|
|
289
|
-
</div>
|
|
290
|
-
</>
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
case "executing":
|
|
294
|
-
return (
|
|
295
|
-
<div className={styles.progressSection}>
|
|
296
|
-
<MessageBar intent="info">
|
|
297
|
-
<MessageBarBody>
|
|
298
|
-
<MessageBarTitle>Running workflow...</MessageBarTitle>
|
|
299
|
-
Executing <strong>{selectedWorkflow?.name}</strong> on {recordCount} records
|
|
300
|
-
{progress && ` (batch ${progress.batchesCompleted} of ${progress.totalBatches})`}
|
|
301
|
-
</MessageBarBody>
|
|
302
|
-
</MessageBar>
|
|
303
|
-
{progress ? (
|
|
304
|
-
<>
|
|
305
|
-
<ProgressBar value={progress.completed} max={progress.total} thickness="large" />
|
|
306
|
-
<div className={styles.progressStats}>
|
|
307
|
-
<Text size={200}>
|
|
308
|
-
{progress.completed} of {progress.total} completed ({progress.succeeded}{" "}
|
|
309
|
-
succeeded, {progress.failed} failed)
|
|
310
|
-
</Text>
|
|
311
|
-
{progress.estimatedSecondsRemaining !== undefined &&
|
|
312
|
-
progress.estimatedSecondsRemaining > 0 && (
|
|
313
|
-
<Text className={styles.eta}>
|
|
314
|
-
{formatEta(progress.estimatedSecondsRemaining)}
|
|
315
|
-
</Text>
|
|
316
|
-
)}
|
|
317
|
-
</div>
|
|
318
|
-
</>
|
|
319
|
-
) : (
|
|
320
|
-
<div
|
|
321
|
-
style={{ display: "flex", alignItems: "center", gap: tokens.spacingHorizontalS }}
|
|
322
|
-
>
|
|
323
|
-
<Spinner size="small" />
|
|
324
|
-
<Text>Starting workflow execution...</Text>
|
|
325
|
-
</div>
|
|
326
|
-
)}
|
|
327
|
-
</div>
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
case "complete":
|
|
331
|
-
return (
|
|
332
|
-
<div className={styles.resultSection}>
|
|
333
|
-
{result && result.succeeded > 0 && (
|
|
334
|
-
<div className={styles.successRow}>
|
|
335
|
-
<CheckmarkCircle20Regular />
|
|
336
|
-
<Text weight="semibold">
|
|
337
|
-
{result.succeeded} record{result.succeeded !== 1 ? "s" : ""} processed
|
|
338
|
-
successfully
|
|
339
|
-
</Text>
|
|
340
|
-
</div>
|
|
341
|
-
)}
|
|
342
|
-
|
|
343
|
-
{result && result.failed > 0 && (
|
|
344
|
-
<>
|
|
345
|
-
<div className={styles.errorRow}>
|
|
346
|
-
<Warning20Regular />
|
|
347
|
-
<Text weight="semibold">
|
|
348
|
-
{result.failed} record{result.failed !== 1 ? "s" : ""} failed
|
|
349
|
-
</Text>
|
|
350
|
-
</div>
|
|
351
|
-
{result.errors.length > 0 && (
|
|
352
|
-
<MessageBar intent="error">
|
|
353
|
-
<MessageBarBody>
|
|
354
|
-
<MessageBarTitle>Errors:</MessageBarTitle>
|
|
355
|
-
{result.errors.slice(0, 5).map((err, i) => (
|
|
356
|
-
<Text key={i} size={200} block>
|
|
357
|
-
{err}
|
|
358
|
-
</Text>
|
|
359
|
-
))}
|
|
360
|
-
{result.errors.length > 5 && (
|
|
361
|
-
<Text size={200}>...and {result.errors.length - 5} more</Text>
|
|
362
|
-
)}
|
|
363
|
-
</MessageBarBody>
|
|
364
|
-
</MessageBar>
|
|
365
|
-
)}
|
|
366
|
-
</>
|
|
367
|
-
)}
|
|
368
|
-
</div>
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
case "error":
|
|
372
|
-
return (
|
|
373
|
-
<MessageBar intent="error">
|
|
374
|
-
<MessageBarBody>
|
|
375
|
-
<MessageBarTitle>Error</MessageBarTitle>
|
|
376
|
-
{error}
|
|
377
|
-
</MessageBarBody>
|
|
378
|
-
</MessageBar>
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
const renderActions = () => {
|
|
384
|
-
switch (state) {
|
|
385
|
-
case "loading":
|
|
386
|
-
return (
|
|
387
|
-
<Button appearance="secondary" onClick={handleClose}>
|
|
388
|
-
Cancel
|
|
389
|
-
</Button>
|
|
390
|
-
);
|
|
391
|
-
|
|
392
|
-
case "select":
|
|
393
|
-
return (
|
|
394
|
-
<>
|
|
395
|
-
<Button appearance="secondary" onClick={handleClose}>
|
|
396
|
-
Cancel
|
|
397
|
-
</Button>
|
|
398
|
-
<Button
|
|
399
|
-
appearance="primary"
|
|
400
|
-
icon={<Play20Regular />}
|
|
401
|
-
onClick={handleExecute}
|
|
402
|
-
disabled={!selectedWorkflowId || workflows.length === 0}
|
|
403
|
-
>
|
|
404
|
-
Run Workflow
|
|
405
|
-
</Button>
|
|
406
|
-
</>
|
|
407
|
-
);
|
|
408
|
-
|
|
409
|
-
case "executing":
|
|
410
|
-
return (
|
|
411
|
-
<Button appearance="secondary" disabled>
|
|
412
|
-
Running...
|
|
413
|
-
</Button>
|
|
414
|
-
);
|
|
415
|
-
|
|
416
|
-
case "complete":
|
|
417
|
-
case "error":
|
|
418
|
-
return (
|
|
419
|
-
<Button appearance="primary" onClick={handleClose}>
|
|
420
|
-
Close
|
|
421
|
-
</Button>
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
|
|
426
|
-
return (
|
|
427
|
-
<Dialog open={open} onOpenChange={(_, data) => !data.open && handleClose()}>
|
|
428
|
-
<DialogSurface>
|
|
429
|
-
<DialogTitle>Run Workflow</DialogTitle>
|
|
430
|
-
<DialogBody>
|
|
431
|
-
<DialogContent className={styles.content}>{renderContent()}</DialogContent>
|
|
432
|
-
</DialogBody>
|
|
433
|
-
<DialogActions>{renderActions()}</DialogActions>
|
|
434
|
-
</DialogSurface>
|
|
435
|
-
</Dialog>
|
|
436
|
-
);
|
|
437
|
-
}
|