@e-llm-studio/instant-learning 1.3.3 → 1.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/ILMarketplaceWrapper.js +3 -0
  2. package/dist/ILMarketplaceWrapper.js.map +1 -1
  3. package/dist/cjs/ILMarketplaceWrapper.js +3 -0
  4. package/dist/cjs/ILMarketplaceWrapper.js.map +1 -1
  5. package/dist/cjs/features/IL-OTJ/ValueTypeWrappers/TeachMePlugin.js +5 -2
  6. package/dist/cjs/features/IL-OTJ/ValueTypeWrappers/TeachMePlugin.js.map +1 -1
  7. package/dist/cjs/features/IL-OTJ/_components/ChatComponents/ChatComponent.js +12 -4
  8. package/dist/cjs/features/IL-OTJ/_components/ChatComponents/ChatComponent.js.map +1 -1
  9. package/dist/cjs/features/IL-OTJ/_components/ChatComponents/NormalChat.js +13 -4
  10. package/dist/cjs/features/IL-OTJ/_components/ChatComponents/NormalChat.js.map +1 -1
  11. package/dist/cjs/features/IL-marketplace/PromoteFlow/EnvironmentConfigurationModal.js +41 -4
  12. package/dist/cjs/features/IL-marketplace/PromoteFlow/EnvironmentConfigurationModal.js.map +1 -1
  13. package/dist/cjs/features/IL-marketplace/PromoteFlow/PromoteFlow.js +444 -169
  14. package/dist/cjs/features/IL-marketplace/PromoteFlow/PromoteFlow.js.map +1 -1
  15. package/dist/cjs/features/IL-marketplace/PromoteFlow/PromoteFlow.module.css.js +2 -2
  16. package/dist/cjs/features/IL-marketplace/PromoteFlow/promote-flow.api.js +19 -2
  17. package/dist/cjs/features/IL-marketplace/PromoteFlow/promote-flow.api.js.map +1 -1
  18. package/dist/cjs/features/IL-marketplace/PromoteFlow/promote-flow.store.js +7 -0
  19. package/dist/cjs/features/IL-marketplace/PromoteFlow/promote-flow.store.js.map +1 -1
  20. package/dist/cjs/features/InstantLearning/InstantLearning.js +1 -0
  21. package/dist/cjs/features/InstantLearning/InstantLearning.js.map +1 -1
  22. package/dist/cjs/features/InstantLearning/_components/ChatComponent.js +3 -1
  23. package/dist/cjs/features/InstantLearning/_components/ChatComponent.js.map +1 -1
  24. package/dist/cjs/features/InstantLearning/_components/ContextualChatComponent.js +3 -1
  25. package/dist/cjs/features/InstantLearning/_components/ContextualChatComponent.js.map +1 -1
  26. package/dist/features/IL-OTJ/ValueTypeWrappers/TeachMePlugin.js +5 -2
  27. package/dist/features/IL-OTJ/ValueTypeWrappers/TeachMePlugin.js.map +1 -1
  28. package/dist/features/IL-OTJ/_components/ChatComponents/ChatComponent.js +12 -4
  29. package/dist/features/IL-OTJ/_components/ChatComponents/ChatComponent.js.map +1 -1
  30. package/dist/features/IL-OTJ/_components/ChatComponents/NormalChat.js +13 -4
  31. package/dist/features/IL-OTJ/_components/ChatComponents/NormalChat.js.map +1 -1
  32. package/dist/features/IL-marketplace/PromoteFlow/EnvironmentConfigurationModal.js +41 -4
  33. package/dist/features/IL-marketplace/PromoteFlow/EnvironmentConfigurationModal.js.map +1 -1
  34. package/dist/features/IL-marketplace/PromoteFlow/PromoteFlow.js +446 -171
  35. package/dist/features/IL-marketplace/PromoteFlow/PromoteFlow.js.map +1 -1
  36. package/dist/features/IL-marketplace/PromoteFlow/PromoteFlow.module.css.js +2 -2
  37. package/dist/features/IL-marketplace/PromoteFlow/promote-flow.api.js +17 -3
  38. package/dist/features/IL-marketplace/PromoteFlow/promote-flow.api.js.map +1 -1
  39. package/dist/features/IL-marketplace/PromoteFlow/promote-flow.store.js +7 -0
  40. package/dist/features/IL-marketplace/PromoteFlow/promote-flow.store.js.map +1 -1
  41. package/dist/features/InstantLearning/InstantLearning.js +1 -0
  42. package/dist/features/InstantLearning/InstantLearning.js.map +1 -1
  43. package/dist/features/InstantLearning/_components/ChatComponent.js +3 -1
  44. package/dist/features/InstantLearning/_components/ChatComponent.js.map +1 -1
  45. package/dist/features/InstantLearning/_components/ContextualChatComponent.js +3 -1
  46. package/dist/features/InstantLearning/_components/ContextualChatComponent.js.map +1 -1
  47. package/dist/types/src/ILMarketplaceWrapper.d.ts.map +1 -1
  48. package/dist/types/src/features/IL-OTJ/ValueTypeWrappers/TeachMePlugin.d.ts.map +1 -1
  49. package/dist/types/src/features/IL-OTJ/_components/ChatComponents/ChatComponent.d.ts.map +1 -1
  50. package/dist/types/src/features/IL-OTJ/_components/ChatComponents/NormalChat.d.ts.map +1 -1
  51. package/dist/types/src/features/IL-marketplace/PromoteFlow/EnvironmentConfigurationModal.d.ts.map +1 -1
  52. package/dist/types/src/features/IL-marketplace/PromoteFlow/PromoteFlow.d.ts.map +1 -1
  53. package/dist/types/src/features/IL-marketplace/PromoteFlow/promote-flow.api.d.ts +5 -2
  54. package/dist/types/src/features/IL-marketplace/PromoteFlow/promote-flow.api.d.ts.map +1 -1
  55. package/dist/types/src/features/IL-marketplace/PromoteFlow/promote-flow.interfaces.d.ts +26 -1
  56. package/dist/types/src/features/IL-marketplace/PromoteFlow/promote-flow.interfaces.d.ts.map +1 -1
  57. package/dist/types/src/features/IL-marketplace/PromoteFlow/promote-flow.store.d.ts +3 -1
  58. package/dist/types/src/features/IL-marketplace/PromoteFlow/promote-flow.store.d.ts.map +1 -1
  59. package/dist/types/src/features/InstantLearning/InstantLearning.d.ts.map +1 -1
  60. package/dist/types/src/features/InstantLearning/_components/ChatComponent.d.ts.map +1 -1
  61. package/dist/types/src/features/InstantLearning/_components/ContextualChatComponent.d.ts.map +1 -1
  62. package/package.json +4 -5
@@ -2,10 +2,10 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { useState, useMemo, useEffect } from 'react';
3
3
  import Tooltip, { tooltipClasses } from '@mui/material/Tooltip';
4
4
  import { styled } from '@mui/material/styles';
