@evergis/react 4.0.65 → 4.0.66

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.
@@ -10,8 +10,13 @@ export * from './useChartData';
10
10
  export * from './useDashboardHeader';
11
11
  export * from './useDataSources';
12
12
  export * from './useDiffPage';
13
+ export * from './useAfterSave';
14
+ export * from './useBeforeSave';
13
15
  export * from './useExpandableContainers';
14
16
  export * from './useExportPdf';
17
+ export * from './useFeatureSaveHooks';
18
+ export * from './useFeatureSaveHooks.utils';
19
+ export * from './useSavePrototypeBuilder';
15
20
  export * from './useWidgetFilters';
16
21
  export * from './useGetConfigLayer';
17
22
  export * from './useGlobalContext';
@@ -0,0 +1,3 @@
1
+ import { TaskPrototypeDto } from '@evergis/api';
2
+ import { ConfigRelatedResource, SaveHookInput } from '../types';
3
+ export declare const useAfterSave: (hook: ConfigRelatedResource | undefined, buildPrototype: (hook: ConfigRelatedResource, input: SaveHookInput) => TaskPrototypeDto) => (input: SaveHookInput) => Promise<void>;
@@ -0,0 +1,3 @@
1
+ import { TaskPrototypeDto } from '@evergis/api';
2
+ import { ConfigRelatedResource, SaveHookInput } from '../types';
3
+ export declare const useBeforeSave: (hook: ConfigRelatedResource | undefined, buildPrototype: (hook: ConfigRelatedResource, input: SaveHookInput) => TaskPrototypeDto) => (input: SaveHookInput) => Promise<boolean>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Орchestrator-хук для серверных `beforeSave` / `afterSave` python-скриптов,
3
+ * описанных в `layerInfo.configuration.editConfiguration.options` слоя.
4
+ *
5
+ * - `runBeforeSave(input)` — `Promise<boolean>`, resolves `false` если
6
+ * серверная проверка не прошла; сохранение должно быть отменено.
7
+ * - `runAfterSave(input)` — fire-and-forget после успешного save.
8
+ *
9
+ * Активируется только если у потребителя в GlobalProvider переданы
10
+ * `api`, `notification` и `t`, а приложение обёрнуто в
11
+ * `<ServerNotificationsProvider>` (для SignalR-подписки на прогресс).
12
+ */
13
+ export declare const useFeatureSaveHooks: () => {
14
+ readonly runBeforeSave: (input: import('../types').SaveHookInput) => Promise<boolean>;
15
+ readonly runAfterSave: (input: import('../types').SaveHookInput) => Promise<void>;
16
+ };
@@ -0,0 +1,4 @@
1
+ import { ConfigRelatedResource } from '../types';
2
+ export declare const SAVE_HOOK_RESULT_DURATION = 4000;
3
+ export declare const isHookActive: (hook?: ConfigRelatedResource) => hook is ConfigRelatedResource;
4
+ export declare const createSaveNotificationId: () => string;
@@ -0,0 +1,3 @@
1
+ import { TaskPrototypeDto } from '@evergis/api';
2
+ import { ConfigRelatedResource, SaveHookInput } from '../types';
3
+ export declare const useSavePrototypeBuilder: () => (hook: ConfigRelatedResource, input: SaveHookInput) => TaskPrototypeDto;
@@ -59,6 +59,28 @@ export interface ConfigRelatedResource {
59
59
  fileName?: string;
60
60
  methodName?: string;
61
61
  }
