Improve linear app (#20453)
- Add front component form to create linear issue <img width="1512" height="831" alt="image" src="https://github.com/user-attachments/assets/ffbb223f-30a8-4c64-ac6d-002c29b604c1" /> <img width="1512" height="829" alt="image" src="https://github.com/user-attachments/assets/a5ed2464-35a9-4a60-804c-5f15eb0043b4" /> - improve marketplace Linear app page <img width="1302" height="834" alt="image" src="https://github.com/user-attachments/assets/cdec7ec2-953d-4a49-a797-5369834b03c1" /> - update admin settings to display non secret values <img width="861" height="473" alt="image" src="https://github.com/user-attachments/assets/41dadf02-aa5d-4eb6-befe-0ad8ad4049b2" />
This commit is contained in:
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-linear",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"description": "Linear integration for Twenty. Connect a user's Linear account and create issues from logic functions.",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -20,7 +20,7 @@
|
||||
"test:watch": "vitest --config vitest.unit.config.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "2.3.0"
|
||||
"twenty-sdk": "2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 198 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 427 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 255 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 420 KiB |
@@ -1,9 +1,6 @@
|
||||
import { defineApplication } from 'twenty-sdk/define';
|
||||
|
||||
import {
|
||||
APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
} from 'src/constants/universal-identifiers';
|
||||
import { ABOUT_DESCRIPTION } from './constants/ABOUT_DESCRIPTION.md';
|
||||
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
@@ -11,10 +8,19 @@ export default defineApplication({
|
||||
description:
|
||||
'Connect Linear to Twenty. Each workspace member connects their own Linear account; logic functions can then create issues and read team data on their behalf.',
|
||||
logoUrl: 'public/linear-logomark.svg',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
// OAuth client_id/secret live at the registration level (one OAuth app per
|
||||
// Twenty server, configured by the server admin) — not per-workspace —
|
||||
// so they're declared as serverVariables, not applicationVariables.
|
||||
aboutDescription: ABOUT_DESCRIPTION,
|
||||
applicationVariables: undefined,
|
||||
author: 'Twenty',
|
||||
category: 'Product management',
|
||||
emailSupport: 'contact@twenty.com',
|
||||
screenshots: [
|
||||
'public/gallery/command-menu-item-1.png',
|
||||
'public/gallery/command-menu-item-2.png',
|
||||
'public/gallery/command-menu-item-3.png',
|
||||
'public/gallery/command-menu-item-4.png',
|
||||
],
|
||||
termsUrl: 'https://github.com/twentyhq/twenty?tab=License-1-ov-file#readme',
|
||||
websiteUrl: 'https://www.twenty.com',
|
||||
serverVariables: {
|
||||
LINEAR_CLIENT_ID: {
|
||||
description:
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { defineCommandMenuItem } from 'twenty-sdk/define';
|
||||
|
||||
import {
|
||||
CREATE_ISSUE_COMMAND_UNIVERSAL_IDENTIFIER,
|
||||
CREATE_ISSUE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
} from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineCommandMenuItem({
|
||||
universalIdentifier: CREATE_ISSUE_COMMAND_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Create Linear issue',
|
||||
shortLabel: 'Linear issue',
|
||||
icon: 'IconPlaylistAdd',
|
||||
isPinned: false,
|
||||
availabilityType: 'GLOBAL',
|
||||
frontComponentUniversalIdentifier:
|
||||
CREATE_ISSUE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
+1015
File diff suppressed because it is too large
Load Diff
+1
-3
@@ -15,8 +15,6 @@ export default defineConnectionProvider({
|
||||
clientIdVariable: 'LINEAR_CLIENT_ID',
|
||||
clientSecretVariable: 'LINEAR_CLIENT_SECRET',
|
||||
tokenRequestContentType: 'form-urlencoded',
|
||||
// Linear supports PKCE but doesn't require it for confidential clients.
|
||||
// Disabled to keep the test surface minimal.
|
||||
usePkce: false,
|
||||
usePkce: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export const ABOUT_DESCRIPTION = `Connect your Linear account to Twenty to create issues and look up teams straight from your workflows or the AI chat.
|
||||
|
||||
## What you can do
|
||||
|
||||
Once installed and connected, two tools become available:
|
||||
|
||||
- **Create Linear issue**
|
||||
- From the AI chat, ask something like *"create a Linear issue in the Engineering team titled 'Fix login bug'"* and the AI will file it for you.
|
||||
- From a workflow, add it as a step with \`teamId\` + \`title\` (and optional \`description\`).
|
||||
|
||||
- **List Linear teams** — discovers the teams in your Linear workspace, useful when you need to pick a \`teamId\` for the create-issue step.
|
||||
|
||||
## Installing
|
||||
|
||||
1. Open **Settings → Applications** in your Twenty workspace.
|
||||
2. Find **Linear** in the available apps and click **Install**.
|
||||
3. Open the app, go to the **Connections** tab, and click **Add connection**.
|
||||
4. Choose **Just for me** (your personal Linear account) or **Workspace shared** (a team-managed Linear account anyone in this workspace can act through), then complete the Linear sign-in.
|
||||
|
||||
That's it — you can now use the tools above.`;
|
||||
@@ -17,3 +17,24 @@ export const CREATE_LINEAR_ISSUE_UNIVERSAL_IDENTIFIER =
|
||||
|
||||
export const LIST_LINEAR_TEAMS_UNIVERSAL_IDENTIFIER =
|
||||
'15824bbc-9c64-4f97-b45f-d0a44b402bb8';
|
||||
|
||||
export const LIST_LINEAR_TEAMS_ROUTE_UNIVERSAL_IDENTIFIER =
|
||||
'a1d4e7b3-5c2f-4a89-9b0e-6f3d8c1a7e5b';
|
||||
|
||||
export const CREATE_LINEAR_ISSUE_ROUTE_UNIVERSAL_IDENTIFIER =
|
||||
'c8f2a6d1-3b7e-4e09-8d5c-2a9f0b4e6c3d';
|
||||
|
||||
export const CREATE_ISSUE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
|
||||
'd4a1c8e5-7f3b-4d62-a90e-1b5c3f8d2a6e';
|
||||
|
||||
export const LIST_LINEAR_WORKFLOW_STATES_ROUTE_UNIVERSAL_IDENTIFIER =
|
||||
'f5a2d8c1-4b7e-4f93-a6d0-9c3e1b8f5a2d';
|
||||
|
||||
export const LIST_LINEAR_ISSUE_OPTIONS_ROUTE_UNIVERSAL_IDENTIFIER =
|
||||
'a3b7e1d9-8c4f-4a26-b5d0-7e9f2c6a8b3d';
|
||||
|
||||
export const LIST_LINEAR_ISSUE_OPTIONS_UNIVERSAL_IDENTIFIER =
|
||||
'c2b99086-6c1b-43e4-9685-de4ade6bda63';
|
||||
|
||||
export const CREATE_ISSUE_COMMAND_UNIVERSAL_IDENTIFIER =
|
||||
'e7b3d9f2-6a1c-4e85-b4d0-8f2c5a7e3b19';
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import type { RoutePayload } from 'twenty-sdk/define';
|
||||
|
||||
import { CREATE_LINEAR_ISSUE_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
import { createLinearIssueHandler } from 'src/logic-functions/handlers/create-linear-issue-handler';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
const body = event.body as Record<string, unknown> | null;
|
||||
|
||||
return createLinearIssueHandler({
|
||||
teamId: body?.teamId as string | undefined,
|
||||
title: body?.title as string | undefined,
|
||||
description: body?.description as string | undefined,
|
||||
priority: body?.priority as number | undefined,
|
||||
stateId: body?.stateId as string | undefined,
|
||||
assigneeId: body?.assigneeId as string | undefined,
|
||||
projectId: body?.projectId as string | undefined,
|
||||
estimate: body?.estimate as number | undefined,
|
||||
labelIds: body?.labelIds as string[] | undefined,
|
||||
cycleId: body?.cycleId as string | undefined,
|
||||
dueDate: body?.dueDate as string | undefined,
|
||||
});
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: CREATE_LINEAR_ISSUE_ROUTE_UNIVERSAL_IDENTIFIER,
|
||||
name: 'create-linear-issue-route',
|
||||
timeoutSeconds: 30,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/linear/issues',
|
||||
httpMethod: 'POST',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
});
|
||||
+47
@@ -27,6 +27,45 @@ export default defineLogicFunction({
|
||||
type: 'string',
|
||||
description: 'Optional issue description (Markdown supported).',
|
||||
},
|
||||
priority: {
|
||||
type: 'integer',
|
||||
description:
|
||||
'Issue priority: 0 = No priority, 1 = Urgent, 2 = High, 3 = Medium, 4 = Low.',
|
||||
minimum: 0,
|
||||
maximum: 4,
|
||||
},
|
||||
stateId: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The workflow state ID for the issue status. Use list-linear-issue-options to discover available states for a team.',
|
||||
},
|
||||
assigneeId: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The user ID to assign the issue to. Use list-linear-issue-options to discover team members.',
|
||||
},
|
||||
projectId: {
|
||||
type: 'string',
|
||||
description: 'The project ID to associate the issue with.',
|
||||
},
|
||||
estimate: {
|
||||
type: 'number',
|
||||
description:
|
||||
'The estimate value for the issue. Must match the team estimate scale.',
|
||||
},
|
||||
labelIds: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Array of label IDs to apply to the issue.',
|
||||
},
|
||||
cycleId: {
|
||||
type: 'string',
|
||||
description: 'The cycle ID to add the issue to.',
|
||||
},
|
||||
dueDate: {
|
||||
type: 'string',
|
||||
description: 'Due date in YYYY-MM-DD format.',
|
||||
},
|
||||
},
|
||||
required: ['teamId', 'title'],
|
||||
},
|
||||
@@ -40,6 +79,14 @@ export default defineLogicFunction({
|
||||
teamId: { type: 'string' },
|
||||
title: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
priority: { type: 'number' },
|
||||
stateId: { type: 'string' },
|
||||
assigneeId: { type: 'string' },
|
||||
projectId: { type: 'string' },
|
||||
estimate: { type: 'number' },
|
||||
labelIds: { type: 'array', items: { type: 'string' } },
|
||||
cycleId: { type: 'string' },
|
||||
dueDate: { type: 'string' },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
+11
@@ -49,6 +49,17 @@ export const createLinearIssueHandler = async (
|
||||
teamId: input.teamId,
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
...(input.priority !== undefined && { priority: input.priority }),
|
||||
...(input.stateId !== undefined && { stateId: input.stateId }),
|
||||
...(input.assigneeId !== undefined && {
|
||||
assigneeId: input.assigneeId,
|
||||
}),
|
||||
...(input.projectId !== undefined && { projectId: input.projectId }),
|
||||
...(input.estimate !== undefined && { estimate: input.estimate }),
|
||||
...(input.labelIds !== undefined &&
|
||||
input.labelIds.length > 0 && { labelIds: input.labelIds }),
|
||||
...(input.cycleId !== undefined && { cycleId: input.cycleId }),
|
||||
...(input.dueDate !== undefined && { dueDate: input.dueDate }),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
import { listConnections } from 'twenty-sdk/logic-function';
|
||||
|
||||
import { callLinearGraphQL } from 'src/logic-functions/utils/call-linear-graphql';
|
||||
|
||||
type LinearMember = {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
};
|
||||
|
||||
type LinearProject = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type LinearLabel = {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
type LinearCycle = {
|
||||
id: string;
|
||||
name: string | null;
|
||||
number: number;
|
||||
startsAt: string;
|
||||
endsAt: string;
|
||||
};
|
||||
|
||||
type LinearWorkflowState = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
position: number;
|
||||
};
|
||||
|
||||
type IssueOptionsQueryResult = {
|
||||
team: {
|
||||
states: { nodes: LinearWorkflowState[] };
|
||||
members: { nodes: LinearMember[] };
|
||||
cycles: { nodes: LinearCycle[] };
|
||||
issueEstimationType: string;
|
||||
issueEstimationAllowZero: boolean;
|
||||
};
|
||||
projects: { nodes: LinearProject[] };
|
||||
issueLabels: { nodes: LinearLabel[] };
|
||||
};
|
||||
|
||||
type IssueOptions = {
|
||||
states: LinearWorkflowState[];
|
||||
members: LinearMember[];
|
||||
projects: LinearProject[];
|
||||
labels: LinearLabel[];
|
||||
cycles: LinearCycle[];
|
||||
estimationType: string;
|
||||
estimationAllowZero: boolean;
|
||||
};
|
||||
|
||||
type HandlerResult =
|
||||
| { success: true; options: IssueOptions }
|
||||
| { success: false; error: string };
|
||||
|
||||
export const listLinearIssueOptionsHandler = async (input: {
|
||||
teamId?: string;
|
||||
}): Promise<HandlerResult> => {
|
||||
if (!input.teamId) {
|
||||
return { success: false, error: '`teamId` is required.' };
|
||||
}
|
||||
|
||||
const connections = await listConnections({ providerName: 'linear' });
|
||||
const connection =
|
||||
connections.find((c) => c.visibility === 'workspace') ?? connections[0];
|
||||
|
||||
if (!connection) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Linear is not connected. Open the app settings and click "Add connection" first.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await callLinearGraphQL<IssueOptionsQueryResult>({
|
||||
accessToken: connection.accessToken,
|
||||
query: `
|
||||
query IssueOptions($teamId: String!) {
|
||||
team(id: $teamId) {
|
||||
states { nodes { id name type position } }
|
||||
members { nodes { id name displayName } }
|
||||
cycles { nodes { id name number startsAt endsAt } }
|
||||
issueEstimationType
|
||||
issueEstimationAllowZero
|
||||
}
|
||||
projects { nodes { id name } }
|
||||
issueLabels { nodes { id name color } }
|
||||
}
|
||||
`,
|
||||
variables: { teamId: input.teamId },
|
||||
});
|
||||
|
||||
if (result.errors || !result.data) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.errors?.[0]?.message ?? 'Unknown Linear API error',
|
||||
};
|
||||
}
|
||||
|
||||
const { team, projects, issueLabels } = result.data;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const activeCycles = team.cycles.nodes.filter((c) => c.endsAt >= now);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
options: {
|
||||
states: team.states.nodes.sort((a, b) => a.position - b.position),
|
||||
members: team.members.nodes.sort((a, b) =>
|
||||
a.displayName.localeCompare(b.displayName),
|
||||
),
|
||||
projects: projects.nodes.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
labels: issueLabels.nodes.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
cycles: activeCycles.sort(
|
||||
(a, b) =>
|
||||
new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime(),
|
||||
),
|
||||
estimationType: team.issueEstimationType,
|
||||
estimationAllowZero: team.issueEstimationAllowZero,
|
||||
},
|
||||
};
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import { listConnections } from 'twenty-sdk/logic-function';
|
||||
|
||||
import { callLinearGraphQL } from 'src/logic-functions/utils/call-linear-graphql';
|
||||
|
||||
type LinearWorkflowState = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
position: number;
|
||||
};
|
||||
|
||||
type WorkflowStatesQueryResult = {
|
||||
team: { states: { nodes: LinearWorkflowState[] } };
|
||||
};
|
||||
|
||||
type HandlerResult =
|
||||
| { success: true; states: LinearWorkflowState[] }
|
||||
| { success: false; error: string };
|
||||
|
||||
export const listLinearWorkflowStatesHandler = async (input: {
|
||||
teamId?: string;
|
||||
}): Promise<HandlerResult> => {
|
||||
if (!input.teamId) {
|
||||
return { success: false, error: '`teamId` is required.' };
|
||||
}
|
||||
|
||||
const connections = await listConnections({ providerName: 'linear' });
|
||||
const connection =
|
||||
connections.find((c) => c.visibility === 'workspace') ?? connections[0];
|
||||
|
||||
if (!connection) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Linear is not connected. Open the app settings and click "Add connection" first.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await callLinearGraphQL<WorkflowStatesQueryResult>({
|
||||
accessToken: connection.accessToken,
|
||||
query: `
|
||||
query WorkflowStates($teamId: String!) {
|
||||
team(id: $teamId) {
|
||||
states { nodes { id name type position } }
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { teamId: input.teamId },
|
||||
});
|
||||
|
||||
if (result.errors || !result.data) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.errors?.[0]?.message ?? 'Unknown Linear API error',
|
||||
};
|
||||
}
|
||||
|
||||
const states = result.data.team.states.nodes.sort(
|
||||
(a, b) => a.position - b.position,
|
||||
);
|
||||
|
||||
return { success: true, states };
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import type { RoutePayload } from 'twenty-sdk/define';
|
||||
|
||||
import { LIST_LINEAR_ISSUE_OPTIONS_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
import { listLinearIssueOptionsHandler } from 'src/logic-functions/handlers/list-linear-issue-options-handler';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
return listLinearIssueOptionsHandler({
|
||||
teamId: event.queryStringParameters?.teamId,
|
||||
});
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: LIST_LINEAR_ISSUE_OPTIONS_ROUTE_UNIVERSAL_IDENTIFIER,
|
||||
name: 'list-linear-issue-options-route',
|
||||
timeoutSeconds: 15,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/linear/issue-options',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
});
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
|
||||
import { LIST_LINEAR_ISSUE_OPTIONS_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
import { listLinearIssueOptionsHandler } from 'src/logic-functions/handlers/list-linear-issue-options-handler';
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: LIST_LINEAR_ISSUE_OPTIONS_UNIVERSAL_IDENTIFIER,
|
||||
name: 'list-linear-issue-options',
|
||||
description:
|
||||
'Returns available options for creating a Linear issue in a specific team: workflow states, members, projects, labels, cycles, and estimation settings. Requires a teamId (call list-linear-teams to discover one).',
|
||||
timeoutSeconds: 15,
|
||||
handler: listLinearIssueOptionsHandler,
|
||||
toolTriggerSettings: {
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
teamId: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The Linear team ID to fetch issue options for. Use list-linear-teams to discover available teams.',
|
||||
},
|
||||
},
|
||||
required: ['teamId'],
|
||||
},
|
||||
},
|
||||
workflowActionTriggerSettings: {
|
||||
label: 'List Linear Issue Options',
|
||||
inputSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
teamId: { type: 'string' },
|
||||
},
|
||||
},
|
||||
],
|
||||
outputSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
options: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
states: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
position: { type: 'number' },
|
||||
},
|
||||
},
|
||||
},
|
||||
members: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
displayName: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
projects: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
color: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
cycles: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
number: { type: 'number' },
|
||||
startsAt: { type: 'string' },
|
||||
endsAt: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
estimationType: { type: 'string' },
|
||||
estimationAllowZero: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
error: { type: 'string' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import type { RoutePayload } from 'twenty-sdk/define';
|
||||
|
||||
import { LIST_LINEAR_TEAMS_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
import { listLinearTeamsHandler } from 'src/logic-functions/handlers/list-linear-teams-handler';
|
||||
|
||||
const handler = async (_event: RoutePayload) => {
|
||||
return listLinearTeamsHandler();
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: LIST_LINEAR_TEAMS_ROUTE_UNIVERSAL_IDENTIFIER,
|
||||
name: 'list-linear-teams-route',
|
||||
timeoutSeconds: 15,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/linear/teams',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
});
|
||||
@@ -16,4 +16,33 @@ export default defineLogicFunction({
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
workflowActionTriggerSettings: {
|
||||
label: 'List Linear Teams',
|
||||
inputSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
],
|
||||
outputSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
teams: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
key: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
error: { type: 'string' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import type { RoutePayload } from 'twenty-sdk/define';
|
||||
|
||||
import { LIST_LINEAR_WORKFLOW_STATES_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
import { listLinearWorkflowStatesHandler } from 'src/logic-functions/handlers/list-linear-workflow-states-handler';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
return listLinearWorkflowStatesHandler({
|
||||
teamId: event.queryStringParameters?.teamId,
|
||||
});
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: LIST_LINEAR_WORKFLOW_STATES_ROUTE_UNIVERSAL_IDENTIFIER,
|
||||
name: 'list-linear-workflow-states-route',
|
||||
timeoutSeconds: 15,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/linear/workflow-states',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
});
|
||||
+8
@@ -2,4 +2,12 @@ export type CreateIssueInput = {
|
||||
teamId?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
priority?: number;
|
||||
stateId?: string;
|
||||
assigneeId?: string;
|
||||
projectId?: string;
|
||||
estimate?: number;
|
||||
labelIds?: string[];
|
||||
cycleId?: string;
|
||||
dueDate?: string;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { defineRole } from 'twenty-sdk/define';
|
||||
import { defineApplicationRole } from 'twenty-sdk/define';
|
||||
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
|
||||
// The Linear logic functions never read workspace data — they only call
|
||||
// Linear's GraphQL API on behalf of the connected user.
|
||||
export default defineRole({
|
||||
export default defineApplicationRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Linear function role',
|
||||
description: 'No-op role for Linear logic functions',
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
@@ -17,7 +18,7 @@
|
||||
"strictBindCallApply": false,
|
||||
"target": "es2020",
|
||||
"module": "esnext",
|
||||
"lib": ["es2020"],
|
||||
"lib": ["es2020", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
|
||||
@@ -3736,15 +3736,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-client-sdk@npm:2.3.0":
|
||||
version: 2.3.0
|
||||
resolution: "twenty-client-sdk@npm:2.3.0"
|
||||
"twenty-client-sdk@npm:2.3.1":
|
||||
version: 2.3.1
|
||||
resolution: "twenty-client-sdk@npm:2.3.1"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@genql/runtime": "npm:^2.10.0"
|
||||
esbuild: "npm:^0.25.0"
|
||||
graphql: "npm:^16.8.1"
|
||||
checksum: 10c0/ab9a4f4ea9222eebdab6d80f396e5281cc0b78bc571163bc8a4f302afc38989ded21c67e0a8e2aad3cdad20f5ec24f60cd2286d9b38a86f7a77333aff5500b20
|
||||
checksum: 10c0/b82a40b6411857345e40b81d32f7069364c6795e79306447d1c21b441315016d8c27ee2c128cd97b82379221812cb18bb556eb37dea337e83b05873b9bfc6c6f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3754,16 +3754,16 @@ __metadata:
|
||||
dependencies:
|
||||
"@types/node": "npm:^24.7.2"
|
||||
oxlint: "npm:^0.16.0"
|
||||
twenty-sdk: "npm:2.3.0"
|
||||
twenty-sdk: "npm:2.3.1"
|
||||
typescript: "npm:^5.9.3"
|
||||
vite-tsconfig-paths: "npm:^4.3.2"
|
||||
vitest: "npm:^3.1.1"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"twenty-sdk@npm:2.3.0":
|
||||
version: 2.3.0
|
||||
resolution: "twenty-sdk@npm:2.3.0"
|
||||
"twenty-sdk@npm:2.3.1":
|
||||
version: 2.3.1
|
||||
resolution: "twenty-sdk@npm:2.3.1"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@genql/runtime": "npm:^2.10.0"
|
||||
@@ -3783,7 +3783,7 @@ __metadata:
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
tinyglobby: "npm:^0.2.15"
|
||||
twenty-client-sdk: "npm:2.3.0"
|
||||
twenty-client-sdk: "npm:2.3.1"
|
||||
typescript: "npm:^5.9.2"
|
||||
uuid: "npm:^13.0.0"
|
||||
vite: "npm:^7.0.0"
|
||||
@@ -3791,7 +3791,7 @@ __metadata:
|
||||
zod: "npm:^4.1.11"
|
||||
bin:
|
||||
twenty: dist/cli.cjs
|
||||
checksum: 10c0/8020481e7500472ec841574458ed18a20588e31f51a8ff9b86e8ceddc933afd9e34840e4130b1aab53b66c35ea7e6b571d2f2dd7b35836bac96f487d84239c1f
|
||||
checksum: 10c0/137a7a7e6d46175019406a21694103100e744e49154f2de5529962249ab06b9fc8240bc097fd2f231b315b6b8504ba3b45279e22aa80bdb9e735cd2570497eac
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -1967,6 +1967,18 @@ type RotateClientSecret {
|
||||
clientSecret: String!
|
||||
}
|
||||
|
||||
type ApplicationRegistrationVariableDTO {
|
||||
id: UUID!
|
||||
key: String!
|
||||
value: String
|
||||
description: String!
|
||||
isSecret: Boolean!
|
||||
isRequired: Boolean!
|
||||
isFilled: Boolean!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type Relation {
|
||||
type: RelationType!
|
||||
sourceObjectMetadata: Object!
|
||||
@@ -3034,7 +3046,7 @@ type Query {
|
||||
findManyApplicationRegistrations: [ApplicationRegistration!]!
|
||||
findOneApplicationRegistration(id: String!): ApplicationRegistration!
|
||||
findApplicationRegistrationStats(id: String!): ApplicationRegistrationStats!
|
||||
findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariable!]!
|
||||
findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariableDTO!]!
|
||||
applicationRegistrationTarballUrl(id: String!): String
|
||||
currentUser: User!
|
||||
currentWorkspace: Workspace!
|
||||
|
||||
@@ -1605,6 +1605,19 @@ export interface RotateClientSecret {
|
||||
__typename: 'RotateClientSecret'
|
||||
}
|
||||
|
||||
export interface ApplicationRegistrationVariableDTO {
|
||||
id: Scalars['UUID']
|
||||
key: Scalars['String']
|
||||
value?: Scalars['String']
|
||||
description: Scalars['String']
|
||||
isSecret: Scalars['Boolean']
|
||||
isRequired: Scalars['Boolean']
|
||||
isFilled: Scalars['Boolean']
|
||||
createdAt: Scalars['DateTime']
|
||||
updatedAt: Scalars['DateTime']
|
||||
__typename: 'ApplicationRegistrationVariableDTO'
|
||||
}
|
||||
|
||||
export interface Relation {
|
||||
type: RelationType
|
||||
sourceObjectMetadata: Object
|
||||
@@ -2633,7 +2646,7 @@ export interface Query {
|
||||
findManyApplicationRegistrations: ApplicationRegistration[]
|
||||
findOneApplicationRegistration: ApplicationRegistration
|
||||
findApplicationRegistrationStats: ApplicationRegistrationStats
|
||||
findApplicationRegistrationVariables: ApplicationRegistrationVariable[]
|
||||
findApplicationRegistrationVariables: ApplicationRegistrationVariableDTO[]
|
||||
applicationRegistrationTarballUrl?: Scalars['String']
|
||||
currentUser: User
|
||||
currentWorkspace: Workspace
|
||||
@@ -4566,6 +4579,20 @@ export interface RotateClientSecretGenqlSelection{
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface ApplicationRegistrationVariableDTOGenqlSelection{
|
||||
id?: boolean | number
|
||||
key?: boolean | number
|
||||
value?: boolean | number
|
||||
description?: boolean | number
|
||||
isSecret?: boolean | number
|
||||
isRequired?: boolean | number
|
||||
isFilled?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
updatedAt?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface RelationGenqlSelection{
|
||||
type?: boolean | number
|
||||
sourceObjectMetadata?: ObjectGenqlSelection
|
||||
@@ -5682,7 +5709,7 @@ export interface QueryGenqlSelection{
|
||||
findManyApplicationRegistrations?: ApplicationRegistrationGenqlSelection
|
||||
findOneApplicationRegistration?: (ApplicationRegistrationGenqlSelection & { __args: {id: Scalars['String']} })
|
||||
findApplicationRegistrationStats?: (ApplicationRegistrationStatsGenqlSelection & { __args: {id: Scalars['String']} })
|
||||
findApplicationRegistrationVariables?: (ApplicationRegistrationVariableGenqlSelection & { __args: {applicationRegistrationId: Scalars['String']} })
|
||||
findApplicationRegistrationVariables?: (ApplicationRegistrationVariableDTOGenqlSelection & { __args: {applicationRegistrationId: Scalars['String']} })
|
||||
applicationRegistrationTarballUrl?: { __args: {id: Scalars['String']} }
|
||||
currentUser?: UserGenqlSelection
|
||||
currentWorkspace?: WorkspaceGenqlSelection
|
||||
@@ -7400,6 +7427,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const ApplicationRegistrationVariableDTO_possibleTypes: string[] = ['ApplicationRegistrationVariableDTO']
|
||||
export const isApplicationRegistrationVariableDTO = (obj?: { __typename?: any } | null): obj is ApplicationRegistrationVariableDTO => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isApplicationRegistrationVariableDTO"')
|
||||
return ApplicationRegistrationVariableDTO_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Relation_possibleTypes: string[] = ['Relation']
|
||||
export const isRelation = (obj?: { __typename?: any } | null): obj is Relation => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isRelation"')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -362,6 +362,19 @@ export type ApplicationRegistrationVariable = {
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export type ApplicationRegistrationVariableDto = {
|
||||
__typename?: 'ApplicationRegistrationVariableDTO';
|
||||
createdAt: Scalars['DateTime'];
|
||||
description: Scalars['String'];
|
||||
id: Scalars['UUID'];
|
||||
isFilled: Scalars['Boolean'];
|
||||
isRequired: Scalars['Boolean'];
|
||||
isSecret: Scalars['Boolean'];
|
||||
key: Scalars['String'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
value?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ApplicationTokenPair = {
|
||||
__typename?: 'ApplicationTokenPair';
|
||||
applicationAccessToken: AuthToken;
|
||||
@@ -4119,7 +4132,7 @@ export type Query = {
|
||||
findApplicationRegistrationByClientId?: Maybe<PublicApplicationRegistration>;
|
||||
findApplicationRegistrationByUniversalIdentifier?: Maybe<ApplicationRegistration>;
|
||||
findApplicationRegistrationStats: ApplicationRegistrationStats;
|
||||
findApplicationRegistrationVariables: Array<ApplicationRegistrationVariable>;
|
||||
findApplicationRegistrationVariables: Array<ApplicationRegistrationVariableDto>;
|
||||
findManyAgents: Array<Agent>;
|
||||
findManyApplicationRegistrations: Array<ApplicationRegistration>;
|
||||
findManyApplications: Array<Application>;
|
||||
@@ -7007,7 +7020,7 @@ export type FindApplicationRegistrationVariablesQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type FindApplicationRegistrationVariablesQuery = { __typename?: 'Query', findApplicationRegistrationVariables: Array<{ __typename?: 'ApplicationRegistrationVariable', id: string, key: string, description: string, isSecret: boolean, isRequired: boolean, isFilled: boolean, createdAt: string, updatedAt: string }> };
|
||||
export type FindApplicationRegistrationVariablesQuery = { __typename?: 'Query', findApplicationRegistrationVariables: Array<{ __typename?: 'ApplicationRegistrationVariableDTO', id: string, key: string, value?: string | null, description: string, isSecret: boolean, isRequired: boolean, isFilled: boolean, createdAt: string, updatedAt: string }> };
|
||||
|
||||
export type FindManyApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -8092,7 +8105,7 @@ export const UpdateApplicationRegistrationDocument = {"kind":"Document","definit
|
||||
export const UpdateApplicationRegistrationVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateApplicationRegistrationVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationVariableInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateApplicationRegistrationVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<UpdateApplicationRegistrationVariableMutation, UpdateApplicationRegistrationVariableMutationVariables>;
|
||||
export const ApplicationRegistrationTarballUrlDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ApplicationRegistrationTarballUrl"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"applicationRegistrationTarballUrl"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode<ApplicationRegistrationTarballUrlQuery, ApplicationRegistrationTarballUrlQueryVariables>;
|
||||
export const FindApplicationRegistrationStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindApplicationRegistrationStats"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findApplicationRegistrationStats"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeInstalls"}},{"kind":"Field","name":{"kind":"Name","value":"mostInstalledVersion"}},{"kind":"Field","name":{"kind":"Name","value":"versionDistribution"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]} as unknown as DocumentNode<FindApplicationRegistrationStatsQuery, FindApplicationRegistrationStatsQueryVariables>;
|
||||
export const FindApplicationRegistrationVariablesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindApplicationRegistrationVariables"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findApplicationRegistrationVariables"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<FindApplicationRegistrationVariablesQuery, FindApplicationRegistrationVariablesQueryVariables>;
|
||||
export const FindApplicationRegistrationVariablesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindApplicationRegistrationVariables"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findApplicationRegistrationVariables"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<FindApplicationRegistrationVariablesQuery, FindApplicationRegistrationVariablesQueryVariables>;
|
||||
export const FindManyApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findManyApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindManyApplicationRegistrationsQuery, FindManyApplicationRegistrationsQueryVariables>;
|
||||
export const FindOneApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindOneApplicationRegistrationQuery, FindOneApplicationRegistrationQueryVariables>;
|
||||
export const UninstallApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UninstallApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"universalIdentifier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uninstallApplication"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"universalIdentifier"},"value":{"kind":"Variable","name":{"kind":"Name","value":"universalIdentifier"}}}]}]}}]} as unknown as DocumentNode<UninstallApplicationMutation, UninstallApplicationMutationVariables>;
|
||||
|
||||
+1
@@ -9,6 +9,7 @@ export const FIND_APPLICATION_REGISTRATION_VARIABLES = gql`
|
||||
) {
|
||||
id
|
||||
key
|
||||
value
|
||||
description
|
||||
isSecret
|
||||
isRequired
|
||||
|
||||
+1
-4
@@ -7,7 +7,6 @@ import {
|
||||
UpdateApplicationRegistrationVariableDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useMutation, useQuery } from '@apollo/client/react';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
@@ -145,9 +144,7 @@ export const SettingsApplicationRegistrationConfigVariableDetail = () => {
|
||||
<TextInput
|
||||
value={value}
|
||||
placeholder={
|
||||
variable.isFilled
|
||||
? '••••••••••••••••••••••••'
|
||||
: t`set-config-value`
|
||||
!isEditing ? (variable.value ?? t`set-config-value`) : ''
|
||||
}
|
||||
onChange={setValue}
|
||||
disabled={!isEditing}
|
||||
|
||||
+3
-11
@@ -1,9 +1,6 @@
|
||||
import type { ApplicationRegistrationData } from '~/pages/settings/applications/tabs/types/ApplicationRegistrationData';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import {
|
||||
type ApplicationRegistrationVariable,
|
||||
FindApplicationRegistrationVariablesDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { FindApplicationRegistrationVariablesDocument } from '~/generated-metadata/graphql';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { H2Title, Status } from 'twenty-ui/display';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
@@ -28,17 +25,12 @@ export const SettingsApplicationRegistrationConfigTab = ({
|
||||
},
|
||||
);
|
||||
|
||||
const variables: ApplicationRegistrationVariable[] =
|
||||
variablesData?.findApplicationRegistrationVariables ?? [];
|
||||
const variables = variablesData?.findApplicationRegistrationVariables ?? [];
|
||||
|
||||
const configVariables = variables.map((variable) => ({
|
||||
name: variable.key,
|
||||
description: variable.description,
|
||||
value: variable.isFilled ? (
|
||||
'••••••••••'
|
||||
) : (
|
||||
<Status color="gray" text={t`Not set`} />
|
||||
),
|
||||
value: variable.value ?? <Status color="gray" text={t`Not set`} />,
|
||||
to: getSettingsPath(
|
||||
SettingsPath.ApplicationRegistrationConfigVariableDetails,
|
||||
{
|
||||
|
||||
+14
-3
@@ -14,6 +14,7 @@ import {
|
||||
import { type CreateApplicationRegistrationVariableInput } from 'src/engine/core-modules/application/application-registration-variable/dtos/create-application-registration-variable.input';
|
||||
import { type UpdateApplicationRegistrationVariableInput } from 'src/engine/core-modules/application/application-registration-variable/dtos/update-application-registration-variable.input';
|
||||
import { SecretEncryptionService } from 'src/engine/core-modules/secret-encryption/secret-encryption.service';
|
||||
import { ApplicationRegistrationVariableDTO } from 'src/engine/core-modules/application/application-registration-variable/dtos/application-registration-variable.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationRegistrationVariableService {
|
||||
@@ -25,19 +26,29 @@ export class ApplicationRegistrationVariableService {
|
||||
private readonly encryptionService: SecretEncryptionService,
|
||||
) {}
|
||||
|
||||
async findVariables(
|
||||
async findVariablesWithObfuscatedValues(
|
||||
applicationRegistrationId: string,
|
||||
workspaceId: string,
|
||||
): Promise<ApplicationRegistrationVariableEntity[]> {
|
||||
): Promise<ApplicationRegistrationVariableDTO[]> {
|
||||
await this.assertRegistrationOwnedByWorkspace(
|
||||
applicationRegistrationId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return this.variableRepository.find({
|
||||
const variables = await this.variableRepository.find({
|
||||
where: { applicationRegistrationId },
|
||||
order: { key: 'ASC' },
|
||||
});
|
||||
|
||||
return variables.map((variable) => ({
|
||||
...variable,
|
||||
isFilled: variable.isFilled,
|
||||
value: variable.isFilled
|
||||
? variable.isSecret
|
||||
? '•••••••••••••'
|
||||
: this.encryptionService.decrypt(variable.encryptedValue)
|
||||
: null,
|
||||
}));
|
||||
}
|
||||
|
||||
async createVariable(
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { IsBoolean, IsString } from 'class-validator';
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
@ObjectType()
|
||||
export class ApplicationRegistrationVariableDTO {
|
||||
@IDField(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
key: string;
|
||||
|
||||
@IsString()
|
||||
@Field(() => String, { nullable: true })
|
||||
value?: string | null;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
description: string;
|
||||
|
||||
@IsBoolean()
|
||||
@Field()
|
||||
isSecret: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@Field()
|
||||
isRequired: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@Field()
|
||||
isFilled: boolean;
|
||||
|
||||
@Field(() => Date)
|
||||
createdAt: Date;
|
||||
|
||||
@Field(() => Date)
|
||||
updatedAt: Date;
|
||||
}
|
||||
+4
-3
@@ -54,6 +54,7 @@ import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { ApplicationRegistrationVariableDTO } from 'src/engine/core-modules/application/application-registration-variable/dtos/application-registration-variable.dto';
|
||||
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@MetadataResolver(() => ApplicationRegistrationEntity)
|
||||
@@ -186,12 +187,12 @@ export class ApplicationRegistrationResolver {
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.API_KEYS_AND_WEBHOOKS),
|
||||
)
|
||||
@Query(() => [ApplicationRegistrationVariableEntity])
|
||||
@Query(() => [ApplicationRegistrationVariableDTO])
|
||||
async findApplicationRegistrationVariables(
|
||||
@Args('applicationRegistrationId') applicationRegistrationId: string,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<ApplicationRegistrationVariableEntity[]> {
|
||||
return this.applicationRegistrationVariableService.findVariables(
|
||||
): Promise<ApplicationRegistrationVariableDTO[]> {
|
||||
return this.applicationRegistrationVariableService.findVariablesWithObfuscatedValues(
|
||||
applicationRegistrationId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user