5
- import { ArrowLeft, ArrowUpRight, ArrowDownUp, Calendar, LoaderCircle, Bell, Copy, ExternalLink, ChevronDown, Settings2 } from 'lucide-react';
5
+ import { ArrowLeft, CircleCheck, Info, ArrowUpRight, ArrowDownUp, Calendar, LoaderCircle, Bell, Copy, ExternalLink, ChevronDown, Settings2 } from 'lucide-react';
6
6
  import { getGuidelineTitle, cx } from '../utils.js';
7
7
  import { EnvironmentConfigurationModal } from './EnvironmentConfigurationModal.js';
8
- import { getPromotionHistory, searchEnvironmentConfigs, getCWUser, getPromotionStatus, startPromotion, createEnvironmentConfig, schedulePromotion, triggerScheduledPromotion } from './promote-flow.api.js';
8
+ import { getSchemaPreview, searchEnvironmentConfigs, getCWUser, getPromotionStatus, getPromotionHistory, startPromotion, checkPromotionPreflight, createEnvironmentConfig, schedulePromotion, triggerScheduledPromotion, rollbackPromotion } from './promote-flow.api.js';
9
9
  import { usePromoteFlowStore } from './promote-flow.store.js';
10
10
  import { getPromotionResourceId, getEnvironmentDisplayName, getPermittedDestinationTiers, getLatestPromotionForEnvironmentPair, formatPromotionDate, EMPTY_ENVIRONMENT_CONFIGURATION, toCreateEnvironmentConfigRequest, toRegistryRecord } from './promote-flow.utils.js';
11
11
  import cssStyles from './PromoteFlow.module.css.js';
@@ -40,26 +40,21 @@ const ACCORDIONS = [
40
40
  {
41
41
  id: "database",
42
42
  title: "Database Details",
43
- comingSoon: true
44
- },
45
- {
46
- id: "knowledgebase",
47
- title: "Knowledgebase Details",
48
- comingSoon: true
43
+ comingSoon: false
49
44
  }
45
+ // {
46
+ // id: 'knowledgebase',
47
+ // title: 'Knowledgebase Details',
48
+ // comingSoon: true,
49
+ // },
50
50
  ];
51
- const TABLE_NAMES = [
52
- "document_feature_details",
53
- "document_feature_terminology",
54
- "document_feature_validation",
55
- "techo_document_feature",
56
- "document_feature_configuration",
57
- "document_feature_techme",
58
- "document_feature_schema",
59
- "document_feature_cognitive_compare"
60
- ];
51
+ const isMissingPromotionStatus = (error) => {
52
+ var _a;
53
+ if (error instanceof Error && error.message.includes("404")) return true;
54
+ return typeof error === "object" && error !== null && "response" in error && ((_a = error.response) == null ? void 0 : _a.status) === 404;
55
+ };
61
56
  function PromoteFlow({ guideline, onBack, onPromote, className }) {
62
- var _a, _b, _c, _d, _e;
57
+ var _a, _b, _c, _d, _e, _f, _g;
63
58
  const [openSections, setOpenSections] = useState({
64
59
  overview: true
65
60
  });
@@ -69,13 +64,24 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
69
64
  );
70
65
  const [promotedByName, setPromotedByName] = useState(null);
71
66
  const [promotionHistory, setPromotionHistory] = useState([]);
67
+ const [schemaPreview, setSchemaPreview] = useState({});
68
+ const [isLoadingSchemaPreview, setIsLoadingSchemaPreview] = useState(false);
72
69
  const [isLoadingHistory, setIsLoadingHistory] = useState(false);
73
70
  const [promotionConfigs, setPromotionConfigs] = useState([]);
74
71
  const [isLoadingConfigs, setIsLoadingConfigs] = useState(false);
75
72
  const [configSearchTier, setConfigSearchTier] = useState(null);
76
73
  const [submittingAction, setSubmittingAction] = useState(null);
74
+ const [historyActionTaskId, setHistoryActionTaskId] = useState(null);
75
+ const [isStatusPollingStopped, setIsStatusPollingStopped] = useState(false);
76
+ const [isInitialStatusLoading, setIsInitialStatusLoading] = useState(true);
77
+ const [isCheckingPreflight, setIsCheckingPreflight] = useState(false);
78
+ const [preflightResult, setPreflightResult] = useState(null);
77
79
  const sourceEnvironment = usePromoteFlowStore((state) => state.sourceEnvironment);
78
80
  const destinationEnvironment = usePromoteFlowStore((state) => state.destinationEnvironment);
81
+ const tenantSourceEnvironment = usePromoteFlowStore((state) => state.tenantSourceEnvironment);
82
+ const tenantDestinationEnvironment = usePromoteFlowStore(
83
+ (state) => state.tenantDestinationEnvironment
84
+ );
79
85
  const promotionTask = usePromoteFlowStore((state) => state.promotionTask);
80
86
  const promotionResourceId = usePromoteFlowStore((state) => state.promotionResourceId);
81
87
  const isSubmitting = usePromoteFlowStore((state) => state.isSubmitting);
@@ -94,9 +100,14 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
94
100
  const errorForGuideline = errorResourceId === guidelineResourceId ? error : null;
95
101
  const isSourceConfigured = Boolean(sourceEnvironment);
96
102
  const isDestinationConfigured = Boolean(destinationEnvironment);
97
- const canPromote = isSourceConfigured && isDestinationConfigured && !isSubmitting;
103
+ const canPromote = isSourceConfigured && isDestinationConfigured && !isSubmitting && !isInitialStatusLoading;
98
104
  const sourceEnvironmentLabel = getEnvironmentDisplayName(sourceEnvironment, "Dev");
99
105
  const destinationEnvironmentLabel = getEnvironmentDisplayName(destinationEnvironment, "QA");
106
+ const tenantSourceEnvironmentLabel = getEnvironmentDisplayName(tenantSourceEnvironment, "\u2014");
107
+ const tenantDestinationEnvironmentLabel = getEnvironmentDisplayName(
108
+ tenantDestinationEnvironment,
109
+ "\u2014"
110
+ );
100
111
  const isPromoting = Boolean(promotionTaskForGuideline);
101
112
  const canRetryPromotion = (promotionTaskForGuideline == null ? void 0 : promotionTaskForGuideline.status) === "FAILED";
102
113
  const sourceTier = (_b = sourceEnvironment == null ? void 0 : sourceEnvironment.env_tier) != null ? _b : "dev";
@@ -105,7 +116,7 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
105
116
  [sourceTier]
106
117
  );
107
118
  const permittedSourceTiers = ["dev", "qa", "prod", "external"];
108
- const selectedEnvironment = configuredEnvironment === "source" ? sourceEnvironment : destinationEnvironment;
119
+ const selectedEnvironment = configuredEnvironment === "source" ? sourceEnvironment : configuredEnvironment === "destination" ? destinationEnvironment : configuredEnvironment === "tenantSource" ? tenantSourceEnvironment : tenantDestinationEnvironment;
109
120
  const latestPromotionForPair = useMemo(
110
121
  () => getLatestPromotionForEnvironmentPair(
111
122
  promotionHistory,
@@ -114,6 +125,17 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
114
125
  ),
115
126
  [destinationEnvironment, promotionHistory, sourceEnvironment]
116
127
  );