62
+ /**
63
+ * Конфигурация `editConfiguration.options` слоя. Описывает серверные
64
+ * python-скрипты, выполняемые до и после сохранения объекта в FeatureCard.
65
+ *
66
+ * - `beforeSave` — синхронная серверная валидация перед сохранением.
67
+ * Save ждёт `Completed`; при `Error` сохранение отменяется.
68
+ * - `afterSave` — fire-and-forget действие после успешного сохранения.
69
+ *
70
+ * Используется хуком `useFeatureSaveHooks`.
71
+ */
72
+ export interface EditConfigurationOptions {
73
+ beforeSave?: ConfigRelatedResource;
74
+ afterSave?: ConfigRelatedResource;
75
+ }
76
+ /**
77
+ * Вход для `runBeforeSave` / `runAfterSave` хуков `useFeatureSaveHooks`.
78
+ * `featureId` равен `null` при создании нового объекта.
79
+ */
80
+ export interface SaveHookInput {
81
+ featureId: number | string | null;
82
+ changedProperties: Record<string, unknown>;
83
+ }
62
84
  export interface ConfigControl extends Pick<ConfigOptions, "relatedDataSource" | "minValue" | "maxValue" | "step" | "label" | "placeholder" | "width" | "variants"> {
63
85
  type: "dropdown" | "checkbox" | "chips" | "string";
64
86
  attributeName?: string;
@@ -1,4 +1,5 @@
1
1
  export * from './constants';
2
2
  export * from './usePythonTask';
3
3
  export * from './usePythonSandbox';
4
+ export * from './useRemoteTask';
4
5
  export * from './types';
@@ -0,0 +1,16 @@
1
+ import { RemoteTaskStatus, TaskPrototypeDto } from '@evergis/api';
2
+ export interface RemoteTaskUpdate {
3
+ status: RemoteTaskStatus;
4
+ log?: string;
5
+ }
6
+ export interface RemoteTaskResult {
7
+ taskId: string;
8
+ status: RemoteTaskStatus.Completed | RemoteTaskStatus.Error;
9
+ log?: string;
10
+ }
11
+ export interface UseRemoteTaskOptions {
12
+ onUpdate?: (update: RemoteTaskUpdate) => void;
13
+ }
14
+ export declare const useRemoteTask: ({ onUpdate }?: UseRemoteTaskOptions) => {
15
+ runTask: (prototype: TaskPrototypeDto) => Promise<RemoteTaskResult>;
16
+ };
package/dist/index.js CHANGED
@@ -5747,6 +5747,49 @@ const usePythonTask = () => {
5747
5747
  };
5748
5748
  };
5749
5749
 
