@haiiro/docuflow 0.0.16 → 0.0.18

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 (28) hide show
  1. package/README.md +8 -2
  2. package/manifest.json +34 -19
  3. package/package.json +1 -1
  4. package/public/app/docflow-app.js +5 -5
  5. package/src/front-components/docflow-documents.front-component.mjs +1 -1
  6. package/src/front-components/docflow-processes.front-component.mjs +35 -35
  7. package/src/front-components/docflow-processes.front-component.tsx +187 -85
  8. package/src/front-components/docflow-tasks.front-component.mjs +24 -24
  9. package/src/front-components/launch-process.front-component.mjs +19 -19
  10. package/src/front-components/launch-process.front-component.tsx +36 -60
  11. package/src/front-components/process-steps.front-component.mjs +17 -17
  12. package/src/front-components/task-form.front-component.mjs +10 -10
  13. package/src/logic-functions/bitrix-app.logic-function.mjs +1 -1
  14. package/src/logic-functions/complete-task.logic-function.mjs +39 -8
  15. package/src/logic-functions/docflow-app.logic-function.mjs +1 -1
  16. package/src/logic-functions/hand-out-form-step.logic-function.mjs +71284 -11217
  17. package/src/logic-functions/hand-out-form-step.logic-function.ts +6 -1
  18. package/src/logic-functions/launch-process.logic-function.mjs +269 -33
  19. package/src/logic-functions/launch-process.logic-function.ts +42 -37
  20. package/src/logic-functions/on-task-updated.logic-function.mjs +39 -8
  21. package/src/logic-functions/on-workflow-run-updated.logic-function.mjs +48 -9
  22. package/src/logic-functions/on-workflow-run-updated.logic-function.ts +18 -3
  23. package/src/logic-functions/process-catalog.logic-function.mjs +119726 -0
  24. package/src/logic-functions/process-catalog.logic-function.ts +84 -0
  25. package/src/logic-functions/publish-process.logic-function.mjs +218 -16
  26. package/src/logic-functions/publish-process.logic-function.ts +13 -5
  27. package/src/logic-functions/save-launch-fields.logic-function.mjs +5 -2
  28. package/src/logic-functions/save-step-settings.logic-function.mjs +5 -2
@@ -1,6 +1,7 @@
1
1
  import { defineLogicFunction } from 'twenty-sdk/define';
2
2
 
3
3
  import { handOutFormStep } from 'src/server/process-tasks';