128
+ const latestPromotionTaskId = useMemo(() => {
129
+ var _a2;
130
+ const latestPromotion = promotionHistory.reduce(
131
+ (latest, entry) => {
132
+ if (!latest) return entry;
133
+ return Date.parse(entry.updated_at) > Date.parse(latest.updated_at) ? entry : latest;
134
+ },
135
+ null
136
+ );
137
+ return (_a2 = latestPromotion == null ? void 0 : latestPromotion.promotion_task_id) != null ? _a2 : null;
138
+ }, [promotionHistory]);
117
139
  const promoteActionLabel = (latestPromotionForPair == null ? void 0 : latestPromotionForPair.status) === "SUCCESS" ? "Sync" : "Promote Now";
118
140
  const toggleSection = (id) => {
119
141
  setOpenSections((current) => ({ ...current, [id]: !current[id] }));
@@ -121,33 +143,77 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
121
143
  useEffect(() => {
122
144
  let isCurrent = true;
123
145
  setIsLoadingHistory(true);
124
- getPromotionHistory(guidelineResourceId).then((history) => {
125
- if (isCurrent) setPromotionHistory(history);
126
- }).catch((historyError) => {
146
+ setIsInitialStatusLoading(true);
147
+ setError(null);
148
+ setPromotionTask(null);
149
+ const refreshInitialPromotionState = async () => {
150
+ try {
151
+ await getPromotionStatus(guidelineResourceId);
152
+ } catch (statusError) {
153
+ if (isCurrent && !isMissingPromotionStatus(statusError)) {
154
+ setError(
155
+ statusError instanceof Error ? statusError.message : "Unable to refresh promotion status.",
156
+ guidelineResourceId
157
+ );
158
+ }
159
+ } finally {
160
+ if (isCurrent) setIsInitialStatusLoading(false);
161
+ }
162
+ try {
163
+ const history = await getPromotionHistory(guidelineResourceId);
164
+ if (isCurrent) setPromotionHistory(history);
165
+ } catch (historyError) {
166
+ if (isCurrent) {
167
+ setError(
168
+ historyError instanceof Error ? historyError.message : "Unable to load promotion history.",
169
+ guidelineResourceId
170
+ );
171
+ }
172
+ } finally {
173
+ if (isCurrent) setIsLoadingHistory(false);
174
+ }
175
+ };
176
+ void refreshInitialPromotionState();
177
+ return () => {
178
+ isCurrent = false;
179
+ };
180
+ }, [guidelineResourceId, setError, setPromotionTask]);
181
+ useEffect(() => {
182
+ let isCurrent = true;
183
+ setIsLoadingSchemaPreview(true);
184
+ getSchemaPreview(guidelineResourceId).then((preview) => {
185
+ if (!isCurrent) return;
186
+ setSchemaPreview(
187
+ Object.entries(preview != null ? preview : {}).reduce((groups, [key, value]) => {
188
+ if (value && Array.isArray(value.tables)) groups[key] = value;
189
+ return groups;
190
+ }, {})
191
+ );
192
+ }).catch((schemaError) => {
127
193
  if (isCurrent) {
128
- setError(
129
- historyError instanceof Error ? historyError.message : "Unable to load promotion history.",
130
- guidelineResourceId
131
- );
194
+ console.error("Failed to load schema preview", { resourceId: guidelineResourceId, schemaError });
195
+ setSchemaPreview({});
132
196
  }
133
197
  }).finally(() => {
134
- if (isCurrent) setIsLoadingHistory(false);
198
+ if (isCurrent) setIsLoadingSchemaPreview(false);
135
199
  });
136
200
  return () => {
137
201
  isCurrent = false;
138
202
  };
139
- }, [guidelineResourceId, setError]);
203
+ }, [guidelineResourceId]);
140
204
  useEffect(() => {
141
- var _a2, _b2;
205
+ var _a2, _b2, _c2, _d2;
142
206
  if (!configuredEnvironment) return;
143
- const defaultTier = configuredEnvironment === "source" ? (_a2 = sourceEnvironment == null ? void 0 : sourceEnvironment.env_tier) != null ? _a2 : "dev" : (_b2 = destinationEnvironment == null ? void 0 : destinationEnvironment.env_tier) != null ? _b2 : permittedDestinationTiers[0];
207
+ const selectedEnvironment2 = configuredEnvironment === "source" ? (_a2 = sourceEnvironment == null ? void 0 : sourceEnvironment.env_tier) != null ? _a2 : "dev" : configuredEnvironment === "destination" ? (_b2 = destinationEnvironment == null ? void 0 : destinationEnvironment.env_tier) != null ? _b2 : permittedDestinationTiers[0] : configuredEnvironment === "tenantSource" ? (_c2 = tenantSourceEnvironment == null ? void 0 : tenantSourceEnvironment.env_tier) != null ? _c2 : "dev" : (_d2 = tenantDestinationEnvironment == null ? void 0 : tenantDestinationEnvironment.env_tier) != null ? _d2 : permittedDestinationTiers[0];
208
+ const defaultTier = selectedEnvironment2;
144
209
  const envTier = configSearchTier != null ? configSearchTier : defaultTier;
145
210
  let isCurrent = true;
146
211
  setIsLoadingConfigs(true);
147
212
  setPromotionConfigs([]);
148
213
  searchEnvironmentConfigs({
149
214
  env_tier: envTier,
150
- db_type: envTier === "external" ? "EXTERNAL" : "INTERNAL"
215
+ db_type: envTier === "external" ? "EXTERNAL" : "INTERNAL",
216
+ is_tenant: configuredEnvironment === "tenantSource" || configuredEnvironment === "tenantDestination"
151
217
  }).then((configs) => {
152
218
  if (isCurrent) setPromotionConfigs(configs);
153
219
  }).catch(() => {
@@ -164,7 +230,9 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
164
230
  destinationEnvironment == null ? void 0 : destinationEnvironment.env_tier,
165
231
  guidelineResourceId,
166
232
  permittedDestinationTiers,
167
- sourceEnvironment == null ? void 0 : sourceEnvironment.env_tier
233
+ sourceEnvironment == null ? void 0 : sourceEnvironment.env_tier,
234
+ tenantDestinationEnvironment == null ? void 0 : tenantDestinationEnvironment.env_tier,
235
+ tenantSourceEnvironment == null ? void 0 : tenantSourceEnvironment.env_tier
168
236
  ]);
169
237
  useEffect(() => {
170
238
  const promotedByEmail = promotionTaskForGuideline == null ? void 0 : promotionTaskForGuideline.created_by;
@@ -187,11 +255,14 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
187
255
  };
188
256
  }, [promotionTaskForGuideline == null ? void 0 : promotionTaskForGuideline.created_by, promotionTaskForGuideline == null ? void 0 : promotionTaskForGuideline.promotion_task_id]);
189
257
  useEffect(() => {
190
- if (!promotionTaskForGuideline || promotionTaskForGuideline.status !== "MIGRATING") return;
258
+ if (!promotionTaskForGuideline || isStatusPollingStopped || !["MIGRATING", "ROLLBACK_IN_PROGRESS"].includes(promotionTaskForGuideline.status)) {
259
+ return;
260
+ }
191
261
  const pollStatus = async () => {
192
262
  try {
193
- setPromotionTask(await getPromotionStatus(promotionTaskForGuideline.promotion_task_id));
263
+ setPromotionTask(await getPromotionStatus(guidelineResourceId), guidelineResourceId);
194
264
  } catch (pollError) {
265
+ setIsStatusPollingStopped(true);
195
266
  setError(
196
267
  pollError instanceof Error ? pollError.message : "Unable to refresh promotion status.",
197
268
  guidelineResourceId
@@ -200,7 +271,7 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
200
271
  };
201
272
  const intervalId = window.setInterval(pollStatus, 3e4);
202
273
  return () => window.clearInterval(intervalId);
203
- }, [guidelineResourceId, promotionTaskForGuideline, setError, setPromotionTask]);
274
+ }, [guidelineResourceId, isStatusPollingStopped, promotionTaskForGuideline, setError, setPromotionTask]);
204
275
  const handlePromoteNow = async () => {
205
276
  if (!canPromote || !sourceEnvironment || !destinationEnvironment) return;
206
277
  setSubmitting(true);
@@ -212,9 +283,18 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
212
283
  resource_type: "PACKAGE",
213
284
  target_version: "latest",
214
285
  global_source_env_id: sourceEnvironment.env_id,
215
- global_target_env_id: destinationEnvironment.env_id
286
+ global_target_env_id: destinationEnvironment.env_id,
287
+ ...tenantSourceEnvironment && tenantDestinationEnvironment ? {
288
+ tenant_source_env_id: tenantSourceEnvironment.env_id,
289
+ tenant_target_env_id: tenantDestinationEnvironment.env_id
290
+ } : {}
216
291
  });
292
+ setIsStatusPollingStopped(false);
217
293
  setPromotionTask(task, guidelineResourceId);
294
+ try {
295
+ setPromotionTask(await getPromotionStatus(guidelineResourceId), guidelineResourceId);
296
+ } catch (e) {
297
+ }
218
298
  onPromote == null ? void 0 : onPromote(guideline);
219
299
  } catch (promotionError) {
220
300
  setError(
@@ -226,15 +306,53 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
226
306
  setSubmittingAction(null);
227
307
  }
228
308
  };
