@solidxai/core-ui 0.1.12-beta.3 → 0.1.12-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/auth/AuthLayout.d.ts.map +1 -1
- package/dist/components/auth/AuthLayout.js +12 -5
- package/dist/components/auth/AuthLayout.js.map +1 -1
- package/dist/components/auth/AuthLayout.tsx +15 -4
- package/dist/components/auth/AuthSettingsContext.js +1 -1
- package/dist/components/auth/AuthSettingsContext.js.map +1 -1
- package/dist/components/auth/AuthSettingsContext.tsx +1 -1
- package/dist/components/core/chatter/SolidChatterHeader.d.ts.map +1 -1
- package/dist/components/core/chatter/SolidChatterHeader.js +18 -4
- package/dist/components/core/chatter/SolidChatterHeader.js.map +1 -1
- package/dist/components/core/chatter/SolidChatterHeader.tsx +17 -3
- package/dist/components/core/common/SolidGenericImport/SolidImportTransaction.d.ts.map +1 -1
- package/dist/components/core/common/SolidGenericImport/SolidImportTransaction.js +12 -2
- package/dist/components/core/common/SolidGenericImport/SolidImportTransaction.js.map +1 -1
- package/dist/components/core/common/SolidGenericImport/SolidImportTransaction.tsx +12 -1
- package/dist/components/core/extension/solid-core/metadata/MetadataExplorerFormWidget.d.ts +2 -0
- package/dist/components/core/extension/solid-core/metadata/MetadataExplorerFormWidget.d.ts.map +1 -1
- package/dist/components/core/extension/solid-core/metadata/MetadataExplorerFormWidget.js +4 -2
- package/dist/components/core/extension/solid-core/metadata/MetadataExplorerFormWidget.js.map +1 -1
- package/dist/components/core/extension/solid-core/metadata/MetadataExplorerFormWidget.tsx +11 -3
- package/dist/components/core/form/SolidFormView.js +1 -1
- package/dist/components/core/form/SolidFormView.js.map +1 -1
- package/dist/components/core/form/SolidFormView.tsx +1 -1
- package/dist/components/core/form/fields/relations/RolesGroupedByModuleWidget.d.ts.map +1 -1
- package/dist/components/core/form/fields/relations/RolesGroupedByModuleWidget.js +11 -1
- package/dist/components/core/form/fields/relations/RolesGroupedByModuleWidget.js.map +1 -1
- package/dist/components/core/form/fields/relations/RolesGroupedByModuleWidget.tsx +26 -19
- package/dist/components/core/form/fields/relations/SolidRelationManyToOneField.d.ts.map +1 -1
- package/dist/components/core/form/fields/relations/SolidRelationManyToOneField.js +13 -5
- package/dist/components/core/form/fields/relations/SolidRelationManyToOneField.js.map +1 -1
- package/dist/components/core/form/fields/relations/SolidRelationManyToOneField.tsx +16 -9
- package/dist/components/core/json/SolidJsonEditor.css +7 -0
- package/dist/components/core/list/SolidDataTable.d.ts.map +1 -1
- package/dist/components/core/list/SolidDataTable.js +2 -2
- package/dist/components/core/list/SolidDataTable.js.map +1 -1
- package/dist/components/core/list/SolidDataTable.tsx +2 -1
- package/dist/components/core/list/SolidListView.d.ts.map +1 -1
- package/dist/components/core/list/SolidListView.js +58 -58
- package/dist/components/core/list/SolidListView.js.map +1 -1
- package/dist/components/core/list/SolidListView.tsx +5 -4
- package/dist/components/core/model/FieldMetaDataForm.d.ts +1 -1
- package/dist/components/core/model/FieldMetaDataForm.d.ts.map +1 -1
- package/dist/components/core/model/FieldMetaDataForm.js +119 -102
- package/dist/components/core/model/FieldMetaDataForm.js.map +1 -1
- package/dist/components/core/model/FieldMetaDataForm.tsx +82 -39
- package/dist/components/core/model/FieldSelector.d.ts +5 -1
- package/dist/components/core/model/FieldSelector.d.ts.map +1 -1
- package/dist/components/core/model/FieldSelector.js +25 -6
- package/dist/components/core/model/FieldSelector.js.map +1 -1
- package/dist/components/core/model/FieldSelector.tsx +50 -5
- package/dist/components/core/module/CreateModule.css +60 -6
- package/dist/components/core/module/CreateModule.d.ts.map +1 -1
- package/dist/components/core/module/CreateModule.js +2 -2
- package/dist/components/core/module/CreateModule.js.map +1 -1
- package/dist/components/core/module/CreateModule.tsx +56 -37
- package/dist/components/layout/AdminTopHeader.js +1 -1
- package/dist/components/layout/AdminTopHeader.js.map +1 -1
- package/dist/components/layout/AdminTopHeader.tsx +2 -2
- package/dist/components/shad-cn-ui/SolidSelect.d.ts +2 -1
- package/dist/components/shad-cn-ui/SolidSelect.d.ts.map +1 -1
- package/dist/components/shad-cn-ui/SolidSelect.js +12 -4
- package/dist/components/shad-cn-ui/SolidSelect.js.map +1 -1
- package/dist/components/shad-cn-ui/SolidSelect.tsx +13 -1
- package/dist/helpers/modelViewPersistence.d.ts.map +1 -1
- package/dist/helpers/modelViewPersistence.js +3 -2
- package/dist/helpers/modelViewPersistence.js.map +1 -1
- package/dist/helpers/modelViewPersistence.ts +3 -2
- package/dist/redux/api/datasourceIntrospectionApi.d.ts +43 -1
- package/dist/redux/api/datasourceIntrospectionApi.d.ts.map +1 -1
- package/dist/redux/api/datasourceIntrospectionApi.js +34 -1
- package/dist/redux/api/datasourceIntrospectionApi.js.map +1 -1
- package/dist/redux/api/datasourceIntrospectionApi.ts +61 -0
- package/dist/redux/store/defaultStoreConfig.d.ts +11 -0
- package/dist/redux/store/defaultStoreConfig.d.ts.map +1 -1
- package/dist/resources/solid-custom.css +3 -86
- package/dist/resources/solid-tailwind-generated.css +1 -1
- package/dist/resources/themes/solid-dark-purple/theme-variables.css +226 -0
- package/dist/resources/themes/solid-dark-purple/theme.css +1 -185
- package/dist/resources/themes/solid-light-purple/theme-variables.css +237 -0
- package/dist/resources/themes/solid-light-purple/theme.css +1 -206
- package/dist/routes/pages/admin/core/DatasourceIntrospectionPage.css +396 -31
- package/dist/routes/pages/admin/core/DatasourceIntrospectionPage.d.ts.map +1 -1
- package/dist/routes/pages/admin/core/DatasourceIntrospectionPage.js +680 -152
- package/dist/routes/pages/admin/core/DatasourceIntrospectionPage.js.map +1 -1
- package/dist/routes/pages/admin/core/DatasourceIntrospectionPage.tsx +1358 -441
- package/package.json +1 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ArrowLeft,
|
|
3
|
+
Check,
|
|
2
4
|
Cog,
|
|
3
5
|
Database,
|
|
4
6
|
ExternalLink,
|
|
@@ -11,10 +13,12 @@ import {
|
|
|
11
13
|
import { camelCase, startCase } from "lodash";
|
|
12
14
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
13
15
|
import { useDispatch } from "react-redux";
|
|
14
|
-
import { useParams } from "react-router-dom";
|
|
16
|
+
import { useNavigate, useParams } from "react-router-dom";
|
|
15
17
|
import { showToast } from "../../../../redux/features/toastSlice";
|
|
16
18
|
import { SolidJsonEditor } from "../../../../components/core/json/SolidJsonEditor";
|
|
17
19
|
import { Column, SolidDataTable } from "../../../../components/core/list/SolidDataTable";
|
|
20
|
+
import FieldMetaDataForm from "../../../../components/core/model/FieldMetaDataForm";
|
|
21
|
+
import { waitForBackendAvailability } from "../../../../helpers/waitForBackendAvailability";
|
|
18
22
|
import {
|
|
19
23
|
SolidButton,
|
|
20
24
|
SolidCheckbox,
|
|
@@ -38,11 +42,16 @@ import {
|
|
|
38
42
|
type DatasourceIntrospectionMappingPreview,
|
|
39
43
|
type DatasourceIntrospectionMappingRequest,
|
|
40
44
|
type DatasourceIntrospectionTableRecord,
|
|
45
|
+
type DatasourceIntrospectionApplyResponse,
|
|
46
|
+
type DatasourceIntrospectionCreateMigrationResponse,
|
|
41
47
|
useApplyDatasourceIntrospectionMappingMutation,
|
|
48
|
+
useCreateDatasourceIntrospectionMigrationArtifactsMutation,
|
|
49
|
+
useGenerateDatasourceIntrospectionCodeMutation,
|
|
42
50
|
useGetDatasourceIntrospectionBootstrapQuery,
|
|
43
51
|
useGetDatasourceIntrospectionTableDetailQuery,
|
|
44
52
|
useGetDatasourceIntrospectionTablesQuery,
|
|
45
53
|
usePreviewDatasourceIntrospectionMappingMutation,
|
|
54
|
+
useRunDatasourceIntrospectionMigrationMutation,
|
|
46
55
|
} from "../../../../redux/api/datasourceIntrospectionApi";
|
|
47
56
|
import { useGetmodelByIdQuery } from "../../../../redux/api/modelApi";
|
|
48
57
|
import "./DatasourceIntrospectionPage.css";
|
|
@@ -52,9 +61,17 @@ type SessionSettings = {
|
|
|
52
61
|
prioritizeUnmappedTables: boolean;
|
|
53
62
|
};
|
|
54
63
|
|
|
64
|
+
type WorkspaceStep =
|
|
65
|
+
| "configure"
|
|
66
|
+
| "review-json"
|
|
67
|
+
| "generate-code"
|
|
68
|
+
| "create-migrations"
|
|
69
|
+
| "run-migration";
|
|
70
|
+
|
|
55
71
|
type DrawerColumnDraft = {
|
|
56
72
|
columnName: string;
|
|
57
73
|
fieldName: string;
|
|
74
|
+
displayName: string;
|
|
58
75
|
dataType: string;
|
|
59
76
|
solidFieldType: string;
|
|
60
77
|
ormType: string;
|
|
@@ -65,12 +82,14 @@ type DrawerColumnDraft = {
|
|
|
65
82
|
numericScale: number | null;
|
|
66
83
|
ordinalPosition: number | null;
|
|
67
84
|
isAutoGenerated: boolean;
|
|
85
|
+
isDetectedPrimaryKey: boolean;
|
|
68
86
|
handledBySuperclass: boolean;
|
|
69
87
|
superclassFieldName: string | null;
|
|
70
88
|
fieldId: number | null;
|
|
71
89
|
include: boolean;
|
|
72
90
|
matched: boolean;
|
|
73
91
|
isPrimaryKey: boolean;
|
|
92
|
+
fieldConfig: Record<string, any> | null;
|
|
74
93
|
};
|
|
75
94
|
|
|
76
95
|
type DrawerDraft = {
|
|
@@ -121,6 +140,49 @@ const scalarFieldTypeOptions = [
|
|
|
121
140
|
];
|
|
122
141
|
|
|
123
142
|
const scalarFieldTypeValueSet = new Set(scalarFieldTypeOptions.map((option) => option.value));
|
|
143
|
+
const mappingStrategyOptions = [
|
|
144
|
+
{ value: "generated_id", label: "Legacy generated id" },
|
|
145
|
+
{ value: "existing_id", label: "Legacy existing id" },
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
function getBaseClassNameForLegacyType(value: "none" | "existing_id" | "generated_id") {
|
|
149
|
+
return value === "existing_id"
|
|
150
|
+
? "LegacyCommonEntityWithExistingId"
|
|
151
|
+
: "LegacyCommonEntityWithGeneratedId";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getSuperclassFieldNameForLegacyType(columnName: string, legacyTableType: "none" | "existing_id" | "generated_id") {
|
|
155
|
+
const normalized = columnName.toLowerCase();
|
|
156
|
+
const legacyFieldMap: Record<string, string> = {
|
|
157
|
+
ss_created_at: "createdAt",
|
|
158
|
+
ss_updated_at: "updatedAt",
|
|
159
|
+
ss_deleted_at: "deletedAt",
|
|
160
|
+
ss_deleted_tracker: "deletedTracker",
|
|
161
|
+
ss_published_at: "publishedAt",
|
|
162
|
+
ss_locale_name: "localeName",
|
|
163
|
+
ss_default_entity_locale_id: "defaultEntityLocaleId",
|
|
164
|
+
ss_created_by_id: "createdBy",
|
|
165
|
+
ss_updated_by_id: "updatedBy",
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
if (normalized === "ss_id" && legacyTableType === "generated_id") {
|
|
169
|
+
return "id";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return legacyFieldMap[normalized] ?? null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getSuggestedFieldNameForLegacyType(column: Pick<DrawerColumnDraft, "columnName" | "isAutoGenerated">, legacyTableType: "none" | "existing_id" | "generated_id") {
|
|
176
|
+
if (legacyTableType === "existing_id" && column.isAutoGenerated) {
|
|
177
|
+
return "id";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (legacyTableType === "generated_id" && column.columnName.toLowerCase() === "id") {
|
|
181
|
+
return "legacyId";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return camelCase(column.columnName.replace(/^ss_/i, ""));
|
|
185
|
+
}
|
|
124
186
|
|
|
125
187
|
function getDefaultOrmTypeForSolidFieldType(dataSourceType: string, solidFieldType: string) {
|
|
126
188
|
const normalizedDatasourceType = dataSourceType.toLowerCase();
|
|
@@ -179,12 +241,6 @@ function getDefaultOrmTypeForSolidFieldType(dataSourceType: string, solidFieldTy
|
|
|
179
241
|
return ormTypesByDatasource[normalizedDatasourceType]?.[solidFieldType] ?? "varchar";
|
|
180
242
|
}
|
|
181
243
|
|
|
182
|
-
function legacyTypeLabel(value: string) {
|
|
183
|
-
if (value === "generated_id") return "Legacy generated id";
|
|
184
|
-
if (value === "existing_id") return "Legacy existing id";
|
|
185
|
-
return "Legacy generated id";
|
|
186
|
-
}
|
|
187
|
-
|
|
188
244
|
function statusLabel(record: DatasourceIntrospectionTableRecord) {
|
|
189
245
|
return record.mapped ? "Mapped" : "Ready to map";
|
|
190
246
|
}
|
|
@@ -198,6 +254,30 @@ function readQueryError(error: any) {
|
|
|
198
254
|
);
|
|
199
255
|
}
|
|
200
256
|
|
|
257
|
+
function isRecoverableGenerateCodeError(error: any) {
|
|
258
|
+
const status = error?.status ?? error?.originalStatus;
|
|
259
|
+
const message = [
|
|
260
|
+
error?.error,
|
|
261
|
+
error?.message,
|
|
262
|
+
error?.data?.message,
|
|
263
|
+
error?.data?.error,
|
|
264
|
+
]
|
|
265
|
+
.filter(Boolean)
|
|
266
|
+
.join(" ")
|
|
267
|
+
.toLowerCase();
|
|
268
|
+
|
|
269
|
+
if (status === "FETCH_ERROR" || status === "TIMEOUT_ERROR" || status === "PARSING_ERROR") {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
message.includes("failed to fetch")
|
|
275
|
+
|| message.includes("network")
|
|
276
|
+
|| message.includes("load failed")
|
|
277
|
+
|| message.includes("fetch")
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
201
281
|
function normalizeName(value: string) {
|
|
202
282
|
return value.trim().toLowerCase();
|
|
203
283
|
}
|
|
@@ -249,6 +329,7 @@ function buildDrawerDraft(
|
|
|
249
329
|
return {
|
|
250
330
|
columnName: column.columnName,
|
|
251
331
|
fieldName: matchingField?.name ?? column.fieldName,
|
|
332
|
+
displayName: matchingField?.displayName ?? column.displayName,
|
|
252
333
|
dataType: column.dataType,
|
|
253
334
|
solidFieldType: matchingField?.type ?? column.solidFieldType,
|
|
254
335
|
ormType: matchingField?.ormType ?? column.ormType,
|
|
@@ -259,12 +340,14 @@ function buildDrawerDraft(
|
|
|
259
340
|
numericScale: column.numericScale,
|
|
260
341
|
ordinalPosition: column.ordinalPosition,
|
|
261
342
|
isAutoGenerated: column.isAutoGenerated,
|
|
343
|
+
isDetectedPrimaryKey: column.isPrimaryKey,
|
|
262
344
|
handledBySuperclass: column.handledBySuperclass,
|
|
263
345
|
superclassFieldName: column.superclassFieldName,
|
|
264
346
|
fieldId: matchingField?.id ?? null,
|
|
265
347
|
include: column.handledBySuperclass ? false : Boolean(matchingField) || !selectedTable.mapped,
|
|
266
348
|
matched: Boolean(matchingField),
|
|
267
|
-
isPrimaryKey: Boolean(matchingField?.isPrimaryKey) || (column.isAutoGenerated && column.fieldName === "id"),
|
|
349
|
+
isPrimaryKey: Boolean(matchingField?.isPrimaryKey) || column.isPrimaryKey || (column.isAutoGenerated && column.fieldName === "id"),
|
|
350
|
+
fieldConfig: matchingField ? sanitizeExistingFieldPayload(matchingField) : null,
|
|
268
351
|
};
|
|
269
352
|
});
|
|
270
353
|
|
|
@@ -319,6 +402,11 @@ function getDrawerValidationErrors(draft: DrawerDraft) {
|
|
|
319
402
|
continue;
|
|
320
403
|
}
|
|
321
404
|
|
|
405
|
+
if (!column.displayName.trim()) {
|
|
406
|
+
errors.push(`Field label is required for column "${column.columnName}".`);
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
|
|
322
410
|
if (!scalarFieldTypeValueSet.has(column.solidFieldType)) {
|
|
323
411
|
errors.push(`SolidX type "${column.solidFieldType}" is not allowed in step 1 for column "${column.columnName}".`);
|
|
324
412
|
continue;
|
|
@@ -377,6 +465,7 @@ function buildMappingRequest(
|
|
|
377
465
|
columns: draft.columns.map((column) => ({
|
|
378
466
|
columnName: column.columnName,
|
|
379
467
|
fieldName: column.fieldName,
|
|
468
|
+
displayName: column.displayName,
|
|
380
469
|
dataType: column.dataType,
|
|
381
470
|
solidFieldType: column.solidFieldType,
|
|
382
471
|
ormType: column.ormType,
|
|
@@ -393,6 +482,7 @@ function buildMappingRequest(
|
|
|
393
482
|
include: column.include,
|
|
394
483
|
matched: column.matched,
|
|
395
484
|
isPrimaryKey: column.isPrimaryKey,
|
|
485
|
+
fieldConfig: column.fieldConfig,
|
|
396
486
|
})),
|
|
397
487
|
};
|
|
398
488
|
}
|
|
@@ -541,6 +631,7 @@ function parseAndValidateReviewedModelJson(
|
|
|
541
631
|
|
|
542
632
|
export function DatasourceIntrospectionPage() {
|
|
543
633
|
const params = useParams();
|
|
634
|
+
const navigate = useNavigate();
|
|
544
635
|
const dispatch = useDispatch();
|
|
545
636
|
const moduleId = Number(params.moduleId ?? 0);
|
|
546
637
|
const isValidModuleId = Number.isFinite(moduleId) && moduleId > 0;
|
|
@@ -556,21 +647,31 @@ export function DatasourceIntrospectionPage() {
|
|
|
556
647
|
});
|
|
557
648
|
|
|
558
649
|
const [selectedDatasource, setSelectedDatasource] = useState("");
|
|
559
|
-
const [
|
|
650
|
+
const [selectedRows, setSelectedRows] = useState<DatasourceIntrospectionTableRecord[]>([]);
|
|
651
|
+
const [workspaceTableKey, setWorkspaceTableKey] = useState("");
|
|
560
652
|
const [tableSearch, setTableSearch] = useState("");
|
|
561
653
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
562
654
|
const [settingsTab, setSettingsTab] = useState("datasource-info");
|
|
563
|
-
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
564
655
|
const [drawerDraft, setDrawerDraft] = useState<DrawerDraft | null>(null);
|
|
565
656
|
const [drawerTab, setDrawerTab] = useState("general-info");
|
|
566
|
-
const [
|
|
567
|
-
const [reviewTab, setReviewTab] = useState("metadata-json");
|
|
657
|
+
const [workspaceStep, setWorkspaceStep] = useState<WorkspaceStep>("configure");
|
|
568
658
|
const [mappingPreview, setMappingPreview] = useState<DatasourceIntrospectionMappingPreview | null>(null);
|
|
659
|
+
const [persistedMappingResult, setPersistedMappingResult] = useState<DatasourceIntrospectionApplyResponse | null>(null);
|
|
660
|
+
const [migrationArtifactsResult, setMigrationArtifactsResult] = useState<DatasourceIntrospectionCreateMigrationResponse | null>(null);
|
|
661
|
+
const [generateCodeOutput, setGenerateCodeOutput] = useState("");
|
|
662
|
+
const [isGeneratingCodeWorkflow, setIsGeneratingCodeWorkflow] = useState(false);
|
|
663
|
+
const [generateCodeStatusMessage, setGenerateCodeStatusMessage] = useState("");
|
|
664
|
+
const [generateCodeTimeoutMessage, setGenerateCodeTimeoutMessage] = useState<string | null>(null);
|
|
665
|
+
const [generateCodeReady, setGenerateCodeReady] = useState(false);
|
|
666
|
+
const [hasAttemptedGenerateCode, setHasAttemptedGenerateCode] = useState(false);
|
|
667
|
+
const [runMigrationOutput, setRunMigrationOutput] = useState("");
|
|
668
|
+
const [runMigrationCompleted, setRunMigrationCompleted] = useState(false);
|
|
569
669
|
const [reviewedMetadataJsonValue, setReviewedMetadataJsonValue] = useState<any>(null);
|
|
570
670
|
const [metadataJsonText, setMetadataJsonText] = useState("");
|
|
571
671
|
const [metadataJsonValidationError, setMetadataJsonValidationError] = useState<string | null>(null);
|
|
572
672
|
const [metadataJsonResetToken, setMetadataJsonResetToken] = useState("initial");
|
|
573
673
|
const [drawerBootstrapping, setDrawerBootstrapping] = useState(false);
|
|
674
|
+
const [fieldConfigColumnName, setFieldConfigColumnName] = useState<string | null>(null);
|
|
574
675
|
const [sessionSettings, setSessionSettings] = useState<SessionSettings>({
|
|
575
676
|
relationInferenceEnabled: false,
|
|
576
677
|
prioritizeUnmappedTables: true,
|
|
@@ -578,15 +679,6 @@ export function DatasourceIntrospectionPage() {
|
|
|
578
679
|
const [draftSessionSettings, setDraftSessionSettings] = useState<SessionSettings>(sessionSettings);
|
|
579
680
|
const drawerBootstrapTimerRef = useRef<number | null>(null);
|
|
580
681
|
|
|
581
|
-
const scrollDrawerBodyToTop = () => {
|
|
582
|
-
if (typeof document === "undefined") return;
|
|
583
|
-
|
|
584
|
-
window.requestAnimationFrame(() => {
|
|
585
|
-
const drawerBody = document.querySelector<HTMLDivElement>(".sdix-drawer-body");
|
|
586
|
-
drawerBody?.scrollTo({ top: 0, left: 0 });
|
|
587
|
-
});
|
|
588
|
-
};
|
|
589
|
-
|
|
590
682
|
const [
|
|
591
683
|
previewMapping,
|
|
592
684
|
{ isLoading: isPreviewMappingLoading },
|
|
@@ -596,8 +688,32 @@ export function DatasourceIntrospectionPage() {
|
|
|
596
688
|
applyMapping,
|
|
597
689
|
{ isLoading: isApplyMappingLoading },
|
|
598
690
|
] = useApplyDatasourceIntrospectionMappingMutation();
|
|
691
|
+
const [
|
|
692
|
+
createMigrationArtifacts,
|
|
693
|
+
{ isLoading: isCreateMigrationArtifactsLoading },
|
|
694
|
+
] = useCreateDatasourceIntrospectionMigrationArtifactsMutation();
|
|
695
|
+
const [
|
|
696
|
+
generateCode,
|
|
697
|
+
{ isLoading: isGenerateCodeLoading },
|
|
698
|
+
] = useGenerateDatasourceIntrospectionCodeMutation();
|
|
699
|
+
const [
|
|
700
|
+
runMigration,
|
|
701
|
+
{ isLoading: isRunMigrationLoading },
|
|
702
|
+
] = useRunDatasourceIntrospectionMigrationMutation();
|
|
599
703
|
|
|
600
704
|
const isSavingMapping = isApplyMappingLoading;
|
|
705
|
+
const isCreatingMigrationArtifacts = isCreateMigrationArtifactsLoading;
|
|
706
|
+
|
|
707
|
+
const showFieldDraftToaster = (message: any, severity: any) => {
|
|
708
|
+
const errorMessages = Object.values(message ?? {});
|
|
709
|
+
if (!errorMessages.length) return;
|
|
710
|
+
dispatch(showToast({
|
|
711
|
+
severity,
|
|
712
|
+
summary: "Datasource introspection",
|
|
713
|
+
detail: errorMessages.map(String).join(", "),
|
|
714
|
+
life: 3000,
|
|
715
|
+
}));
|
|
716
|
+
};
|
|
601
717
|
|
|
602
718
|
useEffect(() => {
|
|
603
719
|
if (!bootstrap) return;
|
|
@@ -608,13 +724,23 @@ export function DatasourceIntrospectionPage() {
|
|
|
608
724
|
}, [bootstrap, selectedDatasource]);
|
|
609
725
|
|
|
610
726
|
useEffect(() => {
|
|
611
|
-
|
|
727
|
+
setSelectedRows([]);
|
|
728
|
+
setWorkspaceTableKey("");
|
|
612
729
|
setDrawerDraft(null);
|
|
613
|
-
|
|
730
|
+
setFieldConfigColumnName(null);
|
|
614
731
|
setTableSearch("");
|
|
615
|
-
|
|
616
|
-
setReviewTab("metadata-json");
|
|
732
|
+
setWorkspaceStep("configure");
|
|
617
733
|
setMappingPreview(null);
|
|
734
|
+
setPersistedMappingResult(null);
|
|
735
|
+
setMigrationArtifactsResult(null);
|
|
736
|
+
setGenerateCodeOutput("");
|
|
737
|
+
setIsGeneratingCodeWorkflow(false);
|
|
738
|
+
setGenerateCodeStatusMessage("");
|
|
739
|
+
setGenerateCodeTimeoutMessage(null);
|
|
740
|
+
setGenerateCodeReady(false);
|
|
741
|
+
setHasAttemptedGenerateCode(false);
|
|
742
|
+
setRunMigrationOutput("");
|
|
743
|
+
setRunMigrationCompleted(false);
|
|
618
744
|
setReviewedMetadataJsonValue(null);
|
|
619
745
|
setMetadataJsonText("");
|
|
620
746
|
setMetadataJsonValidationError(null);
|
|
@@ -627,11 +753,6 @@ export function DatasourceIntrospectionPage() {
|
|
|
627
753
|
}
|
|
628
754
|
}, []);
|
|
629
755
|
|
|
630
|
-
useEffect(() => {
|
|
631
|
-
if (!drawerOpen) return;
|
|
632
|
-
scrollDrawerBodyToTop();
|
|
633
|
-
}, [drawerOpen, drawerStep, drawerTab, reviewTab, selectedTableKey]);
|
|
634
|
-
|
|
635
756
|
const datasourceOptions = useMemo(
|
|
636
757
|
() => (bootstrap?.datasources ?? []).map((datasource) => ({
|
|
637
758
|
label: `${datasource.displayName} (${providerLabel(datasource.type)})`,
|
|
@@ -653,9 +774,13 @@ export function DatasourceIntrospectionPage() {
|
|
|
653
774
|
},
|
|
654
775
|
);
|
|
655
776
|
|
|
777
|
+
const selectedListRow = selectedRows[0] ?? null;
|
|
778
|
+
const selectedListRowKey = selectedListRow ? `${selectedListRow.schema ?? ""}::${selectedListRow.tableName}` : "";
|
|
779
|
+
const isWorkspaceMode = Boolean(workspaceTableKey);
|
|
780
|
+
|
|
656
781
|
const selectedTable = useMemo(
|
|
657
|
-
() => tablesResponse?.records?.find((record) => `${record.schema ?? ""}::${record.tableName}` ===
|
|
658
|
-
[tablesResponse,
|
|
782
|
+
() => tablesResponse?.records?.find((record) => `${record.schema ?? ""}::${record.tableName}` === workspaceTableKey) ?? null,
|
|
783
|
+
[tablesResponse, workspaceTableKey],
|
|
659
784
|
);
|
|
660
785
|
|
|
661
786
|
const {
|
|
@@ -672,7 +797,7 @@ export function DatasourceIntrospectionPage() {
|
|
|
672
797
|
schema: selectedTable?.schema ?? undefined,
|
|
673
798
|
},
|
|
674
799
|
{
|
|
675
|
-
skip: !
|
|
800
|
+
skip: !isWorkspaceMode || !isValidModuleId || !selectedDatasource || !selectedTable || !!tablesResponse?.synchronizeBlocked,
|
|
676
801
|
},
|
|
677
802
|
);
|
|
678
803
|
|
|
@@ -682,11 +807,63 @@ export function DatasourceIntrospectionPage() {
|
|
|
682
807
|
isFetching: isMappedModelFetching,
|
|
683
808
|
error: mappedModelError,
|
|
684
809
|
} = useGetmodelByIdQuery(selectedTable?.mappedModelId ?? 0, {
|
|
685
|
-
skip: !
|
|
810
|
+
skip: !isWorkspaceMode || !selectedTable?.mapped || !selectedTable?.mappedModelId,
|
|
686
811
|
});
|
|
687
812
|
|
|
688
813
|
const mappedModelData = mappedModelResponse?.data;
|
|
689
814
|
const selectedDatasourceRecord = bootstrap?.datasources?.find((datasource) => datasource.name === selectedDatasource) ?? null;
|
|
815
|
+
const activeFieldConfigColumn = drawerDraft?.columns.find((column) => column.columnName === fieldConfigColumnName) ?? null;
|
|
816
|
+
const activeFieldConfigMetaData = useMemo(() => {
|
|
817
|
+
if (!drawerDraft || !activeFieldConfigColumn) {
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const configured = activeFieldConfigColumn.fieldConfig ?? {};
|
|
822
|
+
const suggestedMax =
|
|
823
|
+
activeFieldConfigColumn.characterMaximumLength && ["shortText", "longText"].includes(activeFieldConfigColumn.solidFieldType)
|
|
824
|
+
? activeFieldConfigColumn.characterMaximumLength
|
|
825
|
+
: configured.max;
|
|
826
|
+
|
|
827
|
+
return {
|
|
828
|
+
...configured,
|
|
829
|
+
...(activeFieldConfigColumn.fieldId ? { id: activeFieldConfigColumn.fieldId } : {}),
|
|
830
|
+
name: activeFieldConfigColumn.fieldName,
|
|
831
|
+
displayName: activeFieldConfigColumn.displayName,
|
|
832
|
+
description: configured.description ?? activeFieldConfigColumn.displayName,
|
|
833
|
+
type: activeFieldConfigColumn.solidFieldType,
|
|
834
|
+
ormType: activeFieldConfigColumn.ormType,
|
|
835
|
+
columnName: activeFieldConfigColumn.columnName,
|
|
836
|
+
defaultValue: configured.defaultValue ?? activeFieldConfigColumn.defaultValue ?? undefined,
|
|
837
|
+
required: configured.required ?? (!activeFieldConfigColumn.nullable || activeFieldConfigColumn.isPrimaryKey),
|
|
838
|
+
unique: configured.unique ?? false,
|
|
839
|
+
encrypt: configured.encrypt ?? false,
|
|
840
|
+
index: configured.index ?? false,
|
|
841
|
+
max: suggestedMax ?? undefined,
|
|
842
|
+
min: configured.min ?? undefined,
|
|
843
|
+
private: configured.private ?? false,
|
|
844
|
+
isSystem: false,
|
|
845
|
+
isPrimaryKey: activeFieldConfigColumn.isPrimaryKey,
|
|
846
|
+
isUserKey: drawerDraft.userKeyField === activeFieldConfigColumn.fieldName,
|
|
847
|
+
enableAuditTracking: configured.enableAuditTracking ?? drawerDraft.enableAuditTracking,
|
|
848
|
+
isMultiSelect: configured.isMultiSelect ?? false,
|
|
849
|
+
length: configured.length ?? activeFieldConfigColumn.characterMaximumLength ?? undefined,
|
|
850
|
+
};
|
|
851
|
+
}, [activeFieldConfigColumn, drawerDraft]);
|
|
852
|
+
const fieldDraftAllFields = useMemo(() => {
|
|
853
|
+
if (!drawerDraft || !activeFieldConfigColumn) {
|
|
854
|
+
return [];
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return drawerDraft.columns
|
|
858
|
+
.filter((column) => column.columnName !== activeFieldConfigColumn.columnName)
|
|
859
|
+
.map((column) => ({
|
|
860
|
+
id: column.fieldId ?? undefined,
|
|
861
|
+
name: column.fieldName,
|
|
862
|
+
displayName: column.displayName,
|
|
863
|
+
type: column.solidFieldType,
|
|
864
|
+
columnName: column.columnName,
|
|
865
|
+
}));
|
|
866
|
+
}, [activeFieldConfigColumn, drawerDraft]);
|
|
690
867
|
const drawerLoadingError = selectedTable?.mapped ? (tableDetailError || mappedModelError) : tableDetailError;
|
|
691
868
|
const isDrawerBlockingLoading = Boolean(
|
|
692
869
|
selectedTable
|
|
@@ -700,7 +877,45 @@ export function DatasourceIntrospectionPage() {
|
|
|
700
877
|
);
|
|
701
878
|
|
|
702
879
|
useEffect(() => {
|
|
703
|
-
if (!
|
|
880
|
+
if (!isWorkspaceMode) {
|
|
881
|
+
setDrawerDraft(null);
|
|
882
|
+
setFieldConfigColumnName(null);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (drawerBootstrapTimerRef.current) {
|
|
887
|
+
window.clearTimeout(drawerBootstrapTimerRef.current);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
setDrawerDraft(null);
|
|
891
|
+
setFieldConfigColumnName(null);
|
|
892
|
+
setDrawerTab("general-info");
|
|
893
|
+
setWorkspaceStep("configure");
|
|
894
|
+
setMappingPreview(null);
|
|
895
|
+
setPersistedMappingResult(null);
|
|
896
|
+
setMigrationArtifactsResult(null);
|
|
897
|
+
setGenerateCodeOutput("");
|
|
898
|
+
setIsGeneratingCodeWorkflow(false);
|
|
899
|
+
setGenerateCodeStatusMessage("");
|
|
900
|
+
setGenerateCodeTimeoutMessage(null);
|
|
901
|
+
setGenerateCodeReady(false);
|
|
902
|
+
setHasAttemptedGenerateCode(false);
|
|
903
|
+
setRunMigrationOutput("");
|
|
904
|
+
setRunMigrationCompleted(false);
|
|
905
|
+
setReviewedMetadataJsonValue(null);
|
|
906
|
+
setMetadataJsonText("");
|
|
907
|
+
setMetadataJsonValidationError(null);
|
|
908
|
+
setMetadataJsonResetToken(`table-${workspaceTableKey}-${Date.now()}`);
|
|
909
|
+
setDrawerBootstrapping(true);
|
|
910
|
+
}, [isWorkspaceMode, workspaceTableKey]);
|
|
911
|
+
|
|
912
|
+
useEffect(() => {
|
|
913
|
+
if (!isWorkspaceMode || !selectedTable) return;
|
|
914
|
+
setSelectedRows([selectedTable]);
|
|
915
|
+
}, [isWorkspaceMode, selectedTable]);
|
|
916
|
+
|
|
917
|
+
useEffect(() => {
|
|
918
|
+
if (!isWorkspaceMode || !selectedTable || !tableDetail) return;
|
|
704
919
|
if (selectedTable.mapped && (!mappedModelData || mappedModelData.id !== selectedTable.mappedModelId)) return;
|
|
705
920
|
|
|
706
921
|
if (drawerBootstrapTimerRef.current) {
|
|
@@ -715,7 +930,7 @@ export function DatasourceIntrospectionPage() {
|
|
|
715
930
|
drawerBootstrapTimerRef.current = window.setTimeout(() => {
|
|
716
931
|
setDrawerBootstrapping(false);
|
|
717
932
|
}, 150);
|
|
718
|
-
}, [
|
|
933
|
+
}, [isWorkspaceMode, selectedTable, tableDetail, mappedModelData, mappedModelResponse]);
|
|
719
934
|
|
|
720
935
|
const filteredRecords = useMemo(() => {
|
|
721
936
|
const records = [...(tablesResponse?.records ?? [])];
|
|
@@ -723,7 +938,7 @@ export function DatasourceIntrospectionPage() {
|
|
|
723
938
|
|
|
724
939
|
const filtered = searchNeedle
|
|
725
940
|
? records.filter((record) => {
|
|
726
|
-
const haystack = `${record.tableName} ${record.schema ?? ""} ${record.mappedModelSingularName ?? ""} ${record.mappedModelDisplayName ?? ""}`.toLowerCase();
|
|
941
|
+
const haystack = `${record.tableName} ${record.schema ?? ""} ${record.mappedModelSingularName ?? ""} ${record.mappedModelDisplayName ?? ""} ${record.mappedModuleName ?? ""} ${record.mappedModuleDisplayName ?? ""}`.toLowerCase();
|
|
727
942
|
return haystack.includes(searchNeedle);
|
|
728
943
|
})
|
|
729
944
|
: records;
|
|
@@ -848,28 +1063,46 @@ export function DatasourceIntrospectionPage() {
|
|
|
848
1063
|
if (selectedDatasource) {
|
|
849
1064
|
await refetchTables();
|
|
850
1065
|
}
|
|
851
|
-
if (selectedTable) {
|
|
1066
|
+
if (isWorkspaceMode && selectedTable) {
|
|
852
1067
|
await refetchTableDetail();
|
|
853
1068
|
}
|
|
854
1069
|
};
|
|
855
1070
|
|
|
856
|
-
const
|
|
1071
|
+
const startMappingForRecord = (record: DatasourceIntrospectionTableRecord) => {
|
|
857
1072
|
if (drawerBootstrapTimerRef.current) {
|
|
858
1073
|
window.clearTimeout(drawerBootstrapTimerRef.current);
|
|
859
1074
|
}
|
|
860
1075
|
|
|
861
|
-
|
|
1076
|
+
setSelectedRows([record]);
|
|
1077
|
+
setWorkspaceTableKey(`${record.schema ?? ""}::${record.tableName}`);
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
const leaveWorkspace = () => {
|
|
1081
|
+
if (drawerBootstrapTimerRef.current) {
|
|
1082
|
+
window.clearTimeout(drawerBootstrapTimerRef.current);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
setWorkspaceTableKey("");
|
|
862
1086
|
setDrawerDraft(null);
|
|
1087
|
+
setFieldConfigColumnName(null);
|
|
1088
|
+
setWorkspaceStep("configure");
|
|
863
1089
|
setDrawerTab("general-info");
|
|
864
|
-
setDrawerStep("edit");
|
|
865
|
-
setReviewTab("metadata-json");
|
|
866
1090
|
setMappingPreview(null);
|
|
1091
|
+
setPersistedMappingResult(null);
|
|
1092
|
+
setMigrationArtifactsResult(null);
|
|
1093
|
+
setGenerateCodeOutput("");
|
|
1094
|
+
setIsGeneratingCodeWorkflow(false);
|
|
1095
|
+
setGenerateCodeStatusMessage("");
|
|
1096
|
+
setGenerateCodeTimeoutMessage(null);
|
|
1097
|
+
setGenerateCodeReady(false);
|
|
1098
|
+
setHasAttemptedGenerateCode(false);
|
|
1099
|
+
setRunMigrationOutput("");
|
|
1100
|
+
setRunMigrationCompleted(false);
|
|
867
1101
|
setReviewedMetadataJsonValue(null);
|
|
868
1102
|
setMetadataJsonText("");
|
|
869
1103
|
setMetadataJsonValidationError(null);
|
|
870
|
-
setMetadataJsonResetToken(`
|
|
871
|
-
setDrawerBootstrapping(
|
|
872
|
-
setDrawerOpen(true);
|
|
1104
|
+
setMetadataJsonResetToken(`workspace-closed-${Date.now()}`);
|
|
1105
|
+
setDrawerBootstrapping(false);
|
|
873
1106
|
};
|
|
874
1107
|
|
|
875
1108
|
const applyDraftSettings = () => {
|
|
@@ -879,6 +1112,15 @@ export function DatasourceIntrospectionPage() {
|
|
|
879
1112
|
|
|
880
1113
|
const updateDrawerDraft = (updater: (current: DrawerDraft) => DrawerDraft) => {
|
|
881
1114
|
setMappingPreview(null);
|
|
1115
|
+
setPersistedMappingResult(null);
|
|
1116
|
+
setMigrationArtifactsResult(null);
|
|
1117
|
+
setGenerateCodeOutput("");
|
|
1118
|
+
setIsGeneratingCodeWorkflow(false);
|
|
1119
|
+
setGenerateCodeStatusMessage("");
|
|
1120
|
+
setGenerateCodeTimeoutMessage(null);
|
|
1121
|
+
setGenerateCodeReady(false);
|
|
1122
|
+
setHasAttemptedGenerateCode(false);
|
|
1123
|
+
setRunMigrationOutput("");
|
|
882
1124
|
setDrawerDraft((current) => (current ? updater(current) : current));
|
|
883
1125
|
};
|
|
884
1126
|
|
|
@@ -891,7 +1133,28 @@ export function DatasourceIntrospectionPage() {
|
|
|
891
1133
|
...current,
|
|
892
1134
|
columns: current.columns.map((column) => {
|
|
893
1135
|
if (column.columnName !== columnName) return column;
|
|
894
|
-
|
|
1136
|
+
|
|
1137
|
+
const previousSuggestedLabel = startCase(column.fieldName);
|
|
1138
|
+
const nextNormalizedFieldName = camelCase(nextFieldName);
|
|
1139
|
+
const nextFieldConfig = column.fieldConfig
|
|
1140
|
+
? {
|
|
1141
|
+
...column.fieldConfig,
|
|
1142
|
+
name: nextNormalizedFieldName,
|
|
1143
|
+
displayName: !column.displayName.trim() || column.displayName === previousSuggestedLabel
|
|
1144
|
+
? startCase(nextNormalizedFieldName)
|
|
1145
|
+
: column.displayName,
|
|
1146
|
+
isUserKey: current.userKeyField === column.fieldName,
|
|
1147
|
+
isPrimaryKey: column.isPrimaryKey,
|
|
1148
|
+
}
|
|
1149
|
+
: column.fieldConfig;
|
|
1150
|
+
return {
|
|
1151
|
+
...column,
|
|
1152
|
+
fieldName: nextNormalizedFieldName,
|
|
1153
|
+
displayName: !column.displayName.trim() || column.displayName === previousSuggestedLabel
|
|
1154
|
+
? startCase(nextNormalizedFieldName)
|
|
1155
|
+
: column.displayName,
|
|
1156
|
+
fieldConfig: nextFieldConfig,
|
|
1157
|
+
};
|
|
895
1158
|
}),
|
|
896
1159
|
userKeyField: current.userKeyField === current.columns.find((column) => column.columnName === columnName)?.fieldName
|
|
897
1160
|
? camelCase(nextFieldName)
|
|
@@ -899,6 +1162,26 @@ export function DatasourceIntrospectionPage() {
|
|
|
899
1162
|
}));
|
|
900
1163
|
};
|
|
901
1164
|
|
|
1165
|
+
const updateColumnDisplayName = (columnName: string, nextDisplayName: string) => {
|
|
1166
|
+
updateDrawerDraft((current) => ({
|
|
1167
|
+
...current,
|
|
1168
|
+
columns: current.columns.map((column) => (
|
|
1169
|
+
column.columnName === columnName
|
|
1170
|
+
? {
|
|
1171
|
+
...column,
|
|
1172
|
+
displayName: nextDisplayName,
|
|
1173
|
+
fieldConfig: column.fieldConfig
|
|
1174
|
+
? {
|
|
1175
|
+
...column.fieldConfig,
|
|
1176
|
+
displayName: nextDisplayName,
|
|
1177
|
+
}
|
|
1178
|
+
: column.fieldConfig,
|
|
1179
|
+
}
|
|
1180
|
+
: column
|
|
1181
|
+
)),
|
|
1182
|
+
}));
|
|
1183
|
+
};
|
|
1184
|
+
|
|
902
1185
|
const updateColumnSolidFieldType = (columnName: string, nextSolidFieldType: string) => {
|
|
903
1186
|
updateDrawerDraft((current) => ({
|
|
904
1187
|
...current,
|
|
@@ -910,11 +1193,142 @@ export function DatasourceIntrospectionPage() {
|
|
|
910
1193
|
...column,
|
|
911
1194
|
solidFieldType: nextSolidFieldType,
|
|
912
1195
|
ormType: getDefaultOrmTypeForSolidFieldType(current.dataSourceType, nextSolidFieldType),
|
|
1196
|
+
fieldConfig: column.fieldConfig
|
|
1197
|
+
? {
|
|
1198
|
+
...column.fieldConfig,
|
|
1199
|
+
type: nextSolidFieldType,
|
|
1200
|
+
ormType: getDefaultOrmTypeForSolidFieldType(current.dataSourceType, nextSolidFieldType),
|
|
1201
|
+
}
|
|
1202
|
+
: column.fieldConfig,
|
|
913
1203
|
};
|
|
914
1204
|
}),
|
|
915
1205
|
}));
|
|
916
1206
|
};
|
|
917
1207
|
|
|
1208
|
+
const openFieldConfigEditor = (columnName: string) => {
|
|
1209
|
+
setFieldConfigColumnName(columnName);
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
const applyFieldConfigDraft = (columnName: string, nextFieldConfig: Record<string, any>) => {
|
|
1213
|
+
const sanitized = sanitizeExistingFieldPayload(nextFieldConfig);
|
|
1214
|
+
updateDrawerDraft((current) => {
|
|
1215
|
+
const currentColumn = current.columns.find((column) => column.columnName === columnName);
|
|
1216
|
+
if (!currentColumn) {
|
|
1217
|
+
return current;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const nextFieldName = typeof sanitized.name === "string" && sanitized.name.trim()
|
|
1221
|
+
? sanitized.name.trim()
|
|
1222
|
+
: currentColumn.fieldName;
|
|
1223
|
+
const nextDisplayName = typeof sanitized.displayName === "string" && sanitized.displayName.trim()
|
|
1224
|
+
? sanitized.displayName.trim()
|
|
1225
|
+
: currentColumn.displayName;
|
|
1226
|
+
const nextSolidFieldType = typeof sanitized.type === "string" && sanitized.type.trim()
|
|
1227
|
+
? sanitized.type.trim()
|
|
1228
|
+
: currentColumn.solidFieldType;
|
|
1229
|
+
const nextOrmType = typeof sanitized.ormType === "string" && sanitized.ormType.trim()
|
|
1230
|
+
? sanitized.ormType.trim()
|
|
1231
|
+
: getDefaultOrmTypeForSolidFieldType(current.dataSourceType, nextSolidFieldType);
|
|
1232
|
+
const nextUserKeyField = current.userKeyField === currentColumn.fieldName
|
|
1233
|
+
? nextFieldName
|
|
1234
|
+
: current.userKeyField;
|
|
1235
|
+
|
|
1236
|
+
return {
|
|
1237
|
+
...current,
|
|
1238
|
+
userKeyField: nextUserKeyField,
|
|
1239
|
+
columns: current.columns.map((column) => {
|
|
1240
|
+
if (column.columnName !== columnName) {
|
|
1241
|
+
return column.fieldConfig
|
|
1242
|
+
? {
|
|
1243
|
+
...column,
|
|
1244
|
+
fieldConfig: {
|
|
1245
|
+
...column.fieldConfig,
|
|
1246
|
+
isUserKey: column.fieldName === nextUserKeyField,
|
|
1247
|
+
},
|
|
1248
|
+
}
|
|
1249
|
+
: column;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
return {
|
|
1253
|
+
...column,
|
|
1254
|
+
fieldName: nextFieldName,
|
|
1255
|
+
displayName: nextDisplayName,
|
|
1256
|
+
solidFieldType: nextSolidFieldType,
|
|
1257
|
+
ormType: nextOrmType,
|
|
1258
|
+
fieldConfig: {
|
|
1259
|
+
...sanitized,
|
|
1260
|
+
name: nextFieldName,
|
|
1261
|
+
displayName: nextDisplayName,
|
|
1262
|
+
type: nextSolidFieldType,
|
|
1263
|
+
ormType: nextOrmType,
|
|
1264
|
+
columnName: column.columnName,
|
|
1265
|
+
isPrimaryKey: column.isPrimaryKey,
|
|
1266
|
+
isUserKey: nextUserKeyField === nextFieldName,
|
|
1267
|
+
},
|
|
1268
|
+
};
|
|
1269
|
+
}),
|
|
1270
|
+
};
|
|
1271
|
+
});
|
|
1272
|
+
setFieldConfigColumnName(null);
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
const updateLegacyTableType = (legacyTableType: "none" | "existing_id" | "generated_id") => {
|
|
1276
|
+
updateDrawerDraft((current) => {
|
|
1277
|
+
const renamedUserKeyColumns = new Map<string, string>();
|
|
1278
|
+
|
|
1279
|
+
const columns = current.columns.map((column) => {
|
|
1280
|
+
const nextSuperclassFieldName = getSuperclassFieldNameForLegacyType(column.columnName, legacyTableType);
|
|
1281
|
+
const nextHandledBySuperclass = Boolean(nextSuperclassFieldName);
|
|
1282
|
+
const nextSuggestedFieldName = getSuggestedFieldNameForLegacyType(column, legacyTableType);
|
|
1283
|
+
const shouldRebuildFieldName = column.handledBySuperclass !== nextHandledBySuperclass
|
|
1284
|
+
|| (legacyTableType === "existing_id" && column.isAutoGenerated && (column.fieldName === "id" || column.fieldName === "legacyId"))
|
|
1285
|
+
|| (legacyTableType === "generated_id" && column.columnName.toLowerCase() === "id");
|
|
1286
|
+
const nextFieldName = shouldRebuildFieldName ? nextSuggestedFieldName : column.fieldName;
|
|
1287
|
+
const nextDisplayName = !column.displayName.trim() || column.displayName === startCase(column.fieldName)
|
|
1288
|
+
? startCase(nextFieldName)
|
|
1289
|
+
: column.displayName;
|
|
1290
|
+
const nextInclude = nextHandledBySuperclass
|
|
1291
|
+
? false
|
|
1292
|
+
: (legacyTableType === "existing_id" && column.isAutoGenerated ? true : column.include);
|
|
1293
|
+
const nextIsPrimaryKey = (
|
|
1294
|
+
legacyTableType === "existing_id" && column.isAutoGenerated && nextFieldName === "id"
|
|
1295
|
+
) || (
|
|
1296
|
+
legacyTableType === "generated_id" && column.isDetectedPrimaryKey
|
|
1297
|
+
);
|
|
1298
|
+
|
|
1299
|
+
if (current.userKeyField === column.fieldName && current.userKeyField !== nextFieldName) {
|
|
1300
|
+
renamedUserKeyColumns.set(column.fieldName, nextFieldName);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
return {
|
|
1304
|
+
...column,
|
|
1305
|
+
fieldName: nextFieldName,
|
|
1306
|
+
displayName: nextDisplayName,
|
|
1307
|
+
handledBySuperclass: nextHandledBySuperclass,
|
|
1308
|
+
superclassFieldName: nextSuperclassFieldName,
|
|
1309
|
+
include: nextInclude,
|
|
1310
|
+
isPrimaryKey: nextIsPrimaryKey,
|
|
1311
|
+
fieldConfig: column.fieldConfig
|
|
1312
|
+
? {
|
|
1313
|
+
...column.fieldConfig,
|
|
1314
|
+
name: nextFieldName,
|
|
1315
|
+
displayName: nextDisplayName,
|
|
1316
|
+
isPrimaryKey: nextIsPrimaryKey,
|
|
1317
|
+
}
|
|
1318
|
+
: column.fieldConfig,
|
|
1319
|
+
};
|
|
1320
|
+
});
|
|
1321
|
+
|
|
1322
|
+
return {
|
|
1323
|
+
...current,
|
|
1324
|
+
legacyTableType,
|
|
1325
|
+
baseClassName: getBaseClassNameForLegacyType(legacyTableType),
|
|
1326
|
+
columns,
|
|
1327
|
+
userKeyField: renamedUserKeyColumns.get(current.userKeyField) ?? current.userKeyField,
|
|
1328
|
+
};
|
|
1329
|
+
});
|
|
1330
|
+
};
|
|
1331
|
+
|
|
918
1332
|
const setColumnIncluded = (columnName: string, include: boolean) => {
|
|
919
1333
|
updateDrawerDraft((current) => ({
|
|
920
1334
|
...current,
|
|
@@ -930,11 +1344,19 @@ export function DatasourceIntrospectionPage() {
|
|
|
930
1344
|
updateDrawerDraft((current) => ({
|
|
931
1345
|
...current,
|
|
932
1346
|
userKeyField: fieldName,
|
|
933
|
-
columns: current.columns.map((column) =>
|
|
934
|
-
column.fieldName === fieldName
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1347
|
+
columns: current.columns.map((column) => {
|
|
1348
|
+
const isUserKey = column.fieldName === fieldName;
|
|
1349
|
+
return {
|
|
1350
|
+
...column,
|
|
1351
|
+
include: isUserKey ? (column.handledBySuperclass ? false : true) : column.include,
|
|
1352
|
+
fieldConfig: column.fieldConfig
|
|
1353
|
+
? {
|
|
1354
|
+
...column.fieldConfig,
|
|
1355
|
+
isUserKey,
|
|
1356
|
+
}
|
|
1357
|
+
: column.fieldConfig,
|
|
1358
|
+
};
|
|
1359
|
+
}),
|
|
938
1360
|
}));
|
|
939
1361
|
};
|
|
940
1362
|
|
|
@@ -956,7 +1378,7 @@ export function DatasourceIntrospectionPage() {
|
|
|
956
1378
|
window.open(`/admin/core/solid-core/model-metadata/form/${targetModelId}`, "_blank", "noopener,noreferrer");
|
|
957
1379
|
};
|
|
958
1380
|
|
|
959
|
-
const
|
|
1381
|
+
const reviewMappingJson = async () => {
|
|
960
1382
|
if (!drawerDraft || !bootstrap?.module?.id) return;
|
|
961
1383
|
|
|
962
1384
|
const validationErrors = getDrawerValidationErrors(drawerDraft);
|
|
@@ -978,8 +1400,11 @@ export function DatasourceIntrospectionPage() {
|
|
|
978
1400
|
setMetadataJsonText(JSON.stringify(preview.metadataJson.model, null, 2));
|
|
979
1401
|
setMetadataJsonValidationError(null);
|
|
980
1402
|
setMetadataJsonResetToken(`preview-${preview.metadataJson.filePath}-${Date.now()}`);
|
|
981
|
-
|
|
982
|
-
|
|
1403
|
+
setPersistedMappingResult(null);
|
|
1404
|
+
setMigrationArtifactsResult(null);
|
|
1405
|
+
setGenerateCodeOutput("");
|
|
1406
|
+
setRunMigrationOutput("");
|
|
1407
|
+
setWorkspaceStep("review-json");
|
|
983
1408
|
} catch (error: any) {
|
|
984
1409
|
dispatch(showToast({
|
|
985
1410
|
severity: "error",
|
|
@@ -989,7 +1414,7 @@ export function DatasourceIntrospectionPage() {
|
|
|
989
1414
|
}
|
|
990
1415
|
};
|
|
991
1416
|
|
|
992
|
-
const
|
|
1417
|
+
const persistMappingArtifacts = async () => {
|
|
993
1418
|
if (!drawerDraft || !bootstrap?.module?.id) return;
|
|
994
1419
|
|
|
995
1420
|
const validationErrors = getDrawerValidationErrors(drawerDraft);
|
|
@@ -997,15 +1422,14 @@ export function DatasourceIntrospectionPage() {
|
|
|
997
1422
|
validationErrors.forEach((detail) => {
|
|
998
1423
|
dispatch(showToast({ severity: "error", summary: "Validation", detail }));
|
|
999
1424
|
});
|
|
1000
|
-
|
|
1425
|
+
setWorkspaceStep("configure");
|
|
1001
1426
|
return;
|
|
1002
1427
|
}
|
|
1003
1428
|
|
|
1004
1429
|
const reviewedModelValidation = parseAndValidateReviewedModelJson(metadataJsonText, drawerDraft);
|
|
1005
1430
|
if (reviewedModelValidation.error || !reviewedModelValidation.parsed) {
|
|
1006
1431
|
setMetadataJsonValidationError(reviewedModelValidation.error ?? "Metadata JSON is invalid.");
|
|
1007
|
-
|
|
1008
|
-
setDrawerStep("review");
|
|
1432
|
+
setWorkspaceStep("review-json");
|
|
1009
1433
|
dispatch(showToast({
|
|
1010
1434
|
severity: "error",
|
|
1011
1435
|
summary: "Validation",
|
|
@@ -1027,28 +1451,180 @@ export function DatasourceIntrospectionPage() {
|
|
|
1027
1451
|
|
|
1028
1452
|
dispatch(showToast({
|
|
1029
1453
|
severity: "success",
|
|
1030
|
-
summary: "
|
|
1454
|
+
summary: "Mapping saved",
|
|
1455
|
+
detail: drawerDraft.mapped
|
|
1456
|
+
? "The model mapping metadata was updated successfully."
|
|
1457
|
+
: "The model mapping metadata was created successfully.",
|
|
1458
|
+
}));
|
|
1459
|
+
|
|
1460
|
+
setPersistedMappingResult(response);
|
|
1461
|
+
setMigrationArtifactsResult(null);
|
|
1462
|
+
setGenerateCodeOutput("");
|
|
1463
|
+
setIsGeneratingCodeWorkflow(false);
|
|
1464
|
+
setGenerateCodeStatusMessage("");
|
|
1465
|
+
setGenerateCodeTimeoutMessage(null);
|
|
1466
|
+
setGenerateCodeReady(false);
|
|
1467
|
+
setHasAttemptedGenerateCode(false);
|
|
1468
|
+
setRunMigrationOutput("");
|
|
1469
|
+
setRunMigrationCompleted(false);
|
|
1470
|
+
setDrawerDraft((current) => (
|
|
1471
|
+
current
|
|
1472
|
+
? {
|
|
1473
|
+
...current,
|
|
1474
|
+
mapped: true,
|
|
1475
|
+
modelId: response.modelId,
|
|
1476
|
+
}
|
|
1477
|
+
: current
|
|
1478
|
+
));
|
|
1479
|
+
setWorkspaceStep("generate-code");
|
|
1480
|
+
} catch (error: any) {
|
|
1481
|
+
dispatch(showToast({
|
|
1482
|
+
severity: "error",
|
|
1483
|
+
summary: "Save failed",
|
|
1484
|
+
detail: readQueryError(error),
|
|
1485
|
+
}));
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
|
|
1489
|
+
const handleGenerateCode = async () => {
|
|
1490
|
+
if (!bootstrap?.module?.id) return;
|
|
1491
|
+
|
|
1492
|
+
setHasAttemptedGenerateCode(true);
|
|
1493
|
+
setRunMigrationCompleted(false);
|
|
1494
|
+
setIsGeneratingCodeWorkflow(true);
|
|
1495
|
+
setGenerateCodeStatusMessage("Submitting module generate-code request...");
|
|
1496
|
+
setGenerateCodeTimeoutMessage(null);
|
|
1497
|
+
setGenerateCodeReady(false);
|
|
1498
|
+
let responseMessage = "";
|
|
1499
|
+
|
|
1500
|
+
try {
|
|
1501
|
+
const response = await generateCode({ moduleId: bootstrap.module.id }).unwrap();
|
|
1502
|
+
responseMessage = response.output || response.message || "";
|
|
1503
|
+
setGenerateCodeOutput(responseMessage);
|
|
1504
|
+
setGenerateCodeStatusMessage("Waiting briefly for the backend restart window...");
|
|
1505
|
+
} catch (error: any) {
|
|
1506
|
+
if (!isRecoverableGenerateCodeError(error)) {
|
|
1507
|
+
setIsGeneratingCodeWorkflow(false);
|
|
1508
|
+
setGenerateCodeStatusMessage("");
|
|
1509
|
+
setGenerateCodeTimeoutMessage(null);
|
|
1510
|
+
setGenerateCodeReady(false);
|
|
1511
|
+
dispatch(showToast({
|
|
1512
|
+
severity: "error",
|
|
1513
|
+
summary: "Generate code failed",
|
|
1514
|
+
detail: readQueryError(error),
|
|
1515
|
+
}));
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
setGenerateCodeStatusMessage("The API connection dropped during restart. Waiting for the backend to come back online...");
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
await new Promise((resolve) => window.setTimeout(resolve, 5000));
|
|
1523
|
+
setGenerateCodeStatusMessage("Checking if the backend is back online...");
|
|
1524
|
+
|
|
1525
|
+
const isBackendAvailable = await waitForBackendAvailability({
|
|
1526
|
+
retries: 40,
|
|
1527
|
+
delayMs: 1500,
|
|
1528
|
+
});
|
|
1529
|
+
|
|
1530
|
+
setIsGeneratingCodeWorkflow(false);
|
|
1531
|
+
|
|
1532
|
+
if (!isBackendAvailable) {
|
|
1533
|
+
setGenerateCodeTimeoutMessage(
|
|
1534
|
+
"Code generation took longer than expected. The backend did not come back within 60 seconds, so there may be a compilation failure or another startup issue. Please review the generated code manually and check the API logs before continuing.",
|
|
1535
|
+
);
|
|
1536
|
+
dispatch(showToast({
|
|
1537
|
+
severity: "warn",
|
|
1538
|
+
summary: "Backend restart taking too long",
|
|
1539
|
+
detail: "The generate-code request completed, but the backend did not come back within 60 seconds.",
|
|
1540
|
+
}));
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
setGenerateCodeStatusMessage("Backend is back online. You can continue.");
|
|
1545
|
+
setGenerateCodeReady(true);
|
|
1546
|
+
setMigrationArtifactsResult(null);
|
|
1547
|
+
setRunMigrationOutput("");
|
|
1548
|
+
dispatch(showToast({
|
|
1549
|
+
severity: "success",
|
|
1550
|
+
summary: "Code generated",
|
|
1551
|
+
detail: responseMessage || "The backend is back online. You can continue to the migration artifacts step.",
|
|
1552
|
+
}));
|
|
1553
|
+
};
|
|
1554
|
+
|
|
1555
|
+
const handleCreateMigrationArtifacts = async () => {
|
|
1556
|
+
if (!drawerDraft || !bootstrap?.module?.id || !persistedMappingResult?.modelId) return;
|
|
1557
|
+
|
|
1558
|
+
const reviewedModelValidation = parseAndValidateReviewedModelJson(metadataJsonText, drawerDraft);
|
|
1559
|
+
if (reviewedModelValidation.error || !reviewedModelValidation.parsed) {
|
|
1560
|
+
setMetadataJsonValidationError(reviewedModelValidation.error ?? "Metadata JSON is invalid.");
|
|
1561
|
+
setWorkspaceStep("review-json");
|
|
1562
|
+
dispatch(showToast({
|
|
1563
|
+
severity: "error",
|
|
1564
|
+
summary: "Validation",
|
|
1565
|
+
detail: reviewedModelValidation.error ?? "Metadata JSON is invalid.",
|
|
1566
|
+
}));
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
try {
|
|
1571
|
+
const response = await createMigrationArtifacts({
|
|
1572
|
+
moduleId: bootstrap.module.id,
|
|
1573
|
+
payload: {
|
|
1574
|
+
...buildMappingRequest(
|
|
1575
|
+
{
|
|
1576
|
+
...drawerDraft,
|
|
1577
|
+
mapped: true,
|
|
1578
|
+
modelId: persistedMappingResult.modelId,
|
|
1579
|
+
},
|
|
1580
|
+
mappingPreview?.migration.timestamp ?? null,
|
|
1581
|
+
),
|
|
1582
|
+
reviewedModel: reviewedModelValidation.parsed,
|
|
1583
|
+
},
|
|
1584
|
+
}).unwrap();
|
|
1585
|
+
|
|
1586
|
+
setMigrationArtifactsResult(response);
|
|
1587
|
+
setWorkspaceStep("run-migration");
|
|
1588
|
+
dispatch(showToast({
|
|
1589
|
+
severity: "success",
|
|
1590
|
+
summary: "Migration artifacts created",
|
|
1031
1591
|
detail: response.migration.written
|
|
1032
|
-
? "The
|
|
1033
|
-
:
|
|
1034
|
-
|
|
1035
|
-
|
|
1592
|
+
? "The migration file and datasource registration were written successfully."
|
|
1593
|
+
: "No migration file was required, but the datasource registration was updated successfully.",
|
|
1594
|
+
}));
|
|
1595
|
+
} catch (error: any) {
|
|
1596
|
+
dispatch(showToast({
|
|
1597
|
+
severity: "error",
|
|
1598
|
+
summary: "Create migration artifacts failed",
|
|
1599
|
+
detail: readQueryError(error),
|
|
1600
|
+
}));
|
|
1601
|
+
}
|
|
1602
|
+
};
|
|
1603
|
+
|
|
1604
|
+
const handleRunMigration = async () => {
|
|
1605
|
+
if (!bootstrap?.module?.id || !selectedDatasource) return;
|
|
1606
|
+
|
|
1607
|
+
try {
|
|
1608
|
+
const response = await runMigration({
|
|
1609
|
+
moduleId: bootstrap.module.id,
|
|
1610
|
+
datasource: selectedDatasource,
|
|
1611
|
+
}).unwrap();
|
|
1612
|
+
|
|
1613
|
+
setRunMigrationOutput(response.output || response.message || "");
|
|
1614
|
+
setRunMigrationCompleted(true);
|
|
1615
|
+
dispatch(showToast({
|
|
1616
|
+
severity: "success",
|
|
1617
|
+
summary: "Migration workflow complete",
|
|
1618
|
+
detail: response.message || `Migration, build, and seed ran successfully for datasource "${selectedDatasource}".`,
|
|
1036
1619
|
}));
|
|
1037
1620
|
|
|
1038
|
-
setDrawerOpen(false);
|
|
1039
|
-
setDrawerDraft(null);
|
|
1040
|
-
setMappingPreview(null);
|
|
1041
|
-
setReviewedMetadataJsonValue(null);
|
|
1042
|
-
setMetadataJsonText("");
|
|
1043
|
-
setMetadataJsonValidationError(null);
|
|
1044
|
-
setMetadataJsonResetToken(`saved-${Date.now()}`);
|
|
1045
|
-
setDrawerStep("edit");
|
|
1046
1621
|
await refetchTables();
|
|
1047
1622
|
await refetchBootstrap();
|
|
1048
1623
|
} catch (error: any) {
|
|
1624
|
+
setRunMigrationOutput(readQueryError(error));
|
|
1049
1625
|
dispatch(showToast({
|
|
1050
1626
|
severity: "error",
|
|
1051
|
-
summary: "
|
|
1627
|
+
summary: "Run migration failed",
|
|
1052
1628
|
detail: readQueryError(error),
|
|
1053
1629
|
}));
|
|
1054
1630
|
}
|
|
@@ -1066,6 +1642,65 @@ export function DatasourceIntrospectionPage() {
|
|
|
1066
1642
|
);
|
|
1067
1643
|
}
|
|
1068
1644
|
|
|
1645
|
+
const stepSequence: WorkspaceStep[] = [
|
|
1646
|
+
"configure",
|
|
1647
|
+
"review-json",
|
|
1648
|
+
"generate-code",
|
|
1649
|
+
"create-migrations",
|
|
1650
|
+
"run-migration",
|
|
1651
|
+
];
|
|
1652
|
+
const currentStepIndex = stepSequence.indexOf(workspaceStep);
|
|
1653
|
+
const stepperItems = [
|
|
1654
|
+
{
|
|
1655
|
+
key: "configure" as const,
|
|
1656
|
+
label: "Configure Mapping",
|
|
1657
|
+
complete: currentStepIndex > 0,
|
|
1658
|
+
active: workspaceStep === "configure",
|
|
1659
|
+
disabled: false,
|
|
1660
|
+
onClick: () => setWorkspaceStep("configure"),
|
|
1661
|
+
},
|
|
1662
|
+
{
|
|
1663
|
+
key: "review-json" as const,
|
|
1664
|
+
label: "Review Mapping JSON",
|
|
1665
|
+
complete: currentStepIndex > 1,
|
|
1666
|
+
active: workspaceStep === "review-json",
|
|
1667
|
+
disabled: !mappingPreview,
|
|
1668
|
+
onClick: () => {
|
|
1669
|
+
if (mappingPreview) setWorkspaceStep("review-json");
|
|
1670
|
+
},
|
|
1671
|
+
},
|
|
1672
|
+
{
|
|
1673
|
+
key: "generate-code" as const,
|
|
1674
|
+
label: "Generate Code",
|
|
1675
|
+
complete: currentStepIndex > 2,
|
|
1676
|
+
active: workspaceStep === "generate-code",
|
|
1677
|
+
disabled: !persistedMappingResult,
|
|
1678
|
+
onClick: () => {
|
|
1679
|
+
if (persistedMappingResult) setWorkspaceStep("generate-code");
|
|
1680
|
+
},
|
|
1681
|
+
},
|
|
1682
|
+
{
|
|
1683
|
+
key: "create-migrations" as const,
|
|
1684
|
+
label: "Create Migrations",
|
|
1685
|
+
complete: currentStepIndex > 3,
|
|
1686
|
+
active: workspaceStep === "create-migrations",
|
|
1687
|
+
disabled: !hasAttemptedGenerateCode,
|
|
1688
|
+
onClick: () => {
|
|
1689
|
+
if (hasAttemptedGenerateCode) setWorkspaceStep("create-migrations");
|
|
1690
|
+
},
|
|
1691
|
+
},
|
|
1692
|
+
{
|
|
1693
|
+
key: "run-migration" as const,
|
|
1694
|
+
label: "Run Migration",
|
|
1695
|
+
complete: false,
|
|
1696
|
+
active: workspaceStep === "run-migration",
|
|
1697
|
+
disabled: !migrationArtifactsResult,
|
|
1698
|
+
onClick: () => {
|
|
1699
|
+
if (migrationArtifactsResult) setWorkspaceStep("run-migration");
|
|
1700
|
+
},
|
|
1701
|
+
},
|
|
1702
|
+
];
|
|
1703
|
+
|
|
1069
1704
|
const selectableUnmappedColumns = drawerDraft?.columns.filter(
|
|
1070
1705
|
(column) => !column.handledBySuperclass && !column.matched && !column.isPrimaryKey,
|
|
1071
1706
|
) ?? [];
|
|
@@ -1107,19 +1742,20 @@ export function DatasourceIntrospectionPage() {
|
|
|
1107
1742
|
</div>
|
|
1108
1743
|
<div className="sdix-form-field">
|
|
1109
1744
|
<label htmlFor="sdix-user-key-field">User Key Field</label>
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1745
|
+
<SolidSelect
|
|
1746
|
+
id="sdix-user-key-field"
|
|
1747
|
+
value={drawerDraft.userKeyField}
|
|
1748
|
+
options={drawerUserKeyOptions}
|
|
1749
|
+
placeholder="Select user key field"
|
|
1115
1750
|
onChange={(event) => changeUserKeyField(event.value)}
|
|
1116
1751
|
/>
|
|
1117
1752
|
</div>
|
|
1118
1753
|
<div className="sdix-form-field sdix-form-field--full">
|
|
1119
1754
|
<label htmlFor="sdix-description">Description</label>
|
|
1120
1755
|
<SolidTextarea
|
|
1756
|
+
className="sdix-description-textarea"
|
|
1121
1757
|
id="sdix-description"
|
|
1122
|
-
rows={
|
|
1758
|
+
rows={2}
|
|
1123
1759
|
value={drawerDraft.description}
|
|
1124
1760
|
onChange={(event) => updateGeneralField("description", event.target.value)}
|
|
1125
1761
|
/>
|
|
@@ -1163,20 +1799,16 @@ export function DatasourceIntrospectionPage() {
|
|
|
1163
1799
|
/>
|
|
1164
1800
|
</div>
|
|
1165
1801
|
<div className="sdix-form-field">
|
|
1166
|
-
<label htmlFor="sdix-legacy-type">
|
|
1167
|
-
<
|
|
1802
|
+
<label htmlFor="sdix-legacy-type">Mapping Strategy</label>
|
|
1803
|
+
<SolidSelect
|
|
1168
1804
|
id="sdix-legacy-type"
|
|
1169
|
-
value={
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
</div>
|
|
1173
|
-
<div className="sdix-form-field">
|
|
1174
|
-
<label htmlFor="sdix-base-class">Superclass</label>
|
|
1175
|
-
<SolidInput
|
|
1176
|
-
id="sdix-base-class"
|
|
1177
|
-
value={drawerDraft.baseClassName}
|
|
1178
|
-
disabled
|
|
1805
|
+
value={drawerDraft.legacyTableType}
|
|
1806
|
+
options={mappingStrategyOptions}
|
|
1807
|
+
onChange={(event) => updateLegacyTableType(event.value)}
|
|
1179
1808
|
/>
|
|
1809
|
+
<small className="sdix-inline-note">
|
|
1810
|
+
This will use <strong>{drawerDraft.baseClassName}</strong> for the generated entity.
|
|
1811
|
+
</small>
|
|
1180
1812
|
</div>
|
|
1181
1813
|
</div>
|
|
1182
1814
|
</section>
|
|
@@ -1196,10 +1828,6 @@ export function DatasourceIntrospectionPage() {
|
|
|
1196
1828
|
</div>
|
|
1197
1829
|
<div className="sdix-column-table">
|
|
1198
1830
|
<div className="sdix-column-row sdix-column-row--head sdix-column-row--mapping">
|
|
1199
|
-
<span>Column</span>
|
|
1200
|
-
<span>Field</span>
|
|
1201
|
-
<span>Database</span>
|
|
1202
|
-
<span>SolidX</span>
|
|
1203
1831
|
<span className="sdix-column-select-header">
|
|
1204
1832
|
{selectableUnmappedColumns.length ? (
|
|
1205
1833
|
<SolidCheckbox
|
|
@@ -1211,11 +1839,17 @@ export function DatasourceIntrospectionPage() {
|
|
|
1211
1839
|
"Include"
|
|
1212
1840
|
)}
|
|
1213
1841
|
</span>
|
|
1842
|
+
<span>Column</span>
|
|
1843
|
+
<span>Field</span>
|
|
1844
|
+
<span>Label</span>
|
|
1845
|
+
<span>Database</span>
|
|
1846
|
+
<span>SolidX</span>
|
|
1214
1847
|
<span>Status</span>
|
|
1215
1848
|
<span>Action</span>
|
|
1216
1849
|
</div>
|
|
1217
1850
|
{drawerDraft.columns.map((column) => {
|
|
1218
1851
|
const isUserKey = drawerDraft.userKeyField === column.fieldName;
|
|
1852
|
+
const isPrimaryKeyField = column.isPrimaryKey;
|
|
1219
1853
|
const statusClassName = column.handledBySuperclass
|
|
1220
1854
|
? "is-superclass"
|
|
1221
1855
|
: column.include
|
|
@@ -1236,6 +1870,20 @@ export function DatasourceIntrospectionPage() {
|
|
|
1236
1870
|
|
|
1237
1871
|
return (
|
|
1238
1872
|
<div key={column.columnName} className="sdix-column-row sdix-column-row--mapping">
|
|
1873
|
+
<span className="sdix-column-include">
|
|
1874
|
+
{column.handledBySuperclass ? (
|
|
1875
|
+
<span className="sdix-column-placeholder">-</span>
|
|
1876
|
+
) : column.matched ? (
|
|
1877
|
+
<span className="sdix-column-placeholder">-</span>
|
|
1878
|
+
) : column.isPrimaryKey ? (
|
|
1879
|
+
<SolidCheckbox checked disabled aria-label="Required included column" />
|
|
1880
|
+
) : (
|
|
1881
|
+
<SolidCheckbox
|
|
1882
|
+
checked={column.include}
|
|
1883
|
+
onChange={(event) => setColumnIncluded(column.columnName, event.target.checked)}
|
|
1884
|
+
/>
|
|
1885
|
+
)}
|
|
1886
|
+
</span>
|
|
1239
1887
|
<span className="sdix-column-meta">
|
|
1240
1888
|
<strong>{column.columnName}</strong>
|
|
1241
1889
|
{column.isAutoGenerated ? <small>Generated id column</small> : null}
|
|
@@ -1246,7 +1894,19 @@ export function DatasourceIntrospectionPage() {
|
|
|
1246
1894
|
disabled={column.handledBySuperclass}
|
|
1247
1895
|
onChange={(event) => updateColumnFieldName(column.columnName, event.target.value)}
|
|
1248
1896
|
/>
|
|
1249
|
-
{isUserKey
|
|
1897
|
+
{isUserKey || isPrimaryKeyField ? (
|
|
1898
|
+
<div className="sdix-inline-note-stack">
|
|
1899
|
+
{isUserKey ? <small className="sdix-inline-note">User key</small> : null}
|
|
1900
|
+
{isPrimaryKeyField ? <small className="sdix-inline-note">Primary key</small> : null}
|
|
1901
|
+
</div>
|
|
1902
|
+
) : null}
|
|
1903
|
+
</span>
|
|
1904
|
+
<span>
|
|
1905
|
+
<SolidInput
|
|
1906
|
+
value={column.displayName}
|
|
1907
|
+
disabled={column.handledBySuperclass}
|
|
1908
|
+
onChange={(event) => updateColumnDisplayName(column.columnName, event.target.value)}
|
|
1909
|
+
/>
|
|
1250
1910
|
</span>
|
|
1251
1911
|
<span className="sdix-column-meta">
|
|
1252
1912
|
<strong>{column.dataType}</strong>
|
|
@@ -1262,39 +1922,38 @@ export function DatasourceIntrospectionPage() {
|
|
|
1262
1922
|
/>
|
|
1263
1923
|
)}
|
|
1264
1924
|
</span>
|
|
1265
|
-
<span className="sdix-column-include">
|
|
1266
|
-
{column.handledBySuperclass ? (
|
|
1267
|
-
<span className="sdix-column-placeholder">-</span>
|
|
1268
|
-
) : column.matched ? (
|
|
1269
|
-
<span className="sdix-column-placeholder">-</span>
|
|
1270
|
-
) : column.isPrimaryKey ? (
|
|
1271
|
-
<SolidCheckbox checked disabled label="Required" />
|
|
1272
|
-
) : (
|
|
1273
|
-
<SolidCheckbox
|
|
1274
|
-
checked={column.include}
|
|
1275
|
-
onChange={(event) => setColumnIncluded(column.columnName, event.target.checked)}
|
|
1276
|
-
/>
|
|
1277
|
-
)}
|
|
1278
|
-
</span>
|
|
1279
1925
|
<span>
|
|
1280
1926
|
<span className={`sdix-status-badge ${statusClassName}`}>{statusText}</span>
|
|
1281
1927
|
</span>
|
|
1282
1928
|
<span className="sdix-column-action">
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1929
|
+
<div className="sdix-column-action-stack">
|
|
1930
|
+
{!column.handledBySuperclass && (column.include || column.matched) ? (
|
|
1931
|
+
<SolidButton
|
|
1932
|
+
size="small"
|
|
1933
|
+
variant="outline"
|
|
1934
|
+
className="sdix-column-action-icon"
|
|
1935
|
+
onClick={() => openFieldConfigEditor(column.columnName)}
|
|
1936
|
+
aria-label={`Configure ${column.fieldName || column.columnName} field`}
|
|
1937
|
+
>
|
|
1938
|
+
<Cog size={14} />
|
|
1939
|
+
</SolidButton>
|
|
1940
|
+
) : null}
|
|
1941
|
+
{column.handledBySuperclass ? (
|
|
1942
|
+
<SolidButton size="small" variant="outline" disabled className="sdix-column-action-chip sdix-column-action-chip--managed">
|
|
1943
|
+
Managed
|
|
1944
|
+
</SolidButton>
|
|
1945
|
+
) : column.isPrimaryKey ? (
|
|
1946
|
+
<SolidButton size="small" variant="outline" disabled className="sdix-column-action-chip sdix-column-action-chip--required">
|
|
1947
|
+
Required
|
|
1948
|
+
</SolidButton>
|
|
1949
|
+
) : column.matched ? (
|
|
1950
|
+
<SolidButton size="small" variant="outline" onClick={() => setColumnIncluded(column.columnName, false)}>
|
|
1951
|
+
Remove mapping
|
|
1952
|
+
</SolidButton>
|
|
1953
|
+
) : (
|
|
1954
|
+
<span className="sdix-column-placeholder">-</span>
|
|
1955
|
+
)}
|
|
1956
|
+
</div>
|
|
1298
1957
|
</span>
|
|
1299
1958
|
</div>
|
|
1300
1959
|
);
|
|
@@ -1321,120 +1980,229 @@ export function DatasourceIntrospectionPage() {
|
|
|
1321
1980
|
},
|
|
1322
1981
|
] : [];
|
|
1323
1982
|
|
|
1324
|
-
const
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1983
|
+
const reviewJsonPanel = mappingPreview ? (
|
|
1984
|
+
<div className="sdix-review-panel sdix-review-panel--metadata">
|
|
1985
|
+
<div className="sdix-review-meta sdix-review-meta--compact">
|
|
1986
|
+
<span>Target file</span>
|
|
1987
|
+
<strong>{mappingPreview.metadataJson.filePath}</strong>
|
|
1988
|
+
</div>
|
|
1989
|
+
<SolidJsonEditor
|
|
1990
|
+
className="sdix-json-editor"
|
|
1991
|
+
value={reviewedMetadataJsonValue ?? mappingPreview.metadataJson.model}
|
|
1992
|
+
resetToken={metadataJsonResetToken}
|
|
1993
|
+
onValueChange={(nextValue) => {
|
|
1994
|
+
setReviewedMetadataJsonValue(nextValue);
|
|
1995
|
+
if (metadataJsonValidationError) {
|
|
1996
|
+
setMetadataJsonValidationError(null);
|
|
1997
|
+
}
|
|
1998
|
+
}}
|
|
1999
|
+
onTextChange={(nextText) => {
|
|
2000
|
+
setMetadataJsonText(nextText);
|
|
2001
|
+
}}
|
|
2002
|
+
onErrorChange={setMetadataJsonValidationError}
|
|
2003
|
+
/>
|
|
2004
|
+
{metadataJsonValidationError ? (
|
|
2005
|
+
<div className="sdix-json-validation-error">
|
|
2006
|
+
<span>{metadataJsonValidationError}</span>
|
|
2007
|
+
</div>
|
|
2008
|
+
) : null}
|
|
2009
|
+
</div>
|
|
2010
|
+
) : null;
|
|
2011
|
+
|
|
2012
|
+
const createMigrationsPanel = mappingPreview ? (
|
|
2013
|
+
<div className="sdix-review-panel sdix-review-panel--migration">
|
|
2014
|
+
{mappingPreview.migration.willGenerate && mappingPreview.migration.filePath ? (
|
|
2015
|
+
<>
|
|
1330
2016
|
<div className="sdix-review-meta sdix-review-meta--compact">
|
|
1331
2017
|
<span>Target file</span>
|
|
1332
|
-
<strong>{mappingPreview.
|
|
2018
|
+
<strong>{mappingPreview.migration.filePath}</strong>
|
|
1333
2019
|
</div>
|
|
1334
|
-
<
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
setReviewedMetadataJsonValue(nextValue);
|
|
1340
|
-
if (metadataJsonValidationError) {
|
|
1341
|
-
setMetadataJsonValidationError(null);
|
|
1342
|
-
}
|
|
1343
|
-
}}
|
|
1344
|
-
onTextChange={(nextText) => {
|
|
1345
|
-
setMetadataJsonText(nextText);
|
|
1346
|
-
}}
|
|
1347
|
-
onErrorChange={setMetadataJsonValidationError}
|
|
1348
|
-
/>
|
|
1349
|
-
{metadataJsonValidationError ? (
|
|
1350
|
-
<div className="sdix-json-validation-error">
|
|
1351
|
-
<span>{metadataJsonValidationError}</span>
|
|
1352
|
-
</div>
|
|
2020
|
+
<p className="sdix-review-note">
|
|
2021
|
+
This migration preview is generated from the current mapping and stays read-only here. Moving ahead writes the migration file to disk and updates the datasource registration now that the entity code already exists.
|
|
2022
|
+
</p>
|
|
2023
|
+
{mappingPreview.migration.content ? (
|
|
2024
|
+
<pre className="sdix-ddl-code sdix-ddl-code--fill"><code>{mappingPreview.migration.content}</code></pre>
|
|
1353
2025
|
) : null}
|
|
2026
|
+
</>
|
|
2027
|
+
) : (
|
|
2028
|
+
<div className="sdix-empty-state is-inline sdix-ddl-empty">
|
|
2029
|
+
<Database size={18} />
|
|
2030
|
+
<p>{mappingPreview.migration.note}</p>
|
|
1354
2031
|
</div>
|
|
1355
|
-
)
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
</>
|
|
1375
|
-
) : (
|
|
1376
|
-
<div className="sdix-empty-state is-inline sdix-ddl-empty">
|
|
1377
|
-
<Database size={18} />
|
|
1378
|
-
<p>{mappingPreview.migration.note}</p>
|
|
1379
|
-
</div>
|
|
1380
|
-
)}
|
|
2032
|
+
)}
|
|
2033
|
+
</div>
|
|
2034
|
+
) : null;
|
|
2035
|
+
|
|
2036
|
+
const generateCodePanel = (
|
|
2037
|
+
<div className="sdix-stage-panel">
|
|
2038
|
+
<div className="sdix-stage-panel__header">
|
|
2039
|
+
<span className="sdix-stage-panel__eyebrow">Step 3</span>
|
|
2040
|
+
<h3>Generate module code</h3>
|
|
2041
|
+
<p>The reviewed metadata JSON is now persisted. Run the normal SolidX module generation flow next so the entity, DTOs, repository, service, controller, and UI code are refreshed before we register the entity on the datasource and write the migration.</p>
|
|
2042
|
+
</div>
|
|
2043
|
+
<div className="sdix-stage-panel__body">
|
|
2044
|
+
<div className="sdix-stage-panel__card">
|
|
2045
|
+
<div className="sdix-stage-panel__card-title">What happens here</div>
|
|
2046
|
+
<ul className="sdix-stage-panel__list">
|
|
2047
|
+
<li>The module generate-code pipeline runs for <strong>{bootstrap?.module?.displayName}</strong>.</li>
|
|
2048
|
+
<li>The newly mapped model entity is generated inside the module source folder.</li>
|
|
2049
|
+
<li>Any existing generated assets for this module may be refreshed.</li>
|
|
2050
|
+
</ul>
|
|
1381
2051
|
</div>
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
<Search size={15} />
|
|
1392
|
-
<SolidInput
|
|
1393
|
-
value={tableSearch}
|
|
1394
|
-
onChange={(event) => setTableSearch(event.target.value)}
|
|
1395
|
-
placeholder="Search tables"
|
|
1396
|
-
disabled={!selectedDatasource}
|
|
1397
|
-
/>
|
|
1398
|
-
</div>
|
|
2052
|
+
<div className="sdix-stage-panel__actions">
|
|
2053
|
+
<SolidButton
|
|
2054
|
+
leftIcon={<Sparkles size={14} />}
|
|
2055
|
+
loading={isGenerateCodeLoading || isGeneratingCodeWorkflow}
|
|
2056
|
+
disabled={isGenerateCodeLoading || isGeneratingCodeWorkflow}
|
|
2057
|
+
onClick={() => void handleGenerateCode()}
|
|
2058
|
+
>
|
|
2059
|
+
Generate Code
|
|
2060
|
+
</SolidButton>
|
|
1399
2061
|
</div>
|
|
2062
|
+
{isGeneratingCodeWorkflow ? (
|
|
2063
|
+
<div className="sdix-empty-state is-inline sdix-stage-progress">
|
|
2064
|
+
<SolidSpinner size={30} />
|
|
2065
|
+
<h3>Generating code</h3>
|
|
2066
|
+
<p>{generateCodeStatusMessage || "Waiting for the backend to finish restarting..."}</p>
|
|
2067
|
+
</div>
|
|
2068
|
+
) : null}
|
|
2069
|
+
{!isGeneratingCodeWorkflow && generateCodeTimeoutMessage ? (
|
|
2070
|
+
<div className="sdix-error is-inline sdix-stage-progress sdix-stage-progress--warning">
|
|
2071
|
+
<ShieldAlert size={22} />
|
|
2072
|
+
<h3>Manual review recommended</h3>
|
|
2073
|
+
<p>{generateCodeTimeoutMessage}</p>
|
|
2074
|
+
</div>
|
|
2075
|
+
) : null}
|
|
2076
|
+
{generateCodeOutput ? (
|
|
2077
|
+
<pre className="sdix-ddl-code sdix-ddl-code--fill"><code>{generateCodeOutput}</code></pre>
|
|
2078
|
+
) : null}
|
|
2079
|
+
</div>
|
|
2080
|
+
</div>
|
|
2081
|
+
);
|
|
1400
2082
|
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
<SolidSelect
|
|
1409
|
-
value={selectedDatasource}
|
|
1410
|
-
options={datasourceOptions}
|
|
1411
|
-
placeholder="Datasource"
|
|
1412
|
-
className="sdix-toolbar-select sdix-toolbar-select--compact"
|
|
1413
|
-
onChange={(event) => setSelectedDatasource(event.value)}
|
|
1414
|
-
/>
|
|
1415
|
-
<SolidButton
|
|
1416
|
-
size="small"
|
|
1417
|
-
className="sdix-icon-button"
|
|
1418
|
-
leftIcon={<RefreshCw size={16} />}
|
|
1419
|
-
onClick={() => void handleRefresh()}
|
|
1420
|
-
tooltip="Refresh"
|
|
1421
|
-
aria-label="Refresh"
|
|
1422
|
-
/>
|
|
1423
|
-
<SolidButton
|
|
1424
|
-
size="small"
|
|
1425
|
-
className="sdix-icon-button"
|
|
1426
|
-
leftIcon={<Cog size={16} />}
|
|
1427
|
-
onClick={() => {
|
|
1428
|
-
setDraftSessionSettings(sessionSettings);
|
|
1429
|
-
setSettingsTab("datasource-info");
|
|
1430
|
-
setSettingsOpen(true);
|
|
1431
|
-
}}
|
|
1432
|
-
tooltip="Session settings"
|
|
1433
|
-
aria-label="Session settings"
|
|
1434
|
-
/>
|
|
2083
|
+
const runMigrationPanel = (
|
|
2084
|
+
<div className="sdix-stage-panel">
|
|
2085
|
+
{!runMigrationCompleted ? (
|
|
2086
|
+
<div className="sdix-stage-panel__header">
|
|
2087
|
+
<span className="sdix-stage-panel__eyebrow">Step 5</span>
|
|
2088
|
+
<h3>Run migration, build, and seed</h3>
|
|
2089
|
+
<p>With the datasource file updated and module code generated, SolidX can now run the TypeORM migration for the selected datasource, rebuild the project, and reseed the mapped module metadata.</p>
|
|
1435
2090
|
</div>
|
|
2091
|
+
) : null}
|
|
2092
|
+
<div className="sdix-stage-panel__body">
|
|
2093
|
+
{!runMigrationCompleted ? (
|
|
2094
|
+
<div className="sdix-stage-panel__card">
|
|
2095
|
+
<div className="sdix-stage-panel__card-title">Commands</div>
|
|
2096
|
+
<pre className="sdix-stage-panel__command"><code>{`npx @solidxai/solidctl@latest migration -d ${selectedDatasource} run
|
|
2097
|
+
npx @solidxai/solidctl@latest build
|
|
2098
|
+
npx @solidxai/solidctl@latest seed --modules-to-seed ${bootstrap?.module?.name || ""}`}</code></pre>
|
|
2099
|
+
</div>
|
|
2100
|
+
) : null}
|
|
2101
|
+
{runMigrationCompleted ? (
|
|
2102
|
+
<div className="sdix-empty-state is-inline sdix-stage-progress">
|
|
2103
|
+
<Check size={24} />
|
|
2104
|
+
<h3>Migration workflow finished</h3>
|
|
2105
|
+
<p>Review the command output below. When you are satisfied, click Finish to return to the model list.</p>
|
|
2106
|
+
</div>
|
|
2107
|
+
) : null}
|
|
2108
|
+
{runMigrationOutput ? (
|
|
2109
|
+
<pre className="sdix-ddl-code sdix-ddl-code--fill"><code>{runMigrationOutput}</code></pre>
|
|
2110
|
+
) : null}
|
|
1436
2111
|
</div>
|
|
2112
|
+
</div>
|
|
2113
|
+
);
|
|
2114
|
+
|
|
2115
|
+
const workspaceContent = drawerDraft ? (() => {
|
|
2116
|
+
switch (workspaceStep) {
|
|
2117
|
+
case "review-json":
|
|
2118
|
+
return reviewJsonPanel;
|
|
2119
|
+
case "create-migrations":
|
|
2120
|
+
return createMigrationsPanel;
|
|
2121
|
+
case "generate-code":
|
|
2122
|
+
return generateCodePanel;
|
|
2123
|
+
case "run-migration":
|
|
2124
|
+
return runMigrationPanel;
|
|
2125
|
+
case "configure":
|
|
2126
|
+
default:
|
|
2127
|
+
return (
|
|
2128
|
+
<SolidTabGroup
|
|
2129
|
+
tabs={drawerTabs}
|
|
2130
|
+
value={drawerTab}
|
|
2131
|
+
onValueChange={setDrawerTab}
|
|
2132
|
+
className="sdix-drawer-tabs sdix-wizard-tabs"
|
|
2133
|
+
panelClassName="sdix-drawer-tab-panel sdix-wizard-tab-panel"
|
|
2134
|
+
/>
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2137
|
+
})() : null;
|
|
2138
|
+
|
|
2139
|
+
const workspaceMeta = drawerDraft ? (() => {
|
|
2140
|
+
switch (workspaceStep) {
|
|
2141
|
+
case "review-json":
|
|
2142
|
+
return (
|
|
2143
|
+
<span>
|
|
2144
|
+
Review the generated metadata JSON before it is written to the module metadata file.
|
|
2145
|
+
</span>
|
|
2146
|
+
);
|
|
2147
|
+
case "generate-code":
|
|
2148
|
+
return persistedMappingResult ? (
|
|
2149
|
+
<span>
|
|
2150
|
+
Metadata JSON has been saved for
|
|
2151
|
+
{" "}
|
|
2152
|
+
<strong>{drawerDraft.displayName || drawerDraft.singularName}</strong>
|
|
2153
|
+
. Generate the module code next so the entity file exists before datasource registration is updated. Continue stays disabled until you attempt code generation at least once.
|
|
2154
|
+
</span>
|
|
2155
|
+
) : (
|
|
2156
|
+
<span>Save the metadata JSON before generating code.</span>
|
|
2157
|
+
);
|
|
2158
|
+
case "create-migrations":
|
|
2159
|
+
return mappingPreview ? (
|
|
2160
|
+
<span>
|
|
2161
|
+
Create the migration artifacts for
|
|
2162
|
+
{" "}
|
|
2163
|
+
<strong>{mappingPreview.summary.includedColumnNames.length}</strong>
|
|
2164
|
+
{" "}
|
|
2165
|
+
mapped columns
|
|
2166
|
+
{mappingPreview.summary.generatedSystemColumnCount
|
|
2167
|
+
? ` and ${mappingPreview.summary.generatedSystemColumnCount} SolidX system column migration changes`
|
|
2168
|
+
: ""}
|
|
2169
|
+
.
|
|
2170
|
+
</span>
|
|
2171
|
+
) : null;
|
|
2172
|
+
case "run-migration":
|
|
2173
|
+
return runMigrationCompleted ? (
|
|
2174
|
+
<span>
|
|
2175
|
+
The migration, build, and seed workflow has completed for
|
|
2176
|
+
{" "}
|
|
2177
|
+
<strong>{selectedDatasource}</strong>
|
|
2178
|
+
. Review the output below and click Finish when you are ready to return to the model list.
|
|
2179
|
+
</span>
|
|
2180
|
+
) : (
|
|
2181
|
+
<span>
|
|
2182
|
+
Run the migration, build, and seed workflow for
|
|
2183
|
+
{" "}
|
|
2184
|
+
<strong>{selectedDatasource}</strong>
|
|
2185
|
+
{" "}
|
|
2186
|
+
to apply the SolidX system columns on the legacy table and refresh the module metadata state.
|
|
2187
|
+
</span>
|
|
2188
|
+
);
|
|
2189
|
+
case "configure":
|
|
2190
|
+
default:
|
|
2191
|
+
return drawerDraft.mapped ? (
|
|
2192
|
+
<span>
|
|
2193
|
+
Editing mapped model
|
|
2194
|
+
{" "}
|
|
2195
|
+
<strong>{selectedTable?.mappedModelDisplayName || selectedTable?.mappedModelSingularName}</strong>
|
|
2196
|
+
{drawerDraft.mappedModuleLabel ? ` in ${drawerDraft.mappedModuleLabel}` : ""}
|
|
2197
|
+
</span>
|
|
2198
|
+
) : (
|
|
2199
|
+
<span>Configure the field mapping draft before generating the metadata JSON preview.</span>
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
})() : null;
|
|
1437
2203
|
|
|
2204
|
+
return (
|
|
2205
|
+
<div className="sdix-page">
|
|
1438
2206
|
{isBootstrapLoading ? (
|
|
1439
2207
|
<div className="sdix-loading">
|
|
1440
2208
|
<SolidSpinner size={28} />
|
|
@@ -1449,6 +2217,70 @@ export function DatasourceIntrospectionPage() {
|
|
|
1449
2217
|
</div>
|
|
1450
2218
|
) : (
|
|
1451
2219
|
<>
|
|
2220
|
+
{!isWorkspaceMode ? (
|
|
2221
|
+
<div className="sdix-toolbar">
|
|
2222
|
+
<div className="sdix-toolbar__left">
|
|
2223
|
+
<div className="sdix-toolbar-search">
|
|
2224
|
+
<Search size={15} />
|
|
2225
|
+
<SolidInput
|
|
2226
|
+
value={tableSearch}
|
|
2227
|
+
onChange={(event) => setTableSearch(event.target.value)}
|
|
2228
|
+
placeholder="Search tables"
|
|
2229
|
+
disabled={!selectedDatasource}
|
|
2230
|
+
/>
|
|
2231
|
+
</div>
|
|
2232
|
+
</div>
|
|
2233
|
+
|
|
2234
|
+
<div className="sdix-toolbar__actions">
|
|
2235
|
+
{isBootstrapFetching || isTablesFetching ? (
|
|
2236
|
+
<div className="sdix-inline-status">
|
|
2237
|
+
<SolidSpinner size={16} />
|
|
2238
|
+
<span>Refreshing</span>
|
|
2239
|
+
</div>
|
|
2240
|
+
) : null}
|
|
2241
|
+
<SolidButton
|
|
2242
|
+
size="small"
|
|
2243
|
+
leftIcon={<Sparkles size={14} />}
|
|
2244
|
+
onClick={() => {
|
|
2245
|
+
if (selectedListRow) {
|
|
2246
|
+
startMappingForRecord(selectedListRow);
|
|
2247
|
+
}
|
|
2248
|
+
}}
|
|
2249
|
+
disabled={!selectedListRow}
|
|
2250
|
+
>
|
|
2251
|
+
Start Mapping
|
|
2252
|
+
</SolidButton>
|
|
2253
|
+
<SolidSelect
|
|
2254
|
+
value={selectedDatasource}
|
|
2255
|
+
options={datasourceOptions}
|
|
2256
|
+
placeholder="Datasource"
|
|
2257
|
+
className="sdix-toolbar-select sdix-toolbar-select--compact"
|
|
2258
|
+
onChange={(event) => setSelectedDatasource(event.value)}
|
|
2259
|
+
/>
|
|
2260
|
+
<SolidButton
|
|
2261
|
+
size="small"
|
|
2262
|
+
className="sdix-icon-button"
|
|
2263
|
+
leftIcon={<RefreshCw size={16} />}
|
|
2264
|
+
onClick={() => void handleRefresh()}
|
|
2265
|
+
tooltip="Refresh"
|
|
2266
|
+
aria-label="Refresh"
|
|
2267
|
+
/>
|
|
2268
|
+
<SolidButton
|
|
2269
|
+
size="small"
|
|
2270
|
+
className="sdix-icon-button"
|
|
2271
|
+
leftIcon={<Cog size={16} />}
|
|
2272
|
+
onClick={() => {
|
|
2273
|
+
setDraftSessionSettings(sessionSettings);
|
|
2274
|
+
setSettingsTab("datasource-info");
|
|
2275
|
+
setSettingsOpen(true);
|
|
2276
|
+
}}
|
|
2277
|
+
tooltip="Session settings"
|
|
2278
|
+
aria-label="Session settings"
|
|
2279
|
+
/>
|
|
2280
|
+
</div>
|
|
2281
|
+
</div>
|
|
2282
|
+
) : null}
|
|
2283
|
+
|
|
1452
2284
|
{selectedDatasource && tablesResponse?.synchronizeBlocked ? (
|
|
1453
2285
|
<div className="sdix-blocker">
|
|
1454
2286
|
<ShieldAlert size={20} />
|
|
@@ -1459,93 +2291,310 @@ export function DatasourceIntrospectionPage() {
|
|
|
1459
2291
|
</div>
|
|
1460
2292
|
) : null}
|
|
1461
2293
|
|
|
1462
|
-
|
|
1463
|
-
<div className="sdix-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
2294
|
+
{!isWorkspaceMode ? (
|
|
2295
|
+
<div className="sdix-workbench">
|
|
2296
|
+
<div className="sdix-table-shell">
|
|
2297
|
+
{!selectedDatasource ? (
|
|
2298
|
+
<div className="sdix-empty-state is-inline">
|
|
2299
|
+
<Database size={22} />
|
|
2300
|
+
<p>Select a datasource to load its tables.</p>
|
|
2301
|
+
</div>
|
|
2302
|
+
) : isTablesLoading ? (
|
|
2303
|
+
<div className="sdix-loading is-inline">
|
|
2304
|
+
<SolidSpinner size={24} />
|
|
2305
|
+
<p>Inspecting datasource tables...</p>
|
|
2306
|
+
</div>
|
|
2307
|
+
) : tablesError ? (
|
|
2308
|
+
<div className="sdix-error is-inline">
|
|
2309
|
+
<span>{readQueryError(tablesError)}</span>
|
|
2310
|
+
<SolidButton size="small" variant="outline" onClick={() => void refetchTables()}>
|
|
2311
|
+
Retry
|
|
2312
|
+
</SolidButton>
|
|
2313
|
+
</div>
|
|
2314
|
+
) : !filteredRecords.length ? (
|
|
2315
|
+
<div className="sdix-empty-state is-inline">
|
|
2316
|
+
<Database size={22} />
|
|
2317
|
+
<p>{tableSearch ? "No tables match the current quick filter." : "No database tables were discovered for this datasource."}</p>
|
|
2318
|
+
</div>
|
|
2319
|
+
) : (
|
|
2320
|
+
<div className="sdix-table-area">
|
|
2321
|
+
<SolidDataTable
|
|
2322
|
+
value={filteredRecords}
|
|
2323
|
+
dataKey="tableName"
|
|
2324
|
+
viewportHeight="calc(100vh - 162px)"
|
|
2325
|
+
size="small"
|
|
2326
|
+
onRowClick={({ data }) => setSelectedRows([data])}
|
|
2327
|
+
rowClassName={(rowData) => `${rowData.schema ?? ""}::${rowData.tableName}` === selectedListRowKey ? "sdix-table-row-selected" : ""}
|
|
2328
|
+
emptyMessage="No datasource tables found"
|
|
2329
|
+
>
|
|
2330
|
+
<Column
|
|
2331
|
+
header=""
|
|
2332
|
+
body={(rowData: DatasourceIntrospectionTableRecord) => {
|
|
2333
|
+
const rowKey = `${rowData.schema ?? ""}::${rowData.tableName}`;
|
|
2334
|
+
return (
|
|
2335
|
+
<div className="sdix-selection-cell" data-no-row-click="true">
|
|
2336
|
+
<SolidCheckbox
|
|
2337
|
+
checked={rowKey === selectedListRowKey}
|
|
2338
|
+
onChange={(event) => setSelectedRows(event.target.checked ? [rowData] : [])}
|
|
2339
|
+
/>
|
|
2340
|
+
</div>
|
|
2341
|
+
);
|
|
2342
|
+
}}
|
|
2343
|
+
style={{ width: 52, minWidth: 52 }}
|
|
2344
|
+
/>
|
|
2345
|
+
<Column
|
|
2346
|
+
field="tableName"
|
|
2347
|
+
header="Table"
|
|
2348
|
+
body={(rowData: DatasourceIntrospectionTableRecord) => (
|
|
2349
|
+
<div className="sdix-table-primary-cell">
|
|
2350
|
+
<strong>{rowData.tableName}</strong>
|
|
2351
|
+
</div>
|
|
2352
|
+
)}
|
|
2353
|
+
style={{ minWidth: 240 }}
|
|
2354
|
+
/>
|
|
2355
|
+
<Column field="schema" header="Schema" body={(rowData: DatasourceIntrospectionTableRecord) => rowData.schema || "dbo/default"} style={{ minWidth: 120 }} />
|
|
2356
|
+
<Column
|
|
2357
|
+
header="Status"
|
|
2358
|
+
body={(rowData: DatasourceIntrospectionTableRecord) => (
|
|
2359
|
+
<span
|
|
2360
|
+
className={`sdix-status-badge ${rowData.mapped ? "is-mapped" : "is-unmapped"}`}
|
|
2361
|
+
>
|
|
2362
|
+
{statusLabel(rowData)}
|
|
2363
|
+
</span>
|
|
2364
|
+
)}
|
|
2365
|
+
style={{ minWidth: 150 }}
|
|
2366
|
+
/>
|
|
2367
|
+
<Column
|
|
2368
|
+
header="Superclass"
|
|
2369
|
+
body={(rowData: DatasourceIntrospectionTableRecord) => rowData.suggestedBaseClassName}
|
|
2370
|
+
style={{ minWidth: 220 }}
|
|
2371
|
+
/>
|
|
2372
|
+
<Column field="suggestedUserKeyField" header="User Key" style={{ minWidth: 160 }} />
|
|
2373
|
+
<Column
|
|
2374
|
+
header="Mapped Model"
|
|
2375
|
+
body={(rowData: DatasourceIntrospectionTableRecord) => (
|
|
2376
|
+
rowData.mapped
|
|
2377
|
+
? (
|
|
2378
|
+
<div className="sdix-table-primary-cell">
|
|
2379
|
+
<strong>{rowData.mappedModelDisplayName || rowData.mappedModelSingularName || "-"}</strong>
|
|
2380
|
+
{rowData.mappedModuleDisplayName || rowData.mappedModuleName ? (
|
|
2381
|
+
<span>{rowData.mappedModuleDisplayName || rowData.mappedModuleName}</span>
|
|
2382
|
+
) : null}
|
|
2383
|
+
</div>
|
|
2384
|
+
)
|
|
2385
|
+
: <span className="sdix-muted-cell">Not mapped</span>
|
|
2386
|
+
)}
|
|
2387
|
+
style={{ minWidth: 200 }}
|
|
2388
|
+
/>
|
|
2389
|
+
<Column
|
|
2390
|
+
header="System Columns"
|
|
2391
|
+
body={(rowData: DatasourceIntrospectionTableRecord) => Math.max(rowData.columnCount - rowData.physicalColumnCount, 0)}
|
|
2392
|
+
style={{ minWidth: 130 }}
|
|
2393
|
+
/>
|
|
2394
|
+
<Column
|
|
2395
|
+
header="Columns"
|
|
2396
|
+
body={(rowData: DatasourceIntrospectionTableRecord) => rowData.physicalColumnCount}
|
|
2397
|
+
style={{ minWidth: 110 }}
|
|
2398
|
+
/>
|
|
2399
|
+
</SolidDataTable>
|
|
2400
|
+
</div>
|
|
2401
|
+
)}
|
|
1473
2402
|
</div>
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
2403
|
+
</div>
|
|
2404
|
+
) : (
|
|
2405
|
+
<div className="sdix-workspace-screen">
|
|
2406
|
+
<div className="sdix-workspace-header">
|
|
2407
|
+
<SolidButton size="small" variant="outline" leftIcon={<ArrowLeft size={14} />} onClick={leaveWorkspace}>
|
|
2408
|
+
Back
|
|
1479
2409
|
</SolidButton>
|
|
2410
|
+
<div className="sdix-workspace-header__identity sdix-workspace-header__identity--right">
|
|
2411
|
+
<h2>{selectedTable?.tableName || "Mapping workspace"}</h2>
|
|
2412
|
+
<p>
|
|
2413
|
+
{selectedTable
|
|
2414
|
+
? `${selectedTable.schema || "dbo"} · ${selectedDatasourceRecord?.database || selectedDatasourceRecord?.displayName || "Datasource"}${selectedDatasourceRecord ? ` · ${providerLabel(selectedDatasourceRecord.type)}` : ""}`
|
|
2415
|
+
: "Complete the legacy table mapping in a dedicated workspace."}
|
|
2416
|
+
</p>
|
|
2417
|
+
</div>
|
|
1480
2418
|
</div>
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
<
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
size="small"
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
)
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
2419
|
+
|
|
2420
|
+
{!selectedTable && !isTablesLoading ? (
|
|
2421
|
+
<div className="sdix-error">
|
|
2422
|
+
<span>The selected table is no longer available for this datasource.</span>
|
|
2423
|
+
<SolidButton size="small" variant="outline" onClick={leaveWorkspace}>
|
|
2424
|
+
Back to table list
|
|
2425
|
+
</SolidButton>
|
|
2426
|
+
</div>
|
|
2427
|
+
) : drawerLoadingError ? (
|
|
2428
|
+
<div className="sdix-error">
|
|
2429
|
+
<span>{readQueryError(drawerLoadingError)}</span>
|
|
2430
|
+
<SolidButton size="small" variant="outline" onClick={() => void refetchTableDetail()}>
|
|
2431
|
+
Retry
|
|
2432
|
+
</SolidButton>
|
|
2433
|
+
</div>
|
|
2434
|
+
) : isDrawerBlockingLoading ? (
|
|
2435
|
+
<div className="sdix-loading sdix-workspace-loading">
|
|
2436
|
+
<SolidSpinner size={24} />
|
|
2437
|
+
<p>Preparing column mapping preview...</p>
|
|
2438
|
+
</div>
|
|
2439
|
+
) : (
|
|
2440
|
+
<div className="sdix-wizard-shell">
|
|
2441
|
+
<div className="sdix-wizard-stepper">
|
|
2442
|
+
{stepperItems.map((step, index) => (
|
|
2443
|
+
<button
|
|
2444
|
+
key={step.key}
|
|
2445
|
+
type="button"
|
|
2446
|
+
className={[
|
|
2447
|
+
"sdix-wizard-stepper-item",
|
|
2448
|
+
step.active ? "is-active" : "",
|
|
2449
|
+
step.complete ? "is-complete" : "",
|
|
2450
|
+
step.disabled ? "is-disabled" : "",
|
|
2451
|
+
].filter(Boolean).join(" ")}
|
|
2452
|
+
onClick={step.onClick}
|
|
2453
|
+
disabled={step.disabled}
|
|
1513
2454
|
>
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
2455
|
+
<span className="sdix-wizard-stepper-badge">
|
|
2456
|
+
{step.complete ? <Check size={12} /> : index + 1}
|
|
2457
|
+
</span>
|
|
2458
|
+
<span className="sdix-wizard-stepper-label">{step.label}</span>
|
|
2459
|
+
</button>
|
|
2460
|
+
))}
|
|
2461
|
+
</div>
|
|
2462
|
+
|
|
2463
|
+
<div className="sdix-wizard-panel">
|
|
2464
|
+
<div className="sdix-wizard-panel__body">
|
|
2465
|
+
{workspaceContent}
|
|
2466
|
+
</div>
|
|
2467
|
+
</div>
|
|
2468
|
+
|
|
2469
|
+
{drawerDraft ? (
|
|
2470
|
+
<div className="sdix-wizard-footer">
|
|
2471
|
+
<div className="sdix-wizard-footer__meta">{workspaceMeta}</div>
|
|
2472
|
+
<div className="sdix-wizard-footer__actions">
|
|
2473
|
+
{drawerDraft.mapped && drawerDraft.modelId ? (
|
|
2474
|
+
<SolidButton size="small" variant="outline" leftIcon={<ExternalLink size={14} />} onClick={openMappedModelForm}>
|
|
2475
|
+
Open model form
|
|
2476
|
+
</SolidButton>
|
|
2477
|
+
) : null}
|
|
2478
|
+
{workspaceStep === "configure" ? (
|
|
2479
|
+
<SolidButton
|
|
2480
|
+
size="small"
|
|
2481
|
+
leftIcon={<Save size={14} />}
|
|
2482
|
+
loading={isPreviewMappingLoading}
|
|
2483
|
+
onClick={() => void reviewMappingJson()}
|
|
2484
|
+
>
|
|
2485
|
+
Review Mapping
|
|
2486
|
+
</SolidButton>
|
|
2487
|
+
) : null}
|
|
2488
|
+
{workspaceStep === "review-json" ? (
|
|
2489
|
+
<>
|
|
2490
|
+
<SolidButton size="small" variant="outline" onClick={() => setWorkspaceStep("configure")}>
|
|
2491
|
+
Back
|
|
2492
|
+
</SolidButton>
|
|
2493
|
+
<SolidButton size="small" leftIcon={<Save size={14} />} loading={isSavingMapping} onClick={() => void persistMappingArtifacts()}>
|
|
2494
|
+
Continue
|
|
2495
|
+
</SolidButton>
|
|
2496
|
+
</>
|
|
2497
|
+
) : null}
|
|
2498
|
+
{workspaceStep === "generate-code" ? (
|
|
2499
|
+
<>
|
|
2500
|
+
<SolidButton size="small" variant="outline" onClick={() => setWorkspaceStep("review-json")}>
|
|
2501
|
+
Back
|
|
2502
|
+
</SolidButton>
|
|
2503
|
+
<SolidButton
|
|
2504
|
+
size="small"
|
|
2505
|
+
disabled={!hasAttemptedGenerateCode || isGenerateCodeLoading || isGeneratingCodeWorkflow}
|
|
2506
|
+
onClick={() => setWorkspaceStep("create-migrations")}
|
|
2507
|
+
>
|
|
2508
|
+
Continue
|
|
2509
|
+
</SolidButton>
|
|
2510
|
+
</>
|
|
2511
|
+
) : null}
|
|
2512
|
+
{workspaceStep === "create-migrations" ? (
|
|
2513
|
+
<>
|
|
2514
|
+
<SolidButton size="small" variant="outline" onClick={() => setWorkspaceStep("generate-code")}>
|
|
2515
|
+
Back
|
|
2516
|
+
</SolidButton>
|
|
2517
|
+
<SolidButton size="small" leftIcon={<Save size={14} />} loading={isCreatingMigrationArtifacts} onClick={() => void handleCreateMigrationArtifacts()}>
|
|
2518
|
+
Continue
|
|
2519
|
+
</SolidButton>
|
|
2520
|
+
</>
|
|
2521
|
+
) : null}
|
|
2522
|
+
{workspaceStep === "run-migration" ? (
|
|
2523
|
+
runMigrationCompleted ? (
|
|
2524
|
+
<SolidButton
|
|
2525
|
+
size="small"
|
|
2526
|
+
leftIcon={<Check size={14} />}
|
|
2527
|
+
onClick={() => navigate("/admin/core/solid-core/model-metadata/list")}
|
|
2528
|
+
>
|
|
2529
|
+
Finish
|
|
2530
|
+
</SolidButton>
|
|
2531
|
+
) : (
|
|
2532
|
+
<>
|
|
2533
|
+
<SolidButton size="small" variant="outline" onClick={() => setWorkspaceStep("create-migrations")}>
|
|
2534
|
+
Back
|
|
2535
|
+
</SolidButton>
|
|
2536
|
+
<SolidButton size="small" leftIcon={<Database size={14} />} loading={isRunMigrationLoading} onClick={() => void handleRunMigration()}>
|
|
2537
|
+
Continue
|
|
2538
|
+
</SolidButton>
|
|
2539
|
+
</>
|
|
2540
|
+
)
|
|
2541
|
+
) : null}
|
|
2542
|
+
</div>
|
|
2543
|
+
</div>
|
|
2544
|
+
) : null}
|
|
2545
|
+
</div>
|
|
2546
|
+
)}
|
|
1547
2547
|
</div>
|
|
1548
|
-
|
|
2548
|
+
)}
|
|
2549
|
+
|
|
2550
|
+
<SolidDialog
|
|
2551
|
+
open={Boolean(activeFieldConfigColumn)}
|
|
2552
|
+
onOpenChange={(open) => {
|
|
2553
|
+
if (!open) {
|
|
2554
|
+
setFieldConfigColumnName(null);
|
|
2555
|
+
}
|
|
2556
|
+
}}
|
|
2557
|
+
style={{ width: "min(1080px, calc(100vw - 48px))" }}
|
|
2558
|
+
>
|
|
2559
|
+
<SolidDialogHeader>
|
|
2560
|
+
<div>
|
|
2561
|
+
<SolidDialogTitle>Field configuration</SolidDialogTitle>
|
|
2562
|
+
<SolidDialogDescription>
|
|
2563
|
+
Reuse the standard field form here to fine-tune the mapped SolidX field before saving the introspection draft.
|
|
2564
|
+
</SolidDialogDescription>
|
|
2565
|
+
</div>
|
|
2566
|
+
<SolidDialogClose />
|
|
2567
|
+
</SolidDialogHeader>
|
|
2568
|
+
<SolidDialogSeparator />
|
|
2569
|
+
<SolidDialogBody>
|
|
2570
|
+
{activeFieldConfigColumn && activeFieldConfigMetaData && drawerDraft ? (
|
|
2571
|
+
<FieldMetaDataForm
|
|
2572
|
+
modelMetaData={{
|
|
2573
|
+
singularName: drawerDraft.singularName,
|
|
2574
|
+
pluralName: drawerDraft.pluralName,
|
|
2575
|
+
}}
|
|
2576
|
+
fieldMetaData={activeFieldConfigMetaData}
|
|
2577
|
+
setFieldMetaData={() => undefined}
|
|
2578
|
+
setVisiblePopup={() => undefined}
|
|
2579
|
+
setIsDirty={() => undefined}
|
|
2580
|
+
allFields={fieldDraftAllFields}
|
|
2581
|
+
deleteModelFunction={() => undefined}
|
|
2582
|
+
params={params}
|
|
2583
|
+
setIsRequiredPopUp={() => undefined}
|
|
2584
|
+
showToaster={showFieldDraftToaster}
|
|
2585
|
+
onDraftSubmit={(nextFieldConfig: Record<string, any>) => applyFieldConfigDraft(activeFieldConfigColumn.columnName, nextFieldConfig)}
|
|
2586
|
+
onClose={() => setFieldConfigColumnName(null)}
|
|
2587
|
+
availableFieldTypes={scalarFieldTypeOptions.map((option) => option.value)}
|
|
2588
|
+
forceShowTypeSelector
|
|
2589
|
+
selectorRequireContinue
|
|
2590
|
+
selectorContinueLabel="Next"
|
|
2591
|
+
selectorInitialFieldType={activeFieldConfigColumn.solidFieldType}
|
|
2592
|
+
disableIdentityEditingForExisting={false}
|
|
2593
|
+
submitLabel="Save field config"
|
|
2594
|
+
/>
|
|
2595
|
+
) : null}
|
|
2596
|
+
</SolidDialogBody>
|
|
2597
|
+
</SolidDialog>
|
|
1549
2598
|
|
|
1550
2599
|
<SolidDialog
|
|
1551
2600
|
open={settingsOpen}
|
|
@@ -1580,138 +2629,6 @@ export function DatasourceIntrospectionPage() {
|
|
|
1580
2629
|
</SolidButton>
|
|
1581
2630
|
</SolidDialogFooter>
|
|
1582
2631
|
</SolidDialog>
|
|
1583
|
-
|
|
1584
|
-
<SolidDialog
|
|
1585
|
-
open={drawerOpen}
|
|
1586
|
-
onOpenChange={setDrawerOpen}
|
|
1587
|
-
className="sdix-drawer"
|
|
1588
|
-
overlayClassName="sdix-drawer-overlay"
|
|
1589
|
-
dismissible
|
|
1590
|
-
>
|
|
1591
|
-
<SolidDialogHeader className="sdix-drawer-header">
|
|
1592
|
-
<div className="sdix-drawer-header__identity">
|
|
1593
|
-
<SolidDialogTitle>{selectedTable?.tableName || "Table details"}</SolidDialogTitle>
|
|
1594
|
-
<SolidDialogDescription className="sdix-drawer-header__meta">
|
|
1595
|
-
{selectedTable
|
|
1596
|
-
? `${selectedTable.schema || "default"} · ${selectedDatasourceRecord?.database || selectedDatasourceRecord?.displayName || "Datasource"}${selectedDatasourceRecord ? ` · ${providerLabel(selectedDatasourceRecord.type)}` : ""}`
|
|
1597
|
-
: "Review mapping details"}
|
|
1598
|
-
</SolidDialogDescription>
|
|
1599
|
-
</div>
|
|
1600
|
-
<SolidDialogClose />
|
|
1601
|
-
</SolidDialogHeader>
|
|
1602
|
-
<SolidDialogSeparator />
|
|
1603
|
-
<SolidDialogBody className="sdix-drawer-body">
|
|
1604
|
-
{!selectedTable ? (
|
|
1605
|
-
<div className="sdix-empty-state is-inline">
|
|
1606
|
-
<Sparkles size={22} />
|
|
1607
|
-
<p>Select a table row to review its structure.</p>
|
|
1608
|
-
</div>
|
|
1609
|
-
) : drawerLoadingError ? (
|
|
1610
|
-
<div className="sdix-error is-inline">
|
|
1611
|
-
<span>{readQueryError(drawerLoadingError)}</span>
|
|
1612
|
-
<SolidButton size="small" variant="outline" onClick={() => void refetchTableDetail()}>
|
|
1613
|
-
Retry
|
|
1614
|
-
</SolidButton>
|
|
1615
|
-
</div>
|
|
1616
|
-
) : isDrawerBlockingLoading ? (
|
|
1617
|
-
<div className="sdix-loading is-inline">
|
|
1618
|
-
<SolidSpinner size={24} />
|
|
1619
|
-
<p>Preparing column mapping preview...</p>
|
|
1620
|
-
</div>
|
|
1621
|
-
) : (
|
|
1622
|
-
<div className="sdix-drawer-stack">
|
|
1623
|
-
{drawerDraft && !drawerDraft.mapped && drawerStep === "edit" ? (
|
|
1624
|
-
<div className="sdix-detail-callout sdix-system-fields-callout">
|
|
1625
|
-
<h3>SolidX system fields are added later</h3>
|
|
1626
|
-
<p>
|
|
1627
|
-
This preview only lists the legacy table columns that already exist today.
|
|
1628
|
-
{" "}
|
|
1629
|
-
Framework system fields from the selected superclass, including the namespaced
|
|
1630
|
-
{" "}
|
|
1631
|
-
<code>ss_*</code>
|
|
1632
|
-
{" "}
|
|
1633
|
-
columns, are introduced when you save the mapping and then generate/apply the follow-up migrations.
|
|
1634
|
-
</p>
|
|
1635
|
-
</div>
|
|
1636
|
-
) : null}
|
|
1637
|
-
{drawerStep === "edit" ? (
|
|
1638
|
-
<SolidTabGroup
|
|
1639
|
-
tabs={drawerTabs}
|
|
1640
|
-
value={drawerTab}
|
|
1641
|
-
onValueChange={setDrawerTab}
|
|
1642
|
-
className="sdix-drawer-tabs"
|
|
1643
|
-
panelClassName="sdix-drawer-tab-panel"
|
|
1644
|
-
/>
|
|
1645
|
-
) : (
|
|
1646
|
-
<SolidTabGroup
|
|
1647
|
-
tabs={reviewTabs}
|
|
1648
|
-
value={reviewTab}
|
|
1649
|
-
onValueChange={setReviewTab}
|
|
1650
|
-
className="sdix-drawer-tabs"
|
|
1651
|
-
panelClassName="sdix-drawer-tab-panel"
|
|
1652
|
-
/>
|
|
1653
|
-
)}
|
|
1654
|
-
</div>
|
|
1655
|
-
)}
|
|
1656
|
-
</SolidDialogBody>
|
|
1657
|
-
{drawerDraft && !isDrawerBlockingLoading ? (
|
|
1658
|
-
<>
|
|
1659
|
-
<SolidDialogSeparator />
|
|
1660
|
-
<SolidDialogFooter className="sdix-drawer-footer">
|
|
1661
|
-
<div className="sdix-drawer-footer__meta">
|
|
1662
|
-
{drawerStep === "review" && mappingPreview ? (
|
|
1663
|
-
<span>
|
|
1664
|
-
Review the metadata update first, then save to apply
|
|
1665
|
-
{" "}
|
|
1666
|
-
<strong>{mappingPreview.summary.includedColumnNames.length}</strong>
|
|
1667
|
-
{" "}
|
|
1668
|
-
mapped columns
|
|
1669
|
-
{mappingPreview.summary.generatedSystemColumnCount
|
|
1670
|
-
? ` and ${mappingPreview.summary.generatedSystemColumnCount} SolidX system column migration changes`
|
|
1671
|
-
: ""}
|
|
1672
|
-
.
|
|
1673
|
-
</span>
|
|
1674
|
-
) : drawerDraft.mapped ? (
|
|
1675
|
-
<span>
|
|
1676
|
-
Editing mapped model
|
|
1677
|
-
{" "}
|
|
1678
|
-
<strong>{selectedTable?.mappedModelDisplayName || selectedTable?.mappedModelSingularName}</strong>
|
|
1679
|
-
{drawerDraft.mappedModuleLabel ? ` in ${drawerDraft.mappedModuleLabel}` : ""}
|
|
1680
|
-
</span>
|
|
1681
|
-
) : (
|
|
1682
|
-
<span>Saving will create the model metadata and write it to the module JSON.</span>
|
|
1683
|
-
)}
|
|
1684
|
-
</div>
|
|
1685
|
-
<div className="sdix-drawer-footer__actions">
|
|
1686
|
-
{drawerDraft.mapped && drawerDraft.modelId ? (
|
|
1687
|
-
<SolidButton size="small" variant="outline" leftIcon={<ExternalLink size={14} />} onClick={openMappedModelForm}>
|
|
1688
|
-
Open model form
|
|
1689
|
-
</SolidButton>
|
|
1690
|
-
) : null}
|
|
1691
|
-
{drawerStep === "review" ? (
|
|
1692
|
-
<>
|
|
1693
|
-
<SolidButton size="small" variant="outline" onClick={() => setDrawerStep("edit")}>
|
|
1694
|
-
Back to editing
|
|
1695
|
-
</SolidButton>
|
|
1696
|
-
<SolidButton size="small" leftIcon={<Save size={14} />} loading={isSavingMapping} onClick={() => void saveMapping()}>
|
|
1697
|
-
Save mapping
|
|
1698
|
-
</SolidButton>
|
|
1699
|
-
</>
|
|
1700
|
-
) : (
|
|
1701
|
-
<SolidButton
|
|
1702
|
-
size="small"
|
|
1703
|
-
leftIcon={<Save size={14} />}
|
|
1704
|
-
loading={isPreviewMappingLoading}
|
|
1705
|
-
onClick={() => void viewChanges()}
|
|
1706
|
-
>
|
|
1707
|
-
View Changes
|
|
1708
|
-
</SolidButton>
|
|
1709
|
-
)}
|
|
1710
|
-
</div>
|
|
1711
|
-
</SolidDialogFooter>
|
|
1712
|
-
</>
|
|
1713
|
-
) : null}
|
|
1714
|
-
</SolidDialog>
|
|
1715
2632
|
</>
|
|
1716
2633
|
)}
|
|
1717
2634
|
</div>
|