diff --git a/.changeset/light-rice-argue.md b/.changeset/light-rice-argue.md new file mode 100644 index 0000000000..f083d28eac --- /dev/null +++ b/.changeset/light-rice-argue.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Added `gitlab:issues:edit` action to edit existing GitLab issues diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index 102b2e47b0..34a63cdb0f 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -29,12 +29,12 @@ export const createGitlabIssueAction: (options: { }) => TemplateAction< { title: string; - repoUrl: string; projectId: number; + repoUrl: string; labels?: string | undefined; description?: string | undefined; - weight?: number | undefined; token?: string | undefined; + weight?: number | undefined; assignees?: number[] | undefined; createdAt?: string | undefined; confidential?: boolean | undefined; @@ -57,12 +57,12 @@ export const createGitlabProjectAccessTokenAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - repoUrl: string; projectId: string | number; + repoUrl: string; name?: string | undefined; token?: string | undefined; - scopes?: string[] | undefined; expiresAt?: string | undefined; + scopes?: string[] | undefined; accessLevel?: number | undefined; }, { @@ -76,10 +76,10 @@ export const createGitlabProjectDeployTokenAction: (options: { }) => TemplateAction< { name: string; - repoUrl: string; projectId: string | number; - username?: string | undefined; + repoUrl: string; token?: string | undefined; + username?: string | undefined; scopes?: string[] | undefined; }, { @@ -95,8 +95,8 @@ export const createGitlabProjectVariableAction: (options: { { key: string; value: string; - repoUrl: string; projectId: string | number; + repoUrl: string; variableType: string; raw?: boolean | undefined; token?: string | undefined; @@ -149,8 +149,8 @@ export function createPublishGitlabAction(options: { squash_option?: | 'always' | 'never' - | 'default_off' | 'default_on' + | 'default_off' | undefined; topics?: string[] | undefined; visibility?: 'internal' | 'private' | 'public' | undefined; @@ -207,8 +207,8 @@ export const createTriggerGitlabPipelineAction: (options: { }) => TemplateAction< { branch: string; - repoUrl: string; projectId: number; + repoUrl: string; tokenDescription: string; token?: string | undefined; }, @@ -217,10 +217,54 @@ export const createTriggerGitlabPipelineAction: (options: { } >; +// @public +export const editGitlabIssueAction: (options: { + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + projectId: number; + repoUrl: string; + issueIid: number; + title?: string | undefined; + labels?: string | undefined; + description?: string | undefined; + token?: string | undefined; + weight?: number | undefined; + assignees?: number[] | undefined; + addLabels?: string | undefined; + confidential?: boolean | undefined; + milestoneId?: number | undefined; + removeLabels?: string | undefined; + stateEvent?: IssueStateEvent | undefined; + discussionLocked?: boolean | undefined; + epicId?: number | undefined; + dueDate?: string | undefined; + updatedAt?: string | undefined; + issueType?: IssueType | undefined; + }, + { + state: string; + title: string; + projectId: number; + updatedAt: string; + issueUrl: string; + issueId: number; + issueIid: number; + } +>; + // @public const gitlabModule: () => BackendFeature; export default gitlabModule; +// @public +export enum IssueStateEvent { + // (undocumented) + CLOSE = 'close', + // (undocumented) + REOPEN = 'reopen', +} + // @public export enum IssueType { // (undocumented) @@ -228,6 +272,8 @@ export enum IssueType { // (undocumented) ISSUE = 'issue', // (undocumented) + TASK = 'task', + // (undocumented) TEST = 'test_case', } ``` diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts index af359c39b5..d5aa2d1e17 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts @@ -17,7 +17,8 @@ import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { createGitlabIssueAction, IssueType } from './gitlabIssueCreate'; +import { IssueType } from '../commonGitlabConfig'; +import { createGitlabIssueAction } from './gitlabIssueCreate'; const mockGitlabClient = { Issues: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts index 97ac3cd6fb..3eaca95270 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts @@ -17,22 +17,11 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import commonGitlabConfig from '../commonGitlabConfig'; +import commonGitlabConfig, { IssueType } from '../commonGitlabConfig'; import { examples } from './gitlabIssueCreate.examples'; import { z } from 'zod'; import { checkEpicScope, convertDate, getClient, parseRepoUrl } from '../util'; -import { Gitlab, CreateIssueOptions, IssueSchema } from '@gitbeaker/rest'; - -/** - * Gitlab issue types - * - * @public - */ -export enum IssueType { - ISSUE = 'issue', - INCIDENT = 'incident', - TEST = 'test_case', -} +import { CreateIssueOptions, IssueSchema } from '@gitbeaker/rest'; const issueInputProperties = z.object({ projectId: z.number().describe('Project Id'), @@ -150,11 +139,7 @@ export const createGitlabIssueAction = (options: { let isEpicScoped = false; if (epicId) { - isEpicScoped = await checkEpicScope( - api as any as InstanceType, - projectId, - epicId, - ); + isEpicScoped = await checkEpicScope(api, projectId, epicId); if (isEpicScoped) { ctx.logger.info('Epic is within Project Scope'); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.ts new file mode 100644 index 0000000000..5b9cd5a1a8 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.ts @@ -0,0 +1,85 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; +import { commonGitlabConfigExample } from '../commonGitlabConfig'; + +export const examples: TemplateExample[] = [ + { + description: 'Edit a GitLab issue with minimal options', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issues:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Modified Test Issue', + description: 'This is a modified description of the issue', + }, + }, + ], + }), + }, + { + description: 'Edit a GitLab issue with assignees and date options', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issues:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test Issue', + assignees: [18], + description: 'This is the edited description of the issue', + updatedAt: '2024-05-10 18:00:00.000', + dueDate: '2024-09-28', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab Issue with several options', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issues:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test Edit Issue', + assignees: [18, 15], + description: 'This is the description of the issue', + confidential: false, + updatedAt: '2024-05-10 18:00:00.000', + dueDate: '2024-09-28', + discussionLocked: true, + epicId: 1, + labels: 'phase1:label1,phase2:label2', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts new file mode 100644 index 0000000000..fa96a59669 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts @@ -0,0 +1,211 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { ConfigReader } from '@backstage/core-app-api'; +import { ScmIntegrations } from '@backstage/integration'; +import { IssueType } from '../commonGitlabConfig'; +import { editGitlabIssueAction } from './gitlabIssueEdit'; + +const mockGitlabClient = { + Issues: { + edit: jest.fn(), + }, +}; +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:issues:edit', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.useFakeTimers({ + now: new Date(1988, 5, 3, 12, 0, 0), + }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = editGitlabIssueAction({ integrations }); + + it('should return a Gitlab issue when called with minimal input params', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(123, 42, { + title: 'Computer banks to rule the world', + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: undefined, + assigneeIds: [], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: undefined, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: undefined, + weight: undefined, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it('should return a Gitlab issue when called with oAuth Token', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + }); + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(123, 42, { + title: 'Computer banks to rule the world', + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: undefined, + assigneeIds: [], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: undefined, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: undefined, + weight: undefined, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it('should return a Gitlab issue when modified with several input params', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + issueType: IssueType.INCIDENT, + title: 'Computer banks to rule the world', + description: + 'this issue should kickstart research on instruments to sight the stars', + dueDate: '2025-08-20', + token: 'myAwesomeToken', + assignees: [3, 14, 15], + labels: 'operation:mindcrime', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(123, 42, { + title: 'Computer banks to rule the world', + issueType: 'incident', + addLabels: undefined, + removeLabels: undefined, + description: + 'this issue should kickstart research on instruments to sight the stars', + assigneeIds: [3, 14, 15], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: 'operation:mindcrime', + updatedAt: new Date().toISOString(), + dueDate: '2025-08-20', + milestoneId: undefined, + weight: undefined, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.ts new file mode 100644 index 0000000000..8090d62b86 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.ts @@ -0,0 +1,245 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import commonGitlabConfig, { + IssueType, + IssueStateEvent, +} from '../commonGitlabConfig'; +import { examples } from './gitlabIssueEdit.examples'; +import { z } from 'zod'; +import { checkEpicScope, convertDate, getClient, parseRepoUrl } from '../util'; +import { IssueSchema, EditIssueOptions } from '@gitbeaker/rest'; + +const editIssueInputProperties = z.object({ + projectId: z + .number() + .describe( + 'The global ID or URL-encoded path of the project owned by the authenticated user.', + ), + issueIid: z.number().describe("The internal ID of a project's issue"), + addLabels: z + .string({ + description: + 'Comma-separated label names to add to an issue. If a label does not already exist, this creates a new project label and assigns it to the issue.', + }) + .optional(), + assignees: z + .array(z.number(), { + description: 'IDs of the users to assign the issue to.', + }) + .optional(), + confidential: z + .boolean({ description: 'Updates an issue to be confidential.' }) + .optional(), + description: z + .string() + .describe('The description of an issue. Limited to 1,048,576 characters.') + .max(1048576) + .optional(), + discussionLocked: z + .boolean({ + description: + 'Flag indicating if the issue’s discussion is locked. If the discussion is locked only project members can add or edit comments.', + }) + .optional(), + dueDate: z + .string() + .describe( + 'The due date. Date time string in the format YYYY-MM-DD, for example 2016-03-11.', + ) + .regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format. Use YYYY-MM-DD') + .optional(), + epicId: z + .number({ + description: + 'ID of the epic to add the issue to. Valid values are greater than or equal to 0.', + }) + .min(0, 'Valid values should be equal or greater than zero') + .optional(), + issueType: z + .nativeEnum(IssueType, { + description: + 'Updates the type of issue. One of issue, incident, test_case or task.', + }) + .optional(), + labels: z + .string({ + description: + 'Comma-separated label names for an issue. Set to an empty string to unassign all labels. If a label does not already exist, this creates a new project label and assigns it to the issue.', + }) + .optional(), + milestoneId: z + .number({ + description: + 'The global ID of a milestone to assign the issue to. Set to 0 or provide an empty value to unassign a milestone', + }) + .optional(), + removeLabels: z + .string({ + description: 'Comma-separated label names to remove from an issue.', + }) + .optional(), + stateEvent: z + .nativeEnum(IssueStateEvent, { + description: + 'The state event of an issue. To close the issue, use close, and to reopen it, use reopen.', + }) + .optional(), + title: z.string().describe('The title of an issue.').optional(), + updatedAt: z + .string() + .describe( + 'When the issue was updated. Date time string, ISO 8601 formatted', + ) + .regex( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, + 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', + ) + .optional(), + weight: z + .number({ description: 'The issue weight' }) + .min(0, 'Valid values should be equal or greater than zero') + .max(10, 'Valid values should be equal or less than 10') + .optional(), +}); + +const editIssueOutputProperties = z.object({ + issueUrl: z.string({ description: 'Issue WebUrl' }), + projectId: z.number({ + description: 'The project id the issue belongs to WebUrl', + }), + issueId: z.number({ description: 'The issues Id' }), + issueIid: z.number({ + description: "The issues internal ID of a project's issue", + }), + state: z.string({ description: 'The state event of an issue' }), + title: z.string({ description: 'The title of an issue.' }), + updatedAt: z.string({ description: 'The last updated time of the issue.' }), +}); + +/** + * Creates a `gitlab:issues:edit` Scaffolder action. + * + * @param options - Templating configuration. + * @public + */ +export const editGitlabIssueAction = (options: { + integrations: ScmIntegrationRegistry; +}) => { + const { integrations } = options; + return createTemplateAction({ + id: 'gitlab:issues:edit', + description: 'Edit a Gitlab issue.', + examples, + schema: { + input: commonGitlabConfig.merge(editIssueInputProperties), + output: editIssueOutputProperties, + }, + async handler(ctx) { + try { + const { + repoUrl, + projectId, + title, + addLabels, + removeLabels, + issueIid, + description, + confidential = false, + assignees = [], + updatedAt = '', + dueDate, + discussionLocked = false, + epicId, + labels, + issueType, + milestoneId, + stateEvent, + weight, + token, + } = commonGitlabConfig.merge(editIssueInputProperties).parse(ctx.input); + + const { host } = parseRepoUrl(repoUrl, integrations); + const api = getClient({ host, integrations, token }); + + let isEpicScoped = false; + + if (epicId) { + isEpicScoped = await checkEpicScope(api, projectId, epicId); + + if (isEpicScoped) { + ctx.logger.info('Epic is within Project Scope'); + } else { + ctx.logger.warn( + 'Chosen epic is not within the Project Scope. The issue will be created without an associated epic.', + ); + } + } + + const mappedUpdatedAt = convertDate( + String(updatedAt), + new Date().toISOString(), + ); + + const editIssueOptions: EditIssueOptions = { + addLabels, + assigneeIds: assignees, + confidential, + description, + discussionLocked, + dueDate, + epicId: isEpicScoped ? epicId : undefined, + issueType, + labels, + milestoneId, + removeLabels, + stateEvent, + title, + updatedAt: mappedUpdatedAt, + weight, + }; + + const response = (await api.Issues.edit( + projectId, + issueIid, + editIssueOptions, + )) as IssueSchema; + + ctx.output('issueId', response.id); + ctx.output('projectId', response.project_id); + ctx.output('issueUrl', response.web_url); + ctx.output('issueIid', response.iid); + ctx.output('title', response.title); + ctx.output('state', response.state); + ctx.output('updatedAt', response.updated_at); + } catch (error: any) { + if (error instanceof z.ZodError) { + // Handling Zod validation errors + throw new InputError(`Validation error: ${error.message}`, { + validationErrors: error.errors, + }); + } + // Handling other errors + throw new InputError( + `Failed to edit/modify GitLab issue: ${error.message}`, + ); + } + }, + }); +}; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts index c5c24772ef..c601c4656e 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts @@ -16,9 +16,11 @@ export * from './gitlab'; export * from './gitlabGroupEnsureExists'; export * from './gitlabIssueCreate'; +export * from './gitlabIssueEdit'; export * from './gitlabMergeRequest'; export * from './gitlabPipelineTrigger'; export * from './gitlabProjectAccessTokenCreate'; export * from './gitlabProjectDeployTokenCreate'; export * from './gitlabProjectVariableCreate'; export * from './gitlabRepoPush'; +export { IssueType, IssueStateEvent } from '../commonGitlabConfig'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts b/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts index 3c83eacf19..5d0528bad4 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts @@ -29,3 +29,25 @@ export const commonGitlabConfigExample = { repoUrl: 'gitlab.com?owner=namespace-or-owner&repo=project-name', token: '${{ secrets.USER_OAUTH_TOKEN }}', }; + +/** + * Gitlab issue types as specified by gitlab api + * + * @public + */ +export enum IssueType { + ISSUE = 'issue', + INCIDENT = 'incident', + TEST = 'test_case', + TASK = 'task', +} + +/** + * Gitlab issue state events for modifications + * + * @public + */ +export enum IssueStateEvent { + CLOSE = 'close', + REOPEN = 'reopen', +} diff --git a/plugins/scaffolder-backend-module-gitlab/src/module.ts b/plugins/scaffolder-backend-module-gitlab/src/module.ts index 17052b987f..78175532ed 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/module.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/module.ts @@ -29,6 +29,7 @@ import { createPublishGitlabAction, createPublishGitlabMergeRequestAction, createTriggerGitlabPipelineAction, + editGitlabIssueAction, } from './actions'; /** @@ -54,6 +55,7 @@ export const gitlabModule = createBackendModule({ createGitlabProjectDeployTokenAction({ integrations }), createGitlabProjectVariableAction({ integrations }), createGitlabRepoPushAction({ integrations }), + editGitlabIssueAction({ integrations }), createPublishGitlabAction({ config, integrations }), createPublishGitlabMergeRequestAction({ integrations }), createTriggerGitlabPipelineAction({ integrations }), diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 5c766af890..28e1eb92cb 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -397,11 +397,11 @@ export const repoPickerValidation: ( export const RepoUrlPickerFieldExtension: FieldExtensionComponent_2< string, { - allowedHosts?: string[] | undefined; - allowedOrganizations?: string[] | undefined; allowedOwners?: string[] | undefined; + allowedOrganizations?: string[] | undefined; allowedProjects?: string[] | undefined; allowedRepos?: string[] | undefined; + allowedHosts?: string[] | undefined; requestUserCredentials?: | { secretsKey: string; @@ -409,9 +409,9 @@ export const RepoUrlPickerFieldExtension: FieldExtensionComponent_2< | { azure?: string[] | undefined; github?: string[] | undefined; - gitlab?: string[] | undefined; bitbucket?: string[] | undefined; gerrit?: string[] | undefined; + gitlab?: string[] | undefined; gitea?: string[] | undefined; } | undefined; @@ -424,11 +424,11 @@ export const RepoUrlPickerFieldExtension: FieldExtensionComponent_2< export const RepoUrlPickerFieldSchema: FieldSchema< string, { - allowedHosts?: string[] | undefined; - allowedOrganizations?: string[] | undefined; allowedOwners?: string[] | undefined; + allowedOrganizations?: string[] | undefined; allowedProjects?: string[] | undefined; allowedRepos?: string[] | undefined; + allowedHosts?: string[] | undefined; requestUserCredentials?: | { secretsKey: string; @@ -436,9 +436,9 @@ export const RepoUrlPickerFieldSchema: FieldSchema< | { azure?: string[] | undefined; github?: string[] | undefined; - gitlab?: string[] | undefined; bitbucket?: string[] | undefined; gerrit?: string[] | undefined; + gitlab?: string[] | undefined; gitea?: string[] | undefined; } | undefined;