309
+ const handlePreflightCheck = async () => {
310
+ if (!sourceEnvironment || !destinationEnvironment || isCheckingPreflight) return;
311
+ setIsCheckingPreflight(true);
312
+ setPreflightResult(null);
313
+ setError(null);
314
+ try {
315
+ const result = await checkPromotionPreflight({
316
+ resource_id: guidelineResourceId,
317
+ resource_type: "PACKAGE",
318
+ target_version: "latest",
319
+ global_source_env_id: sourceEnvironment.env_id,
320
+ global_target_env_id: destinationEnvironment.env_id,
321
+ ...tenantSourceEnvironment && tenantDestinationEnvironment ? {
322
+ tenant_source_env_id: tenantSourceEnvironment.env_id,
323
+ tenant_target_env_id: tenantDestinationEnvironment.env_id
324
+ } : {}
325
+ });
326
+ setPreflightResult({
327
+ ready: result.ready,
328
+ itemsChecked: result.items_checked,
329
+ itemsReady: result.items_ready
330
+ });
331
+ } catch (preflightError) {
332
+ setError(
333
+ preflightError instanceof Error ? preflightError.message : "Unable to complete pre-flight check.",
334
+ guidelineResourceId
335
+ );
336
+ } finally {
337
+ setIsCheckingPreflight(false);
338
+ }
339
+ };
229
340
  const handleSaveEnvironment = async (environment, configuration) => {
230
- const response = await createEnvironmentConfig(toCreateEnvironmentConfigRequest(configuration));
231
- setEnvironment(environment, toRegistryRecord(response.env_id, configuration));
341
+ const isTenantEnvironment = environment === "tenantSource" || environment === "tenantDestination";
342
+ const configurationToSave = {
343
+ ...configuration,
344
+ is_tenant_db: isTenantEnvironment
345
+ };
346
+ const response = await createEnvironmentConfig(
347
+ toCreateEnvironmentConfigRequest(configurationToSave)
348
+ );
349
+ setEnvironment(environment, toRegistryRecord(response.env_id, configurationToSave));
232
350
  };
233
351
  const openEnvironmentConfiguration = (environment) => {
234
352
  var _a2;
235
- const selected = environment === "source" ? sourceEnvironment : destinationEnvironment;
353
+ const selected = environment === "source" ? sourceEnvironment : environment === "destination" ? destinationEnvironment : environment === "tenantSource" ? tenantSourceEnvironment : tenantDestinationEnvironment;
236
354
  setConfigSearchTier(
237
- (_a2 = selected == null ? void 0 : selected.env_tier) != null ? _a2 : environment === "source" ? "dev" : permittedDestinationTiers[0]
355
+ (_a2 = selected == null ? void 0 : selected.env_tier) != null ? _a2 : environment === "source" || environment === "tenantSource" ? "dev" : permittedDestinationTiers[0]
238
356
  );
239
357
  setConfiguredEnvironment(environment);
240
358
  };
@@ -250,7 +368,11 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
250
368
  resource_type: "PACKAGE",
251
369
  target_version: "latest",
252
370
  global_source_env_id: sourceEnvironment.env_id,
253
- global_target_env_id: destinationEnvironment.env_id
371
+ global_target_env_id: destinationEnvironment.env_id,
372
+ ...tenantSourceEnvironment && tenantDestinationEnvironment ? {
373
+ tenant_source_env_id: tenantSourceEnvironment.env_id,
374
+ tenant_target_env_id: tenantDestinationEnvironment.env_id
375
+ } : {}
254
376
  });
255
377
  const now = (/* @__PURE__ */ new Date()).toISOString();