4
+ import { logDocflow } from 'src/shared/log.util';
4
5
  import { HAND_OUT_FORM_STEP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
5
6
 
6
7
  const handler = async (payload: {
@@ -18,7 +19,11 @@ const handler = async (payload: {
18
19
  return { status: 'INVALID_PAYLOAD' };
19
20
  }
20
21
 
21
- return { status: await handOutFormStep({ workflowRunId, stepId, threadId }) };
22
+ const status = await handOutFormStep({ workflowRunId, stepId, waitKey: threadId });
23
+
24
+ logDocflow('hand out', { workflowRunId, stepId, status });
25
+
26
+ return { status };
22
27
  };
23
28
 
24
29
  export default defineLogicFunction({
@@ -1691,12 +1691,16 @@ var readUserMessage = (value) => {
1691
1691
  }
1692
1692
  return void 0;
1693
1693
  };
1694
+ var isMissingGraphqlField = (error) => error instanceof CoreGraphqlError && /Cannot query field|Unknown type/i.test(error.message);
1694
1695
  var callCoreGraphql = async ({
1695
1696
  query,
1696
- variables
1697
+ variables,
1698
+ runAs = "person"
1697
1699
  }) => {
1698
1700
  const apiUrl = process.env.TWENTY_API_URL;
1699
- const token = process.env.TWENTY_APP_ACCESS_TOKEN;
1701
+ const personToken = process.env.TWENTY_APP_ACCESS_TOKEN;
1702
+ const applicationToken = process.env.TWENTY_APP_APPLICATION_ACCESS_TOKEN;
1703
+ const token = runAs === "person" ? personToken ?? applicationToken : applicationToken ?? personToken;
1700
1704
  if (apiUrl === void 0 || token === void 0) {
1701
1705
  throw new CoreGraphqlError(
1702
1706
  "TWENTY_API_URL or TWENTY_APP_ACCESS_TOKEN is not set",
@@ -1724,6 +1728,27 @@ var callCoreGraphql = async ({
1724
1728
  var RUN_WORKFLOW_VERSION_MUTATION = `mutation RunWorkflowVersion($input: RunWorkflowVersionInput!) {
1725
1729
  runWorkflowVersion(input: $input) { workflowRunId }
1726
1730
  }`;
1731
+ var CORE_WORKFLOWS_QUERY = `query CoreWorkflows($first: Int!, $after: String) {
1732
+ coreWorkflows(first: $first, after: $after) {
1733
+ pageInfo { hasNextPage endCursor }
1734
+ edges { node { id name updatedAt workspaceWorkflowId lastPublishedCoreWorkflowVersionId statuses } }
1735
+ }
1736
+ }`;
1737
+ var CORE_WORKFLOWS_WITH_CURRENT_VERSIONS_QUERY = `query CoreWorkflowsWithCurrentVersions($input: CoreWorkflowsWithCurrentVersionsInput!) {
1738
+ coreWorkflowsWithCurrentVersions(input: $input) {
1739
+ workflow { id }
1740
+ currentVersion { id status createdAt trigger steps workspaceWorkflowVersionId }
1741
+ versions { id status createdAt workspaceWorkflowVersionId }
1742
+ }
1743
+ }`;
1744
+ var CORE_WORKFLOW_VERSION_BY_ID_QUERY = `query CoreWorkflowVersionById($coreWorkflowVersionId: UUID!) {
1745
+ coreWorkflowVersionById(coreWorkflowVersionId: $coreWorkflowVersionId) {
1746
+ id status createdAt trigger steps workspaceWorkflowVersionId
1747
+ }
1748
+ }`;
1749
+ var RUN_CORE_WORKFLOW_VERSION_MUTATION = `mutation RunCoreWorkflowVersion($input: RunCoreWorkflowVersionInput!) {
1750
+ runCoreWorkflowVersion(input: $input) { workflowRunId }
1751
+ }`;
1727
1752
 
1728
1753
  // src/shared/api/object-summaries.ts
1729
1754
  import { MetadataApiClient } from "twenty-client-sdk/metadata";
@@ -1774,6 +1799,14 @@ var readDataValue = (response) => {
1774
1799
  }
1775
1800
  return Object.values(data)[0];
1776
1801
  };
1802
+ var readRestList = (response) => {
1803
+ const value = readDataValue(response);
1804
+ return {
1805
+ records: Array.isArray(value) ? value : [],
1806
+ pageInfo: response?.pageInfo ?? {},
1807
+ totalCount: response?.totalCount
1808
+ };
1809
+ };
1777
1810
  var readRestRecord = (response) => {
1778
1811
  const value = readDataValue(response);
1779
1812
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
@@ -1785,6 +1818,7 @@ var DOCFLOW_LAUNCH_PARAMETERS_KEY = "smbDocflowParameters";
1785
1818
  var LAUNCH_PARAMETERS_STEP_INPUT = `{{trigger.${DOCFLOW_LAUNCH_PARAMETERS_KEY}}}`;
1786
1819
  var LAUNCH_TRIGGER_NAME = "\u0417\u0430\u043F\u0443\u0441\u043A \u0441 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u0430\u043C\u0438";
1787
1820
  var LAUNCH_RECORD_KEY = "record";
1821
+ var DOCFLOW_LAUNCHED_BY_KEY = "smbDocflowLaunchedBy";
1788
1822
  var LAUNCH_RECORDS_LIMIT = 50;
1789
1823
  var FORM_FIELD_REQUIRED_SETTING = "smbIsRequired";
1790
1824
  var FORM_FIELD_ROLE_SETTING = "smbRole";
@@ -1823,7 +1857,17 @@ var parseFormFields = (value) => Array.isArray(value) ? value.filter(
1823
1857
  ) : [];
1824
1858
 
1825
1859
  // src/shared/workflow/launch-step.util.ts
1860
+ var isPlainRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1826
1861
  var isLaunchTrigger = (trigger) => trigger?.type === "WEBHOOK" && trigger.name === LAUNCH_TRIGGER_NAME;
1862
+ var parseExpectedBodyFields = (trigger) => {
1863
+ const expectedBody = trigger?.type === "WEBHOOK" ? trigger.settings?.expectedBody : void 0;
1864
+ if (!isPlainRecord(expectedBody)) {
1865
+ return [];
1866
+ }
1867
+ return Object.entries(expectedBody).flatMap(
1868
+ ([name, sample]) => name === LAUNCH_RECORD_KEY || name.startsWith("smbDocflow") || isPlainRecord(sample) || Array.isArray(sample) ? [] : [{ id: `body-${name}`, name, label: name, type: typeof sample === "number" ? "NUMBER" : "TEXT" }]
1869
+ );
1870
+ };
1827
1871
  var parseLaunchFields = (value) => getFillableFormFields(parseFormFields(value));
1828
1872
  var parseLaunchObject = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
1829
1873
  var buildLaunchParameters = ({
@@ -119313,6 +119357,8 @@ var VI2 = /* @__PURE__ */ (function(e3) {
119313
119357
  return e3.OK = "OK", e3.SUCCESS = "SUCCESS", e3.INFO = "INFO", e3.WARNING = "WARNING", e3.ERROR = "ERROR", e3.NEUTRAL = "NEUTRAL", e3.UNKNOWN = "UNKNOWN", e3;
119314
119358
  })({});
119315
119359
  Object.values(VI2).filter((e3) => e3 !== VI2.OK && e3 !== VI2.UNKNOWN), W2.TEXT, W2.ARRAY, W2.BOOLEAN, W2.DATE, W2.DATE_TIME, W2.NUMBER, W2.NUMERIC, W2.RAW_JSON, W2.RICH_TEXT, W2.SELECT, W2.MULTI_SELECT, kF2.SEND_EMAIL, kF2.DRAFT_EMAIL, kF2.CREATE_CALENDAR_EVENT;
119360
+ var HI2 = "TWENTY_API_URL";
119361
+ var UI2 = "TWENTY_APP_APPLICATION_ACCESS_TOKEN";
119316
119362
  hp2.map((e3) => mp2[e3]);
119317
119363
  var WI2 = {
119318
119364
  universalIdentifier: Wl2(),
@@ -119445,6 +119491,27 @@ fd2({
119445
119491
  message: e4.message
119446
119492
  });
119447
119493
  });
119494
+ var XI2 = () => process.env.TWENTY_APP_APPLICATION_ACCESS_TOKEN ?? process.env.TWENTY_APP_ACCESS_TOKEN;
119495
+ var ZI = async ({ query: e3, variables: t4, caller: n4 }) => {
119496
+ let r4 = process.env[HI2], i5 = XI2();
119497
+ if (!r4 || !i5) throw Error(`${n4}() requires the app runtime env vars ${HI2} and ${UI2}.`);
119498
+ let a5 = await fetch(`${r4}/metadata`, {
119499
+ method: "POST",
119500
+ headers: {
119501
+ "Content-Type": "application/json",
119502
+ Authorization: `Bearer ${i5}`
119503
+ },
119504
+ body: JSON.stringify({
119505
+ query: e3,
119506
+ variables: t4
119507
+ })
119508
+ });
119509
+ if (!a5.ok) throw Error(`${n4}() failed: HTTP ${a5.status} ${a5.statusText}`);
119510
+ let o5 = await a5.json();
119511
+ if (o5.errors && o5.errors.length > 0) throw Error(`${n4}() failed: ${o5.errors.map((e4) => e4.message).join(", ")}`);
119512
+ if (!o5.data) throw Error(`${n4}() failed: response contained no data.`);
119513
+ return o5.data;
119514
+ };
119448
119515
  var aL = "\n id\n handle\n displayName\n visibility\n connectedAccountId\n isSyncEnabled\n";
119449
119516
  var oL = `
119450
119517
  mutation CreateAppMessageChannel($input: CreateAppMessageChannelInput!) {
@@ -119474,6 +119541,45 @@ var fL = `
119474
119541
  }
119475
119542
  }
119476
119543
  `;
119544
+ var TL = "\n query GetAppKeyValue($key: String!, $scope: AppKeyValueScope) {\n appKeyValue(key: $key, scope: $scope) {\n key\n value\n scope\n }\n }\n";
119545
+ var EL = "\n mutation SetAppKeyValue($input: SetAppKeyValueInput!) {\n setAppKeyValue(input: $input) {\n key\n value\n scope\n }\n }\n";
119546
+ var DL = "\n mutation DeleteAppKeyValue($key: String!, $scope: AppKeyValueScope) {\n deleteAppKeyValue(key: $key, scope: $scope)\n }\n";
119547
+ var OL = "WORKSPACE";
119548
+ var kL = {
119549
+ async get(e3, t4) {
119550
+ let { appKeyValue: n4 } = await ZI({
119551
+ query: TL,
119552
+ variables: {
119553
+ key: e3,
119554
+ scope: t4?.scope ?? OL
119555
+ },
119556
+ caller: "kv.get"
119557
+ });
119558
+ return n4?.value ?? null;
119559
+ },
119560
+ async set(e3, t4, n4) {
119561
+ await ZI({
119562
+ query: EL,
119563
+ variables: { input: {
119564
+ key: e3,
119565
+ value: t4,
119566
+ scope: n4?.scope ?? OL
119567
+ } },
119568
+ caller: "kv.set"
119569
+ });
119570
+ },
119571
+ async delete(e3, t4) {
119572
+ let { deleteAppKeyValue: n4 } = await ZI({
119573
+ query: DL,
119574
+ variables: {
119575
+ key: e3,
119576
+ scope: t4?.scope ?? OL
119577
+ },
119578
+ caller: "kv.delete"
119579
+ });
119580
+ return n4;
119581
+ }
119582
+ };
119477
119583
  var AL = class {
119478
119584
  constructor(e3, t4) {
119479
119585
  this.__twentyHttpResponse = true, this.body = e3, this.status = t4?.status, this.headers = t4?.headers;
@@ -119509,33 +119615,158 @@ var withRouteErrors = (name, handler2) => async (...args) => {
119509
119615
  }
119510
119616
  };
119511
119617
 
119618
+ // src/shared/api/rest-filter.util.ts
119619
+ var UNSAFE_FILTER_CHARACTERS = /[()[\]'",]/g;
119620
+ var sanitizeFilterText = (text) => text.replace(UNSAFE_FILTER_CHARACTERS, " ").trim();
119621
+ var quote = (value) => `'${sanitizeFilterText(value)}'`;
119622
+ var restEq = (field, value) => `${field}[eq]:${quote(value)}`;
119623
+
119512
119624
  // src/server/records.ts
119513
119625
  var clients = {
119514
119626
  person: new u2(),
119515
119627
  application: new u2({ runAs: "application" })
119516
119628
  };
119517
119629
  var restClient = (runAs) => clients[runAs];
119518
- var getWorkflow = async (runAs, workflowId) => readRestRecord(
119519
- await restClient(runAs).get(`/rest/workflows/${workflowId}`)
119520
- );
119521
- var getWorkflowVersion = async (runAs, workflowVersionId) => readRestRecord(
119522
- await restClient(runAs).get(`/rest/workflowVersions/${workflowVersionId}`)
119630
+
119631
+ // src/server/process-catalog.ts
119632
+ var MEMBERSHIP_KEY = "docflow-processes";
119633
+ var PAGE_SIZE = 200;
119634
+ var VERSIONS_BATCH_SIZE = 50;
119635
+ var readStringList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
119636
+ var readMembership = async () => {
119637
+ const stored = await kL.get(MEMBERSHIP_KEY);
119638
+ return { included: readStringList(stored?.included), excluded: readStringList(stored?.excluded) };
119639
+ };
119640
+ var readLegacyWorkflows = async (runAs) => {
119641
+ try {
119642
+ const { records } = readRestList(
119643
+ await restClient(runAs).get("/rest/workflows", {
119644
+ query: { filter: restEq("smbSection", DOCFLOW_SECTION), limit: PAGE_SIZE }
119645
+ })
119646
+ );
119647
+ return new Map(records.map((record) => [record.id, record]));
119648
+ } catch {
119649
+ return /* @__PURE__ */ new Map();
119650
+ }
119651
+ };
119652
+ var listCoreWorkflows = async (runAs) => {
119653
+ const nodes = [];
119654
+ let after;
119655
+ do {
119656
+ const { coreWorkflows } = await callCoreGraphql({ query: CORE_WORKFLOWS_QUERY, variables: { first: PAGE_SIZE, after }, runAs });
119657
+ nodes.push(...coreWorkflows.edges.map(({ node }) => node));
119658
+ after = coreWorkflows.pageInfo.hasNextPage ? coreWorkflows.pageInfo.endCursor ?? void 0 : void 0;
119659
+ } while (after !== void 0);
119660
+ return nodes;
119661
+ };
119662
+ var isDocflowNode = (node, membership, legacy) => !membership.excluded.includes(node.id) && (membership.included.includes(node.id) || typeof node.workspaceWorkflowId === "string" && legacy.has(node.workspaceWorkflowId));
119663
+ var byNewest = (first, second) => new Date(second.createdAt ?? 0).getTime() - new Date(first.createdAt ?? 0).getTime();
119664
+ var getVersion = async (id3, runAs) => (await callCoreGraphql({
119665
+ query: CORE_WORKFLOW_VERSION_BY_ID_QUERY,
119666
+ variables: { coreWorkflowVersionId: id3 },
119667
+ runAs
119668
+ })).coreWorkflowVersionById ?? void 0;
119669
+ var readVersions = async (workflowIds, runAs) => {
119670
+ const result = /* @__PURE__ */ new Map();
119671
+ for (let start = 0; start < workflowIds.length; start += VERSIONS_BATCH_SIZE) {
119672
+ const { coreWorkflowsWithCurrentVersions } = await callCoreGraphql({
119673
+ query: CORE_WORKFLOWS_WITH_CURRENT_VERSIONS_QUERY,
119674
+ variables: { input: { coreWorkflowIds: workflowIds.slice(start, start + VERSIONS_BATCH_SIZE) } },
119675
+ runAs
119676
+ });
119677
+ for (const { workflow, currentVersion, versions } of coreWorkflowsWithCurrentVersions) {
119678
+ const sorted = [...versions].sort(byNewest);
119679
+ const current = currentVersion ?? void 0;
119680
+ const activeId = sorted.find(({ status }) => status === "ACTIVE")?.id;
119681
+ const draft = current?.status === "DRAFT" ? current : void 0;
119682
+ const active = activeId === void 0 ? void 0 : current?.id === activeId ? current : await getVersion(activeId, runAs);
119683
+ const deactivatedId = sorted.find(({ status }) => status === "DEACTIVATED")?.id;
119684
+ const deactivated = deactivatedId === void 0 || active !== void 0 || draft !== void 0 ? void 0 : current?.id === deactivatedId ? current : await getVersion(deactivatedId, runAs);
119685
+ result.set(workflow.id, { active, draft, deactivated });
119686
+ }
119687
+ }
119688
+ return result;
119689
+ };
119690
+ var toProcess = (node, versions, legacy) => {
119691
+ const savedFields = parseLaunchFields(legacy?.smbLaunchFields);
119692
+ const active = versions?.active;
119693
+ return {
119694
+ workflowId: node.id,
119695
+ workspaceWorkflowId: node.workspaceWorkflowId ?? null,
119696
+ name: node.name?.trim() || "\u0411\u0435\u0437 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F",
119697
+ updatedAt: node.updatedAt ?? null,
119698
+ ...versions,
119699
+ // Fields saved by docflow, or for a webhook set up in the Twenty editor
119700
+ // the values its body expects
119701
+ launchFields: savedFields.length > 0 ? savedFields : parseExpectedBodyFields(active?.trigger),
119702
+ launchObject: isLaunchTrigger(active?.trigger) ? parseLaunchObject(legacy?.smbLaunchObject) : void 0
119703
+ };
119704
+ };
119705
+ var listDocflowProcesses = async (runAs = "person") => {
119706
+ const [nodes, membership, legacy] = await Promise.all([
119707
+ listCoreWorkflows(runAs),
119708
+ readMembership(),
119709
+ readLegacyWorkflows(runAs)
119710
+ ]);
119711
+ const docflowNodes = nodes.filter((node) => isDocflowNode(node, membership, legacy));
119712
+ const versions = await readVersions(
119713
+ docflowNodes.map(({ id: id3 }) => id3),
119714
+ runAs
119715
+ );
119716
+ return docflowNodes.map(
119717
+ (node) => toProcess(node, versions.get(node.id), node.workspaceWorkflowId ? legacy.get(node.workspaceWorkflowId) : void 0)
119718
+ );
119719
+ };
119720
+ var findDocflowProcess = async (workflowId, runAs = "person") => (await listDocflowProcesses(runAs)).find(
119721
+ (process2) => process2.workflowId === workflowId || process2.workspaceWorkflowId === workflowId
119523
119722
  );
119524
119723
 
119525
119724
  // src/shared/workflow/workflow-run.util.ts
119526
119725
  var DAY_IN_MS = 24 * 60 * 60 * 1e3;
119527
119726
 
119528
119727
  // src/server/workflow-engine.ts
119529
- var runWorkflowVersion = async ({
119530
- workflowVersionId,
119728
+ var runManualProcess = async ({
119729
+ workspaceWorkflowVersionId,
119730
+ coreWorkflowVersionId,
119531
119731
  workflowRunId,
119532
119732
  payload
119533
119733
  }) => {
119734
+ if (typeof workspaceWorkflowVersionId === "string") {
119735
+ try {
119736
+ const data2 = await callCoreGraphql({
119737
+ query: RUN_WORKFLOW_VERSION_MUTATION,
119738
+ variables: { input: { workflowVersionId: workspaceWorkflowVersionId, workflowRunId, payload } }
119739
+ });
119740
+ return data2.runWorkflowVersion.workflowRunId;
119741
+ } catch (error) {
119742
+ if (!isMissingGraphqlField(error)) {
119743
+ throw error;
119744
+ }
119745
+ }
119746
+ }
119534
119747
  const data = await callCoreGraphql({
119535
- query: RUN_WORKFLOW_VERSION_MUTATION,
119536
- variables: { input: { workflowVersionId, workflowRunId, payload } }
119748
+ query: RUN_CORE_WORKFLOW_VERSION_MUTATION,
119749
+ variables: { input: { coreWorkflowVersionId, workflowRunId, payload } }
119750
+ });
119751
+ return data.runCoreWorkflowVersion.workflowRunId;
119752
+ };
119753
+ var runWebhookProcess = async ({
119754
+ workspaceId,
119755
+ workflowId,
119756
+ payload
119757
+ }) => {
119758
+ const apiUrl = (process.env.TWENTY_API_URL ?? "").replace(/\/$/, "");
119759
+ const response = await fetch(`${apiUrl}/webhooks/workflows/${workspaceId}/${workflowId}`, {
119760
+ method: "POST",
119761
+ headers: { "content-type": "application/json" },
119762
+ body: JSON.stringify(payload)
119537
119763
  });
119538
- return data.runWorkflowVersion.workflowRunId;
119764
+ const body = await response.json().catch(() => ({}));
119765
+ if (!response.ok || typeof body.workflowRunId !== "string") {
119766
+ const reason = Array.isArray(body.messages) ? body.messages.join("; ") : body.message;
119767
+ throw new Error(`\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B\u0441\u044F: ${typeof reason === "string" ? reason : `HTTP ${response.status}`}`);
119768
+ }
119769
+ return body.workflowRunId;
119539
119770
  };
119540
119771
 
119541
119772
  // src/universal-identifiers.ts
@@ -119548,21 +119779,21 @@ var handler = async (event, context) => {
119548
119779
  return jsonResponse(401, { message: "\u0417\u0430\u043F\u0443\u0441\u043A\u0430\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u044B \u043C\u043E\u0436\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A" });
119549
119780
  }
119550
119781
  const body = readJsonBody(event.body);
119551
- const workflowVersionId = body.workflowVersionId;
119782
+ const workflowId = body.workflowId;
119552
119783
  const values = readJsonBody(body.values);
119553
119784
  const recordIds = readRecordIds(body.recordIds);
119554
- if (typeof workflowVersionId !== "string" || workflowVersionId.length === 0) {
119785
+ if (typeof workflowId !== "string" || workflowId.length === 0) {
119555
119786
  return jsonResponse(400, { message: "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D \u043F\u0440\u043E\u0446\u0435\u0441\u0441" });
119556
119787
  }
119557
- const version = await getWorkflowVersion("person", workflowVersionId);
119558
- if (version === void 0 || version.status !== "ACTIVE") {
119559
- return jsonResponse(404, { message: "\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0438\u043B\u0438 \u043D\u0435 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D" });
119788
+ const docflowProcess = await findDocflowProcess(workflowId, "person");
119789
+ const version = docflowProcess?.active;
119790
+ if (docflowProcess === void 0) {
119791
+ return jsonResponse(404, { message: "\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442\u0430 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D" });
119560
119792
  }
119561
- const workflow = typeof version.workflowId === "string" ? await getWorkflow("person", version.workflowId) : void 0;
119562
- if (workflow?.smbSection !== DOCFLOW_SECTION) {
119563
- return jsonResponse(403, { message: "\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u043D\u0435 \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0441\u044F \u043A \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442\u0443" });
119793
+ if (version === void 0) {
119794
+ return jsonResponse(404, { message: "\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u043D\u0435 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D" });
119564
119795
  }
119565
- const fields = parseLaunchFields(workflow.smbLaunchFields);
119796
+ const fields = docflowProcess.launchFields;
119566
119797
  const parameters = buildLaunchParameters({ fields, values });
119567
119798
  const missingFields = findMissingRequiredFields({ fields, values: parameters });
119568
119799
  if (missingFields.length > 0) {
@@ -119571,8 +119802,8 @@ var handler = async (event, context) => {
119571
119802
  missingFieldNames: missingFields.map(({ name }) => name)
119572
119803
  });
119573
119804
  }
119574
- const launchObjectName = parseLaunchObject(workflow.smbLaunchObject);
119575
- const isLaunchedOnRecords = launchObjectName !== void 0 && isLaunchTrigger(version.trigger);
119805
+ const launchObjectName = docflowProcess.launchObject;
119806
+ const isLaunchedOnRecords = launchObjectName !== void 0;
119576
119807
  let records = [void 0];
119577
119808
  if (isLaunchedOnRecords) {
119578
119809
  if (recordIds.length === 0) {
@@ -119603,23 +119834,28 @@ var handler = async (event, context) => {
119603
119834
  }
119604
119835
  const workflowRunIds = [];
119605
119836
  for (const record of records) {
119606
- const workflowRunId = crypto.randomUUID();
119837
+ const payload = {
119838
+ ...fields.length > 0 || record !== void 0 ? buildLaunchPayload(parameters, record) : {},
119839
+ [DOCFLOW_LAUNCHED_BY_KEY]: context.workspaceMemberId
119840
+ };
119607
119841
  try {
119608
- await runWorkflowVersion({
119609
- workflowVersionId,
119610
- workflowRunId,
119611
- payload: fields.length > 0 || record !== void 0 ? buildLaunchPayload(parameters, record) : {}
119612
- });
119842
+ workflowRunIds.push(
119843
+ version.trigger?.type === "WEBHOOK" ? await runWebhookProcess({ workspaceId: context.workspaceId, workflowId: docflowProcess.workflowId, payload }) : await runManualProcess({
119844
+ workspaceWorkflowVersionId: version.workspaceWorkflowVersionId,
119845
+ coreWorkflowVersionId: version.id,
119846
+ workflowRunId: crypto.randomUUID(),
119847
+ payload
119848
+ })
119849
+ );
119613
119850
  } catch (error) {
119614
- logDocflowError("launch: run failed", error, { workflowVersionId, recordId: record?.id });
119851
+ logDocflowError("launch: run failed", error, { workflowId: docflowProcess.workflowId, recordId: record?.id });
119615
119852
  return jsonResponse(403, {
119616
- message: error instanceof CoreGraphqlError && error.userMessage !== void 0 ? error.userMessage : "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441",
119853
+ message: error instanceof CoreGraphqlError && error.userMessage !== void 0 ? /only start its own/i.test(`${error.message} ${error.userMessage}`) ? "\u0412 \u044D\u0442\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 Twenty \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u044B \u0441 \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u043E\u043C \xABWebhook\xBB. \u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u0441 \u0440\u0443\u0447\u043D\u044B\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u043E\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F \u0438\u0437 \u0441\u0430\u043C\u043E\u0439 Twenty, \u0438\u043B\u0438 \u043F\u043E\u043C\u0435\u043D\u044F\u0439\u0442\u0435 \u0435\u043C\u0443 \u0442\u0440\u0438\u0433\u0433\u0435\u0440 \u0432 \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0435." : error.userMessage : error instanceof Error ? error.message : "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441",
119617
119854
  workflowRunIds
119618
119855
  });
119619
119856
  }
119620
- workflowRunIds.push(workflowRunId);
119621
119857
  }
119622
- logDocflow("launch: started", { workflowVersionId, workflowRunIds, parameters, recordIds });
119858
+ logDocflow("launch: started", { workflowId: docflowProcess.workflowId, workflowRunIds, parameters, recordIds });
119623
119859
  return { workflowRunId: workflowRunIds[0], workflowRunIds };
119624
119860
  };
119625
119861
  var launch_process_logic_function_default = defineLogicFunction({
@@ -7,18 +7,16 @@ import {
7
7
  import { CoreGraphqlError } from 'src/shared/api/core-graphql.util';
8
8
  import { getObjectSummaries } from 'src/shared/api/object-summaries';
9
9
  import { readRestRecord } from 'src/shared/api/rest-response.util';
10
- import { DOCFLOW_SECTION, LAUNCH_RECORDS_LIMIT } from 'src/shared/docflow.constants';
10
+ import { DOCFLOW_LAUNCHED_BY_KEY, LAUNCH_RECORDS_LIMIT } from 'src/shared/docflow.constants';
11
11
  import { findMissingRequiredFields } from 'src/shared/form/form-fields.util';
12
12
  import {
13
13
  buildLaunchParameters,
14
14
  buildLaunchPayload,
15
- isLaunchTrigger,
16
- parseLaunchFields,
17
- parseLaunchObject,
18
15
  } from 'src/shared/workflow/launch-step.util';
19
16
  import { readJsonBody, jsonResponse, withRouteErrors } from 'src/server/http-response';
20
- import { getWorkflow, getWorkflowVersion, restClient } from 'src/server/records';
21
- import { runWorkflowVersion } from 'src/server/workflow-engine';
17
+ import { findDocflowProcess } from 'src/server/process-catalog';
18
+ import { restClient } from 'src/server/records';
19
+ import { runManualProcess, runWebhookProcess } from 'src/server/workflow-engine';
22
20
  import { logDocflow, logDocflowError } from 'src/shared/log.util';
23
21
  import { LAUNCH_PROCESS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
24
22
 
@@ -31,7 +29,9 @@ const readRecordIds = (value: unknown): string[] =>
31
29
  // so they stay in the run as its trigger output ({{trigger.<field>}}). A
32
30
  // process launched on records runs once per record, the record read as the
33
31
  // person and put into its run as {{trigger.record}}, like the standard manual
34
- // launch on a selection does.
32
+ // launch on a selection does. A webhook process starts through its webhook
33
+ // (Twenty 2.45 and 2.50 alike); the webhook knows no person, so the one who
34
+ // launched rides in the payload too.
35
35
  const handler = async (
36
36
  event: RoutePayload,
37
37
  context: LogicFunctionExecutionContext,
@@ -41,30 +41,28 @@ const handler = async (
41
41
  }
42
42
 
43
43
  const body = readJsonBody(event.body);
44
- const workflowVersionId = body.workflowVersionId;
44
+ const workflowId = body.workflowId;
45
45
  const values = readJsonBody(body.values);
46
46
  const recordIds = readRecordIds(body.recordIds);
47
47
 
48
- if (typeof workflowVersionId !== 'string' || workflowVersionId.length === 0) {
48
+ if (typeof workflowId !== 'string' || workflowId.length === 0) {
49
49
  return jsonResponse(400, { message: 'Не указан процесс' });
50
50
  }
51
51
 
52
- const version = await getWorkflowVersion('person', workflowVersionId);
52
+ // Read as the person: without the "Workflows" permission the engine does
53
+ // not show processes, and then they cannot be launched either
54
+ const docflowProcess = await findDocflowProcess(workflowId, 'person');
55
+ const version = docflowProcess?.active;
53
56
 
54
- if (version === undefined || version.status !== 'ACTIVE') {
55
- return jsonResponse(404, { message: 'Процесс не найден или не опубликован' });
57
+ if (docflowProcess === undefined) {
58
+ return jsonResponse(404, { message: 'Процесс документооборота не найден' });
56
59
  }
57
60
 
58
- const workflow =
59
- typeof version.workflowId === 'string'
60
- ? await getWorkflow('person', version.workflowId)
61
- : undefined;
62
-
63
- if (workflow?.smbSection !== DOCFLOW_SECTION) {
64
- return jsonResponse(403, { message: 'Процесс не относится к документообороту' });
61
+ if (version === undefined) {
62
+ return jsonResponse(404, { message: 'Процесс не опубликован' });
65
63
  }
66
64
 
67
- const fields = parseLaunchFields(workflow.smbLaunchFields);
65
+ const fields = docflowProcess.launchFields;
68
66
  const parameters = buildLaunchParameters({ fields, values });
69
67
  const missingFields = findMissingRequiredFields({ fields, values: parameters });
70
68
 
@@ -75,10 +73,8 @@ const handler = async (
75
73
  });
76
74
  }
77
75
 
78
- const launchObjectName = parseLaunchObject(workflow.smbLaunchObject);
79
- // The record's object comes from the published trigger: the field may
80
- // already hold the object of a draft not published yet.
81
- const isLaunchedOnRecords = launchObjectName !== undefined && isLaunchTrigger(version.trigger);
76
+ const launchObjectName = docflowProcess.launchObject;
77
+ const isLaunchedOnRecords = launchObjectName !== undefined;
82
78
  let records: (Record<string, unknown> | undefined)[] = [undefined];
83
79
 
84
80
  if (isLaunchedOnRecords) {
@@ -119,31 +115,40 @@ const handler = async (
119
115
  const workflowRunIds: string[] = [];
120
116
 
121
117
  for (const record of records) {
122
- const workflowRunId = crypto.randomUUID();
118
+ const payload = {
119
+ ...(fields.length > 0 || record !== undefined ? buildLaunchPayload(parameters, record) : {}),
120
+ [DOCFLOW_LAUNCHED_BY_KEY]: context.workspaceMemberId,
121
+ };
123
122
 
124
123
  try {
125
- await runWorkflowVersion({
126
- workflowVersionId,
127
- workflowRunId,
128
- payload:
129
- fields.length > 0 || record !== undefined ? buildLaunchPayload(parameters, record) : {},
130
- });
124
+ workflowRunIds.push(
125
+ version.trigger?.type === 'WEBHOOK'
126
+ ? await runWebhookProcess({ workspaceId: context.workspaceId, workflowId: docflowProcess.workflowId, payload })
127
+ : await runManualProcess({
128
+ workspaceWorkflowVersionId: version.workspaceWorkflowVersionId,
129
+ coreWorkflowVersionId: version.id,
130
+ workflowRunId: crypto.randomUUID(),
131
+ payload,
132
+ }),
133
+ );
131
134
  } catch (error) {
132
- logDocflowError('launch: run failed', error, { workflowVersionId, recordId: record?.id });
135
+ logDocflowError('launch: run failed', error, { workflowId: docflowProcess.workflowId, recordId: record?.id });
133
136
 
134
137
  return jsonResponse(403, {
135
138
  message:
136
139
  error instanceof CoreGraphqlError && error.userMessage !== undefined
137
- ? error.userMessage
138
- : 'Не удалось запустить процесс',
140
+ ? /only start its own/i.test(`${error.message} ${error.userMessage}`)
141
+ ? 'В этой версии Twenty приложение запускает только процессы с триггером «Webhook». Процесс с ручным запуском запускается из самой Twenty, или поменяйте ему триггер в редакторе.'
142
+ : error.userMessage
143
+ : error instanceof Error
144
+ ? error.message
145
+ : 'Не удалось запустить процесс',
139
146
  workflowRunIds,
140
147
  });
141
148
  }
142
-
143
- workflowRunIds.push(workflowRunId);
144
149
  }
145
150
 
146
- logDocflow('launch: started', { workflowVersionId, workflowRunIds, parameters, recordIds });
151
+ logDocflow('launch: started', { workflowId: docflowProcess.workflowId, workflowRunIds, parameters, recordIds });
147
152
 
148
153
  return { workflowRunId: workflowRunIds[0], workflowRunIds };
149
154
  };