5750
+ const useRemoteTask = ({ onUpdate } = {}) => {
5751
+ const { api: api$1 } = useGlobalContext();
5752
+ const { addSubscription, connection, unsubscribeById } = useServerNotificationsContext();
5753
+ const runTask = React.useCallback((prototype) => new Promise((resolve, reject) => {
5754
+ const start = async () => {
5755
+ const resourceId = prototype.subTaskSettings?.[0]?.startParameters?.resourceId;
5756
+ let prototypeId = await api$1.remoteTaskManager.createTaskPrototype(prototype);
5757
+ prototypeId = prototypeId.replace(/["']+/g, "");
5758
+ const { id: taskId, success } = await api$1.remoteTaskManager.startTask1(prototypeId);
5759
+ if (!success || !taskId) {
5760
+ reject(new Error("startTask returned no taskId"));
5761
+ return;
5762
+ }
5763
+ const subscriptionId = await addSubscription({
5764
+ tag: "python_task_progress_event",
5765
+ resourceId,
5766
+ });
5767
+ const onNotification = ({ data }) => {
5768
+ if (data?.taskId !== taskId) {
5769
+ return;
5770
+ }
5771
+ onUpdate?.({ status: data.status, log: data.log });
5772
+ const isCompleted = data.status === api.RemoteTaskStatus.Completed;
5773
+ const isError = data.status === api.RemoteTaskStatus.Error;
5774
+ if (isCompleted || isError) {
5775
+ connection?.off(SERVER_NOTIFICATION_EVENT.PythonProgressNotification, onNotification);
5776
+ if (subscriptionId) {
5777
+ unsubscribeById(subscriptionId);
5778
+ }
5779
+ resolve({
5780
+ taskId,
5781
+ status: isCompleted ? api.RemoteTaskStatus.Completed : api.RemoteTaskStatus.Error,
5782
+ log: data.log,
5783
+ });
5784
+ }
5785
+ };
5786
+ connection?.on(SERVER_NOTIFICATION_EVENT.PythonProgressNotification, onNotification);
5787
+ };
5788
+ start().catch(reject);
5789
+ }), [api$1, addSubscription, connection, unsubscribeById, onUpdate]);
5790
+ return { runTask };
5791
+ };
5792
+
5750
5793
  const useAppHeight = () => {
5751
5794
  React.useEffect(() => {
5752
5795
  const setAppHeight = () => {
@@ -12413,6 +12456,112 @@ const useDiffPage = (type) => {
12413
12456
  return React.useMemo(() => isDiffPage, [isDiffPage]);
12414
12457
  };
12415
12458
 
12459
+ const SAVE_HOOK_RESULT_DURATION = 4000;
12460
+ const NOTIFICATION_ID_RADIX = 36;
12461
+ const NOTIFICATION_ID_LENGTH = 8;
12462
+ const isHookActive = (hook) => !!hook && (!!hook.resourceId || !!hook.fileName);
12463
+ const createSaveNotificationId = () => `save-hook-${Date.now()}-${Math.random().toString(NOTIFICATION_ID_RADIX).slice(2, NOTIFICATION_ID_LENGTH)}`;
12464
+
12465
+ const useAfterSave = (hook, buildPrototype) => {
12466
+ const { t, notification } = useGlobalContext();
12467
+ const { runTask } = useRemoteTask();
12468
+ return React.useCallback(async (input) => {
12469
+ if (!isHookActive(hook)) {
12470
+ return;
12471
+ }
12472
+ const notificationId = createSaveNotificationId();
12473
+ notification?.add({
12474
+ id: notificationId,
12475
+ title: t("afterSaveProgress", { ns: "dashboard", defaultValue: "Выполняется действие после сохранения" }),
12476
+ progress: true,
12477
+ duration: Number.MAX_SAFE_INTEGER,
12478
+ });
12479
+ try {
12480
+ const { status, log } = await runTask(buildPrototype(hook, input));
12481
+ if (status === api.RemoteTaskStatus.Completed) {
12482
+ notification?.update({
12483
+ id: notificationId,
12484
+ title: t("afterSaveSuccess", { ns: "dashboard", defaultValue: "Действие после сохранения выполнено" }),
12485
+ success: true,
12486
+ progress: false,
12487
+ duration: SAVE_HOOK_RESULT_DURATION,
12488
+ });
12489
+ return;
12490
+ }
12491
+ notification?.update({
12492
+ id: notificationId,
12493
+ title: t("afterSaveError", { ns: "dashboard", defaultValue: "Не удалось выполнить действие после сохранения" }),
12494
+ description: log,
12495
+ error: true,
12496
+ progress: false,
12497
+ duration: SAVE_HOOK_RESULT_DURATION,
12498
+ });
12499
+ }
12500
+ catch (error) {
12501
+ const description = error instanceof Error ? error.message : undefined;
12502
+ notification?.update({
12503
+ id: notificationId,
12504
+ title: t("afterSaveError", { ns: "dashboard", defaultValue: "Не удалось выполнить действие после сохранения" }),
12505
+ description,
12506
+ error: true,
12507
+ progress: false,
12508
+ duration: SAVE_HOOK_RESULT_DURATION,
12509
+ });
12510
+ }
12511
+ }, [hook, buildPrototype, runTask, notification, t]);
12512
+ };
12513
+
12514
+ const useBeforeSave = (hook, buildPrototype) => {
12515
+ const { t, notification } = useGlobalContext();
12516
+ const { runTask } = useRemoteTask();
12517
+ return React.useCallback(async (input) => {
12518
+ if (!isHookActive(hook)) {
12519
+ return true;
12520
+ }
12521
+ const notificationId = createSaveNotificationId();
12522
+ notification?.add({
12523
+ id: notificationId,
12524
+ title: t("beforeSaveProgress", { ns: "dashboard", defaultValue: "Выполняется проверка перед сохранением" }),
12525
+ progress: true,
12526
+ duration: Number.MAX_SAFE_INTEGER,
12527
+ });
12528
+ try {
12529
+ const { status, log } = await runTask(buildPrototype(hook, input));
12530
+ if (status === api.RemoteTaskStatus.Completed) {
12531
+ notification?.update({
12532
+ id: notificationId,
12533
+ title: t("beforeSaveSuccess", { ns: "dashboard", defaultValue: "Проверка перед сохранением пройдена" }),
12534
+ success: true,
12535
+ progress: false,
12536
+ duration: SAVE_HOOK_RESULT_DURATION,
12537
+ });
12538
+ return true;
12539
+ }
12540
+ notification?.update({
12541
+ id: notificationId,
12542
+ title: t("beforeSaveError", { ns: "dashboard", defaultValue: "Не удалось выполнить проверку перед сохранением" }),
12543
+ description: log,
12544
+ error: true,
12545
+ progress: false,
12546
+ duration: SAVE_HOOK_RESULT_DURATION,
12547
+ });
12548
+ return false;
12549
+ }
12550
+ catch (error) {
12551
+ const description = error instanceof Error ? error.message : undefined;
12552
+ notification?.update({
12553
+ id: notificationId,
12554
+ title: t("beforeSaveError", { ns: "dashboard", defaultValue: "Не удалось выполнить проверку перед сохранением" }),
12555
+ description,
12556
+ error: true,
12557
+ progress: false,
12558
+ duration: SAVE_HOOK_RESULT_DURATION,
12559
+ });
12560
+ return false;
12561
+ }
12562
+ }, [hook, buildPrototype, runTask, notification, t]);
12563
+ };
12564
+
12416
12565
  const useExpandableContainers = () => {
12417
12566
  const [expandedContainers, setExpandedContainers] = React.useState({});
12418
12567
  const expandContainer = React.useCallback((id, expanded) => {
@@ -12520,6 +12669,83 @@ const useExportPdf = (id, margin = 20) => {
12520
12669
  return { loading, onExport };
12521
12670
  };
12522
12671
 
12672
+ const useSavePrototypeBuilder = () => {
12673
+ const { ewktGeometry } = useGlobalContext();
12674
+ const { layerInfo, attributes, dataSources, filters: selectedFilters, } = useWidgetContext(exports.WidgetType.FeatureCard);
12675
+ const { projectInfo, dataSources: projectDataSources } = useWidgetContext(exports.WidgetType.Dashboard);
12676
+ const { currentPage } = useWidgetPage(exports.WidgetType.FeatureCard);
12677
+ return React.useCallback((hook, input) => {
12678
+ const resolvedParameters = applyQueryFilters({
12679
+ parameters: hook.parameters ?? {},
12680
+ filters: currentPage?.filters ?? [],
12681
+ selectedFilters,
12682
+ geometry: ewktGeometry,
12683
+ attributes,
12684
+ layerInfo,
12685
+ dataSources,
12686
+ projectDataSources,
12687
+ });
12688
+ const scriptParameters = {
12689
+ projectName: projectInfo?.name,
12690
+ layerName: layerInfo?.name,
12691
+ featureId: input.featureId,
12692
+ properties: input.changedProperties,
12693
+ ...resolvedParameters,
12694
+ };
12695
+ return {
12696
+ enabled: true,
12697
+ startIfPreviousError: true,
12698
+ startIfPreviousNotFinished: true,
12699
+ subTaskSettings: [
12700
+ {
12701
+ type: "pythonService",
12702
+ startParameters: {
12703
+ resourceId: hook.resourceId,
12704
+ fileName: hook.fileName,
12705
+ methodName: hook.methodName,
12706
+ parameters: scriptParameters,
12707
+ method: "pythonrunner/run",
12708
+ },
12709
+ },
12710
+ ],
12711
+ };
12712
+ }, [
12713
+ attributes,
12714
+ currentPage?.filters,
12715
+ dataSources,
12716
+ ewktGeometry,
12717
+ layerInfo,
12718
+ projectDataSources,
12719
+ projectInfo?.name,
12720
+ selectedFilters,
12721
+ ]);
12722
+ };
12723
+
12724
+ /**
12725
+ * Орchestrator-хук для серверных `beforeSave` / `afterSave` python-скриптов,
12726
+ * описанных в `layerInfo.configuration.editConfiguration.options` слоя.
12727
+ *
12728
+ * - `runBeforeSave(input)` — `Promise<boolean>`, resolves `false` если
12729
+ * серверная проверка не прошла; сохранение должно быть отменено.
12730
+ * - `runAfterSave(input)` — fire-and-forget после успешного save.
12731
+ *
12732
+ * Активируется только если у потребителя в GlobalProvider переданы
12733
+ * `api`, `notification` и `t`, а приложение обёрнуто в
12734
+ * `<ServerNotificationsProvider>` (для SignalR-подписки на прогресс).
12735
+ */
12736
+ const useFeatureSaveHooks = () => {
12737
+ const { layerInfo } = useWidgetContext(exports.WidgetType.FeatureCard);
12738
+ const options = React.useMemo(() => layerInfo?.configuration
12739
+ ?.editConfiguration?.options, [layerInfo?.configuration]);
12740
+ const buildPrototype = useSavePrototypeBuilder();
12741
+ const runBeforeSave = useBeforeSave(options?.beforeSave, buildPrototype);
12742
+ const runAfterSave = useAfterSave(options?.afterSave, buildPrototype);
12743
+ return {
12744
+ runBeforeSave,
12745
+ runAfterSave,
12746
+ };
12747
+ };
12748
+
12523
12749
  const getMinMaxFromStringValue = (items, value, current, type) => {
12524
12750
  const valueIndex = items.findIndex(item => item.value === (type === "min" ? value.min : value.max));
12525
12751
  const currentIndex = items.findIndex(item => item.value === (type === "min" ? value.min : value.max));
@@ -13799,6 +14025,7 @@ exports.PresentationPanelWrapper = PresentationPanelWrapper;
13799
14025
  exports.PresentationWrapper = PresentationWrapper;
13800
14026
  exports.ProgressContainer = ProgressContainer;
13801
14027
  exports.RoundedBackgroundContainer = RoundedBackgroundContainer;
14028
+ exports.SAVE_HOOK_RESULT_DURATION = SAVE_HOOK_RESULT_DURATION;
13802
14029
  exports.SERVER_NOTIFICATION_EVENT = SERVER_NOTIFICATION_EVENT;
13803
14030
  exports.STACK_BAR_TOTAL_HEIGHT = STACK_BAR_TOTAL_HEIGHT;
13804
14031
  exports.ServerNotificationsContext = ServerNotificationsContext;
@@ -13841,6 +14068,7 @@ exports.checkIsLoading = checkIsLoading;
13841
14068
  exports.createConfigLayer = createConfigLayer;
13842
14069
  exports.createConfigPage = createConfigPage;
13843
14070
  exports.createNewPageId = createNewPageId;
14071
+ exports.createSaveNotificationId = createSaveNotificationId;
13844
14072
  exports.createTreeNode = createTreeNode;
13845
14073
  exports.dateOptions = dateOptions;
13846
14074
  exports.debounce = debounce;
@@ -13902,6 +14130,7 @@ exports.hexToRgba = hexToRgba;
13902
14130
  exports.isEmptyElementValue = isEmptyElementValue;
13903
14131
  exports.isEmptyValue = isEmptyValue;
13904
14132
  exports.isHiddenEmptyValue = isHiddenEmptyValue;
14133
+ exports.isHookActive = isHookActive;
13905
14134
  exports.isLayerService = isLayerService;
13906
14135
  exports.isNotValidSelectedTab = isNotValidSelectedTab;
13907
14136
  exports.isNumeric = isNumeric;
@@ -13928,10 +14157,12 @@ exports.tooltipValueFromRelatedFeatures = tooltipValueFromRelatedFeatures;
13928
14157
  exports.transparentizeColor = transparentizeColor;
13929
14158
  exports.treeNodesToProjectItems = treeNodesToProjectItems;
13930
14159
  exports.updateDataSource = updateDataSource;
14160
+ exports.useAfterSave = useAfterSave;
13931
14161
  exports.useAppHeight = useAppHeight;
13932
14162
  exports.useAttachmentItems = useAttachmentItems;
13933
14163
  exports.useAttachmentPreviewImages = useAttachmentPreviewImages;
13934
14164
  exports.useAutoCompleteControl = useAutoCompleteControl;
14165
+ exports.useBeforeSave = useBeforeSave;
13935
14166
  exports.useChartChange = useChartChange;
13936
14167
  exports.useChartData = useChartData;
13937
14168
  exports.useContainerAttributes = useContainerAttributes;
@@ -13944,6 +14175,7 @@ exports.useDiffPage = useDiffPage;
13944
14175
  exports.useEditGroupAttributes = useEditGroupAttributes;
13945
14176
  exports.useExpandableContainers = useExpandableContainers;
13946
14177
  exports.useExportPdf = useExportPdf;
14178
+ exports.useFeatureSaveHooks = useFeatureSaveHooks;
13947
14179
  exports.useFetchImageWithAuth = useFetchImageWithAuth;
13948
14180
  exports.useFetchWithAuth = useFetchWithAuth;
13949
14181
  exports.useGetConfigLayer = useGetConfigLayer;
@@ -13962,7 +14194,9 @@ exports.usePythonSandbox = usePythonSandbox;
13962
14194
  exports.usePythonTask = usePythonTask;
13963
14195
  exports.useRedrawLayer = useRedrawLayer;
13964
14196
  exports.useRelatedDataSourceAttributes = useRelatedDataSourceAttributes;
14197
+ exports.useRemoteTask = useRemoteTask;
13965
14198
  exports.useRenderElement = useRenderElement;
14199
+ exports.useSavePrototypeBuilder = useSavePrototypeBuilder;
13966
14200
  exports.useServerNotificationsContext = useServerNotificationsContext;
13967
14201
  exports.useShownOtherItems = useShownOtherItems;
13968
14202
  exports.useToggle = useToggle;