256
378
  setPromotionTask(
@@ -285,7 +407,12 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
285
407
  setError(null);
286
408
  try {
287
409
  const task = await triggerScheduledPromotion(triggerUrl);
410
+ setIsStatusPollingStopped(false);
288
411
  setPromotionTask(task, guidelineResourceId);
412
+ try {
413
+ setPromotionTask(await getPromotionStatus(guidelineResourceId), guidelineResourceId);
414
+ } catch (e) {
415
+ }
289
416
  onPromote == null ? void 0 : onPromote(guideline);
290
417
  } catch (promotionError) {
291
418
  setError(
@@ -297,7 +424,45 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
297
424
  setSubmittingAction(null);
298
425
  }
299
426
  };
427
+ const handleViewPromotionDetails = async (promotionTaskId) => {
428
+ setHistoryActionTaskId(promotionTaskId);
429
+ setError(null);
430
+ try {
431
+ setIsStatusPollingStopped(false);
432
+ setPromotionTask(await getPromotionStatus(guidelineResourceId), guidelineResourceId);
433
+ } catch (statusError) {
434
+ setError(
435
+ statusError instanceof Error ? statusError.message : "Unable to load promotion details.",
436
+ guidelineResourceId
437
+ );
438
+ } finally {
439
+ setHistoryActionTaskId(null);
440
+ }
441
+ };
442
+ const handleRollbackPromotion = async (promotionTaskId) => {
443
+ setHistoryActionTaskId(promotionTaskId);
444
+ setError(null);
445
+ try {
446
+ const task = await rollbackPromotion(promotionTaskId);
447
+ setIsStatusPollingStopped(false);
448
+ setPromotionTask(task, guidelineResourceId);
449
+ } catch (rollbackError) {
450
+ setError(
451
+ rollbackError instanceof Error ? rollbackError.message : "Unable to start rollback.",
452
+ guidelineResourceId
453
+ );
454
+ } finally {
455
+ setHistoryActionTaskId(null);
456
+ }
457
+ };
300
458
  const handleBack = () => {
459
+ if (promotionTaskForGuideline) {
460
+ setPromotionTask(null);
461
+ return;
462
+ }
463
+ handleGoToMarketplace();
464
+ };
465
+ const handleGoToMarketplace = () => {
301
466
  if ((promotionTaskForGuideline == null ? void 0 : promotionTaskForGuideline.status) === "FAILED") {
302
467
  resetPromotionSession();
303
468
  }
@@ -318,63 +483,90 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
318
483
  };
319
484
  const renderAccordionBody = (id) => {
320
485
  if (id === "overview") {
321
- return /* @__PURE__ */ jsxs("div", { className: cssStyles.overviewPanel, children: [
322
- /* @__PURE__ */ jsxs("div", { className: cssStyles.overviewGrid, children: [
323
- /* @__PURE__ */ jsxs("div", { children: [
324
- /* @__PURE__ */ jsxs("div", { className: cssStyles.environmentHeading, children: [
325
- /* @__PURE__ */ jsx("span", { className: cssStyles.metaLabel, children: "Source Environment" }),
326
- /* @__PURE__ */ jsx(
327
- "button",
328
- {
329
- type: "button",
330
- className: cx(
331
- cssStyles.configureEnvironmentButton,
332
- !isSourceConfigured && cssStyles.configureEnvironmentButtonRequired
333
- ),
334
- "aria-label": "Configure source environment",
335
- "data-tooltip": "Configure source environment",
336
- onClick: () => openEnvironmentConfiguration("source"),
337
- children: /* @__PURE__ */ jsx(Settings2, { "aria-hidden": "true", strokeWidth: 2 })
338
- }
339
- )
340
- ] }),
341
- /* @__PURE__ */ jsx("div", { className: cssStyles.metaValue, children: sourceEnvironmentLabel })
486
+ return /* @__PURE__ */ jsx("div", { className: cssStyles.overviewPanel, children: /* @__PURE__ */ jsxs("div", { className: cssStyles.overviewGrid, children: [
487
+ /* @__PURE__ */ jsxs("div", { children: [
488
+ /* @__PURE__ */ jsxs("div", { className: cssStyles.environmentHeading, children: [
489
+ /* @__PURE__ */ jsx("span", { className: cssStyles.metaLabel, children: "Source Environment" }),
490
+ /* @__PURE__ */ jsx(
491
+ "button",
492
+ {
493
+ type: "button",
494
+ className: cx(
495
+ cssStyles.configureEnvironmentButton,
496
+ !isSourceConfigured && cssStyles.configureEnvironmentButtonRequired
497
+ ),
498
+ "aria-label": "Configure source environment",
499
+ "data-tooltip": "Configure source environment",
500
+ onClick: () => openEnvironmentConfiguration("source"),
501
+ children: /* @__PURE__ */ jsx(Settings2, { "aria-hidden": "true", strokeWidth: 2 })
502
+ }
503
+ )
342
504
  ] }),
343
- /* @__PURE__ */ jsxs("div", { children: [
344
- /* @__PURE__ */ jsxs("div", { className: cssStyles.environmentHeading, children: [
345
- /* @__PURE__ */ jsx("span", { className: cssStyles.metaLabel, children: "Destination Environment" }),
346
- /* @__PURE__ */ jsx(
347
- "button",
348
- {
349
- type: "button",
350
- className: cx(
351
- cssStyles.configureEnvironmentButton,
352
- !isDestinationConfigured && cssStyles.configureEnvironmentButtonRequired
353
- ),
354
- "aria-label": "Configure destination environment",
355
- "data-tooltip": "Configure destination environment",
356
- onClick: () => openEnvironmentConfiguration("destination"),
357
- children: /* @__PURE__ */ jsx(Settings2, { "aria-hidden": "true", strokeWidth: 2 })
358
- }
359
- )
360
- ] }),
361
- /* @__PURE__ */ jsx("div", { className: cssStyles.metaValue, children: destinationEnvironmentLabel })
505
+ /* @__PURE__ */ jsx("div", { className: cssStyles.metaValue, children: sourceEnvironmentLabel })
506
+ ] }),
507
+ /* @__PURE__ */ jsxs("div", { children: [
508
+ /* @__PURE__ */ jsxs("div", { className: cssStyles.environmentHeading, children: [
509
+ /* @__PURE__ */ jsx("span", { className: cssStyles.metaLabel, children: "Destination Environment" }),
510
+ /* @__PURE__ */ jsx(
511
+ "button",
512
+ {
513
+ type: "button",
514
+ className: cx(
515
+ cssStyles.configureEnvironmentButton,
516
+ !isDestinationConfigured && cssStyles.configureEnvironmentButtonRequired
517
+ ),
518
+ "aria-label": "Configure destination environment",
519
+ "data-tooltip": "Configure destination environment",
520
+ onClick: () => openEnvironmentConfiguration("destination"),
521
+ children: /* @__PURE__ */ jsx(Settings2, { "aria-hidden": "true", strokeWidth: 2 })
522
+ }
523
+ )
362
524
  ] }),
363
- /* @__PURE__ */ jsxs("div", { children: [
364
- /* @__PURE__ */ jsx("div", { className: cssStyles.metaLabel, children: "ILKB status" }),
365
- /* @__PURE__ */ jsx("div", { className: cssStyles.metaValue, children: statusLabel })
525
+ /* @__PURE__ */ jsx("div", { className: cssStyles.metaValue, children: destinationEnvironmentLabel })
526
+ ] }),
527
+ /* @__PURE__ */ jsxs("div", { children: [
528
+ /* @__PURE__ */ jsx("div", { className: cssStyles.metaLabel, children: "ILKB status" }),
529
+ /* @__PURE__ */ jsx("div", { className: cssStyles.metaValue, children: statusLabel })
530
+ ] }),
531
+ /* @__PURE__ */ jsxs("div", { children: [
532
+ /* @__PURE__ */ jsx("div", { className: cssStyles.metaLabel, children: "Version" }),
533
+ /* @__PURE__ */ jsx("span", { className: cssStyles.versionPill, children: versionLabel })
534
+ ] }),
535
+ /* @__PURE__ */ jsxs("div", { children: [
536
+ /* @__PURE__ */ jsxs("div", { className: cssStyles.environmentHeading, children: [
537
+ /* @__PURE__ */ jsx("span", { className: cssStyles.metaLabel, children: "Tenant Source Environment (optional)" }),
538
+ /* @__PURE__ */ jsx(
539
+ "button",
540
+ {
541
+ type: "button",
542
+ className: cssStyles.configureEnvironmentButton,
543
+ "aria-label": "Configure tenant source environment",
544
+ "data-tooltip": "Configure tenant source environment",
545
+ onClick: () => openEnvironmentConfiguration("tenantSource"),
546
+ children: /* @__PURE__ */ jsx(Settings2, { "aria-hidden": "true", strokeWidth: 2 })
547
+ }
548
+ )
366
549
  ] }),
367
- /* @__PURE__ */ jsxs("div", { children: [
368
- /* @__PURE__ */ jsx("div", { className: cssStyles.metaLabel, children: "Version" }),
369
- /* @__PURE__ */ jsx("span", { className: cssStyles.versionPill, children: versionLabel })
370
- ] })
550
+ /* @__PURE__ */ jsx("div", { className: cssStyles.metaValue, children: tenantSourceEnvironmentLabel })
371
551
  ] }),
372
552
  /* @__PURE__ */ jsxs("div", { children: [
373
- /* @__PURE__ */ jsx("div", { className: cssStyles.metaLabel, children: "Current URL" }),
374
- "Coming soon.",
375
- /* @__PURE__ */ jsx(ExternalLink, { className: cssStyles.urlIcon, strokeWidth: 2 })
553
+ /* @__PURE__ */ jsxs("div", { className: cssStyles.environmentHeading, children: [
554
+ /* @__PURE__ */ jsx("span", { className: cssStyles.metaLabel, children: "Tenant Database Environment (optional)" }),
555
+ /* @__PURE__ */ jsx(
556
+ "button",
557
+ {
558
+ type: "button",
559
+ className: cssStyles.configureEnvironmentButton,
560
+ "aria-label": "Configure tenant database environment",
561
+ "data-tooltip": "Configure tenant database environment",
562
+ onClick: () => openEnvironmentConfiguration("tenantDestination"),
563
+ children: /* @__PURE__ */ jsx(Settings2, { "aria-hidden": "true", strokeWidth: 2 })
564
+ }
565
+ )
566
+ ] }),
567
+ /* @__PURE__ */ jsx("div", { className: cssStyles.metaValue, children: tenantDestinationEnvironmentLabel })
376
568
  ] })
377
- ] });
569
+ ] }) });
378
570
  }
379
571
  if (id === "history") {
380
572
  return /* @__PURE__ */ jsx("div", { className: cssStyles.historyPanel, children: /* @__PURE__ */ jsx("div", { className: cssStyles.historyTableWrapper, children: /* @__PURE__ */ jsxs("table", { className: cssStyles.historyTable, children: [
@@ -387,37 +579,55 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
387
579
  ] }) }),
388
580
  /* @__PURE__ */ jsx("tbody", { children: isLoadingHistory ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 5, children: "Loading promotion history\u2026" }) }) : promotionHistory.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 5, children: "No promotion history yet." }) }) : promotionHistory.map((row) => {
389
581
  var _a2, _b2;
582
+ const canRollback = row.status === "SUCCESS" && row.promotion_task_id === latestPromotionTaskId;
390
583
  return /* @__PURE__ */ jsxs("tr", { children: [
391
584
  /* @__PURE__ */ jsx("td", { children: row.promotion_task_id }),
392
585
  /* @__PURE__ */ jsx("td", { children: (_a2 = row.created_by) != null ? _a2 : "\u2014" }),
393
586
  /* @__PURE__ */ jsx("td", { children: (_b2 = row.error_summary) != null ? _b2 : "No errors." }),
394
587
  /* @__PURE__ */ jsx("td", { children: formatPromotionDate(row.updated_at) }),
395
- /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", { className: cssStyles.statusPill, children: row.status }) })
588
+ /* @__PURE__ */ jsxs("td", { children: [
589
+ /* @__PURE__ */ jsx("span", { className: cssStyles.statusPill, children: row.status }),
590
+ /* @__PURE__ */ jsx(
591
+ "button",
592
+ {
593
+ type: "button",
594
+ className: cx(
595
+ cssStyles.tableActionButton,
596
+ canRollback && cssStyles.tableActionButtonDanger
597
+ ),
598
+ onClick: () => canRollback ? handleRollbackPromotion(row.promotion_task_id) : handleViewPromotionDetails(row.promotion_task_id),
599
+ disabled: isInitialStatusLoading || historyActionTaskId !== null,
600
+ children: historyActionTaskId === row.promotion_task_id ? canRollback ? "Rolling back\u2026" : "Loading\u2026" : canRollback ? "Rollback" : "View details"
601
+ }
602
+ )
603
+ ] })
396
604
  ] }, row.promotion_task_id);
397
605
  }) })
398
606
  ] }) }) });
399
607
  }
400
608
  if (id === "database") {
401
- return /* @__PURE__ */ jsxs("div", { className: cssStyles.databaseGrid, children: [
402
- /* @__PURE__ */ jsxs("div", { className: cssStyles.databaseCard, children: [
403
- /* @__PURE__ */ jsx("h3", { children: "Source Database : il-otj-35" }),
404
- /* @__PURE__ */ jsxs("p", { children: [
405
- /* @__PURE__ */ jsx(ChevronDown, { className: cssStyles.inlineChevron, strokeWidth: 2 }),
406
- "Tables (12)"
609
+ if (isLoadingSchemaPreview) return /* @__PURE__ */ jsx("div", { className: cssStyles.emptyBody, children: "Loading database details\u2026" });
610
+ const groups = Object.entries(schemaPreview);
611
+ if (groups.length === 0) return /* @__PURE__ */ jsx("div", { className: cssStyles.emptyBody, children: "No database details available." });
612
+ return /* @__PURE__ */ jsx("div", { className: cssStyles.databaseGrid, children: groups.flatMap(
613
+ ([groupName, group]) => ["Source", "Destination"].map((environment) => /* @__PURE__ */ jsxs("div", { className: cssStyles.databaseCard, children: [
614
+ /* @__PURE__ */ jsxs("h3", { children: [
615
+ environment,
616
+ " Database: ",
617
+ group.database_name
407
618
  ] }),
408
- /* @__PURE__ */ jsx("ol", { className: cssStyles.tableList, children: TABLE_NAMES.map((name) => /* @__PURE__ */ jsx("li", { children: name }, name)) }),
409
- /* @__PURE__ */ jsx("button", { type: "button", className: cssStyles.moreTablesButton, children: "+4 more" })
410
- ] }),
411
- /* @__PURE__ */ jsxs("div", { className: cssStyles.databaseCard, children: [
412
- /* @__PURE__ */ jsx("h3", { children: "Destination Database : il-otj-35" }),
413
619
  /* @__PURE__ */ jsxs("p", { children: [
414
620
  /* @__PURE__ */ jsx(ChevronDown, { className: cssStyles.inlineChevron, strokeWidth: 2 }),
415
- "Tables (12)"
621
+ groupName,
622
+ " \xB7 ",
623
+ group.schema,
624
+ " \xB7 Tables (",
625
+ group.tables.length,
626
+ ")"
416
627
  ] }),
417
- /* @__PURE__ */ jsx("ol", { className: cssStyles.tableList, children: TABLE_NAMES.map((name) => /* @__PURE__ */ jsx("li", { children: name }, name)) }),
418
- /* @__PURE__ */ jsx("button", { type: "button", className: cssStyles.moreTablesButton, children: "+4 more" })
419
- ] })
420
- ] });
628
+ /* @__PURE__ */ jsx("ol", { className: cssStyles.tableList, children: group.tables.map((name) => /* @__PURE__ */ jsx("li", { children: name }, `${groupName}-${environment}-${name}`)) })
629
+ ] }, `${groupName}-${environment}`))
630
+ ) });
421
631
  }
422
632
  return /* @__PURE__ */ jsx("div", { className: cssStyles.emptyBody, children: "No details available yet." });
423
633
  };
@@ -431,61 +641,108 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
431
641
  ] }),
432
642
  /* @__PURE__ */ jsx("h1", { className: cssStyles.title, children: title })
433
643
  ] }),
434
- /* @__PURE__ */ jsx("div", { className: cssStyles.ctaRow, children: isPromoting ? /* @__PURE__ */ jsxs(Fragment, { children: [
435
- canRetryPromotion && /* @__PURE__ */ jsxs(
436
- "button",
437
- {
438
- type: "button",
439
- className: cssStyles.primaryButton,
440
- onClick: handlePromoteNow,
441
- disabled: !canPromote,
442
- children: [
443
- /* @__PURE__ */ jsx(ArrowUpRight, { className: cssStyles.buttonIcon, strokeWidth: 2.2 }),
444
- isSubmitting ? "Starting promotion\u2026" : promoteActionLabel
445
- ]
446
- }
447
- ),
448
- /* @__PURE__ */ jsxs("button", { type: "button", className: cssStyles.primaryButton, onClick: handleBack, children: [
449
- /* @__PURE__ */ jsx(ArrowDownUp, { className: cssStyles.buttonIcon, strokeWidth: 2.2 }),
450
- "Go to Marketplace"
644
+ /* @__PURE__ */ jsxs("div", { className: cssStyles.ctaRow, children: [
645
+ /* @__PURE__ */ jsxs("div", { className: cssStyles.preflightAction, children: [
646
+ /* @__PURE__ */ jsx(
647
+ StyledPromoteTooltip,
648
+ {
649
+ title: canPromote ? "Optionally check that this migration is ready before promoting." : "Configure both source and destination environments first.",
650
+ placement: "top",
651
+ arrow: true,
652
+ children: /* @__PURE__ */ jsxs(
653
+ "button",
654
+ {
655
+ type: "button",
656
+ className: cssStyles.secondaryButton,
657
+ onClick: handlePreflightCheck,
658
+ disabled: !canPromote || isCheckingPreflight,
659
+ children: [
660
+ /* @__PURE__ */ jsx(CircleCheck, { className: cssStyles.buttonIcon, strokeWidth: 2.2 }),
661
+ isCheckingPreflight ? "Checking readiness\u2026" : "Check promotion readiness"
662
+ ]
663
+ }
664
+ )
665
+ }
666
+ ),
667
+ /* @__PURE__ */ jsx(
668
+ StyledPromoteTooltip,
669
+ {
670
+ title: "Runs a safe validation of the selected source and destination environments. It does not start a migration.",
671
+ placement: "top",
672
+ arrow: true,
673
+ children: /* @__PURE__ */ jsx("span", { className: cssStyles.preflightInfo, "aria-label": "About the pre-flight check", children: /* @__PURE__ */ jsx(Info, { className: cssStyles.helpIcon, strokeWidth: 2.2, "aria-hidden": "true" }) })
674
+ }
675
+ )
676
+ ] }),
677
+ isPromoting ? /* @__PURE__ */ jsxs(Fragment, { children: [
678
+ canRetryPromotion && /* @__PURE__ */ jsxs(
679
+ "button",
680
+ {
681
+ type: "button",
682
+ className: cssStyles.primaryButton,
683
+ onClick: handlePromoteNow,
684
+ disabled: !canPromote,
685
+ children: [
686
+ /* @__PURE__ */ jsx(ArrowUpRight, { className: cssStyles.buttonIcon, strokeWidth: 2.2 }),
687
+ isSubmitting ? "Starting promotion\u2026" : promoteActionLabel
688
+ ]
689
+ }
690
+ ),
691
+ /* @__PURE__ */ jsxs(
692
+ "button",
693
+ {
694
+ type: "button",
695
+ className: cssStyles.primaryButton,
696
+ onClick: handleGoToMarketplace,
697
+ children: [
698
+ /* @__PURE__ */ jsx(ArrowDownUp, { className: cssStyles.buttonIcon, strokeWidth: 2.2 }),
699
+ "Go to Marketplace"
700
+ ]
701
+ }
702
+ )
703
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
704
+ submittingAction !== "now" && /* @__PURE__ */ jsxs(
705
+ "button",
706
+ {
707
+ type: "button",
708
+ className: cssStyles.secondaryButton,
709
+ onClick: handlePromoteLater,
710
+ disabled: !canPromote,
711
+ children: [
712
+ /* @__PURE__ */ jsx(Calendar, { className: cssStyles.buttonIcon, strokeWidth: 2.2 }),
713
+ submittingAction === "later" ? "Scheduling\u2026" : "Promote later"
714
+ ]
715
+ }
716
+ ),
717
+ submittingAction !== "later" && /* @__PURE__ */ jsx(
718
+ StyledPromoteTooltip,
719
+ {
720
+ title: canPromote ? "" : "Configure both source and destination environments first.",
721
+ disableHoverListener: canPromote,
722
+ placement: "top",
723
+ arrow: true,
724
+ children: /* @__PURE__ */ jsxs(
725
+ "button",
726
+ {
727
+ type: "button",
728
+ className: cssStyles.primaryButton,
729
+ onClick: handlePromoteNow,
730
+ disabled: !canPromote,
731
+ children: [
732
+ /* @__PURE__ */ jsx(ArrowUpRight, { className: cssStyles.buttonIcon, strokeWidth: 2.2 }),
733
+ submittingAction === "now" ? "Starting promotion\u2026" : promoteActionLabel
734
+ ]
735
+ }
736
+ )
737
+ }
738
+ )
451
739
  ] })
452
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
453
- submittingAction !== "now" && /* @__PURE__ */ jsxs(
454
- "button",
455
- {
456
- type: "button",
457
- className: cssStyles.secondaryButton,
458
- onClick: handlePromoteLater,
459
- disabled: !canPromote,
460
- children: [
461
- /* @__PURE__ */ jsx(Calendar, { className: cssStyles.buttonIcon, strokeWidth: 2.2 }),
462
- submittingAction === "later" ? "Scheduling\u2026" : "Promote later"
463
- ]
464
- }
465
- ),
466
- submittingAction !== "later" && /* @__PURE__ */ jsx(
467
- StyledPromoteTooltip,
468
- {
469
- title: canPromote ? "" : "Configure both source and destination environments first.",
470
- disableHoverListener: canPromote,
471
- placement: "top",
472
- arrow: true,
473
- children: /* @__PURE__ */ jsxs(
474
- "button",
475
- {
476
- type: "button",
477
- className: cssStyles.primaryButton,
478
- onClick: handlePromoteNow,
479
- disabled: !canPromote,
480
- children: [
481
- /* @__PURE__ */ jsx(ArrowUpRight, { className: cssStyles.buttonIcon, strokeWidth: 2.2 }),
482
- submittingAction === "now" ? "Starting promotion\u2026" : promoteActionLabel
483
- ]
484
- }
485
- )
486
- }
487
- )
488
- ] }) })
740
+ ] })
741
+ ] }),
742
+ preflightResult && /* @__PURE__ */ jsxs("div", { className: cssStyles.preflightSuccess, role: "status", children: [
743
+ "Pre-flight check completed successfully.",
744
+ " ",
745
+ preflightResult.ready ? `Migration is ready (${preflightResult.itemsReady}/${preflightResult.itemsChecked} checks passed).` : `Migration readiness requires attention (${preflightResult.itemsReady}/${preflightResult.itemsChecked} checks passed).`
489
746
  ] }),
490
747
  isPromoting ? /* @__PURE__ */ jsxs("div", { className: cssStyles.promotionStatusBox, children: [
491
748
  /* @__PURE__ */ jsx("div", { className: cssStyles.promotionStatusRow, children: /* @__PURE__ */ jsxs("div", { className: cssStyles.promotionStatusRowHeader, children: [
@@ -540,6 +797,23 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
540
797
  ] })
541
798
  ] })
542
799
  ] }),
800
+ (promotionTaskForGuideline == null ? void 0 : promotionTaskForGuideline.view_detail_url) && /* @__PURE__ */ jsxs("div", { className: cssStyles.promotionDetailsLink, children: [
801
+ /* @__PURE__ */ jsx("div", { className: cssStyles.metaLabel, children: "Promotion details" }),
802
+ /* @__PURE__ */ jsxs(
803
+ "a",
804
+ {
805
+ className: cssStyles.urlLink,
806
+ href: promotionTaskForGuideline.view_detail_url,
807
+ target: "_blank",
808
+ rel: "noreferrer",
809
+ title: promotionTaskForGuideline.view_detail_url,
810
+ children: [
811
+ /* @__PURE__ */ jsx("span", { children: promotionTaskForGuideline.view_detail_url }),
812
+ /* @__PURE__ */ jsx(ExternalLink, { className: cssStyles.urlIcon, strokeWidth: 2 })
813
+ ]
814
+ }
815
+ )
816
+ ] }),
543
817
  (promotionTaskForGuideline == null ? void 0 : promotionTaskForGuideline.status) === "SCHEDULED" && promotionTaskForGuideline.trigger_url && /* @__PURE__ */ jsxs("div", { className: cssStyles.scheduledTrigger, children: [
544
818
  /* @__PURE__ */ jsxs("div", { children: [
545
819
  /* @__PURE__ */ jsx("div", { className: cssStyles.metaLabel, children: "Scheduled promotion URL" }),
@@ -549,7 +823,7 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
549
823
  type: "button",
550
824
  className: cssStyles.scheduledTriggerUrl,
551
825
  onClick: handleTriggerScheduledPromotion,
552
- disabled: submittingAction === "trigger",
826
+ disabled: isInitialStatusLoading || submittingAction === "trigger",
553
827
  title: promotionTaskForGuideline.trigger_url,
554
828
  children: [
555
829
  /* @__PURE__ */ jsx("span", { children: promotionTaskForGuideline.trigger_url }),
@@ -564,7 +838,7 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
564
838
  type: "button",
565
839
  className: cssStyles.primaryButton,
566
840
  onClick: handleTriggerScheduledPromotion,
567
- disabled: submittingAction === "trigger",
841
+ disabled: isInitialStatusLoading || submittingAction === "trigger",
568
842
  children: [
569
843
  /* @__PURE__ */ jsx(ArrowUpRight, { className: cssStyles.buttonIcon, strokeWidth: 2.2 }),
570
844
  submittingAction === "trigger" ? "Starting promotion\u2026" : "Start promotion"
@@ -624,14 +898,15 @@ function PromoteFlow({ guideline, onBack, onPromote, className }) {
624
898
  is_tenant_db: selectedEnvironment.is_tenant_db
625
899
  } : {
626
900
  ...EMPTY_ENVIRONMENT_CONFIGURATION,
627
- env_tier: configuredEnvironment === "source" ? "dev" : permittedDestinationTiers[0],
628
- db_type: configuredEnvironment === "destination" && permittedDestinationTiers[0] === "external" ? "EXTERNAL" : "INTERNAL"
901
+ env_tier: configuredEnvironment === "source" || configuredEnvironment === "tenantSource" ? "dev" : permittedDestinationTiers[0],
902
+ db_type: (configuredEnvironment === "destination" || configuredEnvironment === "tenantDestination") && permittedDestinationTiers[0] === "external" ? "EXTERNAL" : "INTERNAL",
903
+ is_tenant_db: configuredEnvironment === "tenantSource" || configuredEnvironment === "tenantDestination"
629
904
  },
630
- permittedTiers: configuredEnvironment === "source" ? permittedSourceTiers : permittedDestinationTiers,
905
+ permittedTiers: configuredEnvironment === "source" || configuredEnvironment === "tenantSource" ? permittedSourceTiers : permittedDestinationTiers,
631
906
  onSave: handleSaveEnvironment,
632
907
  onSelectPreset: setEnvironment,
633
908
  onPresetTierChange: setConfigSearchTier,
634
- presetTier: configSearchTier != null ? configSearchTier : configuredEnvironment === "source" ? (_d = sourceEnvironment == null ? void 0 : sourceEnvironment.env_tier) != null ? _d : "dev" : (_e = destinationEnvironment == null ? void 0 : destinationEnvironment.env_tier) != null ? _e : permittedDestinationTiers[0],
909
+ presetTier: configSearchTier != null ? configSearchTier : configuredEnvironment === "source" ? (_d = sourceEnvironment == null ? void 0 : sourceEnvironment.env_tier) != null ? _d : "dev" : configuredEnvironment === "destination" ? (_e = destinationEnvironment == null ? void 0 : destinationEnvironment.env_tier) != null ? _e : permittedDestinationTiers[0] : configuredEnvironment === "tenantSource" ? (_f = tenantSourceEnvironment == null ? void 0 : tenantSourceEnvironment.env_tier) != null ? _f : "dev" : (_g = tenantDestinationEnvironment == null ? void 0 : tenantDestinationEnvironment.env_tier) != null ? _g : permittedDestinationTiers[0],
635
910
  onClose: () => {
636
911
  setConfiguredEnvironment(null);
637
912
  setConfigSearchTier(null);