wip: add features
Signed-off-by: Elaine Mattos <elaine.mattos@gmail.com>
This commit is contained in:
committed by
Elaine Mattos
parent
96b8bf417c
commit
8d5867e781
+2
-1
@@ -60,7 +60,8 @@ export const createGitlabGroupEnsureExistsAction = (options: {
|
||||
|
||||
const { path } = ctx.input;
|
||||
const { token, integrationConfig } = getToken(ctx.input, integrations);
|
||||
|
||||
console.log(`THIS IS THE TOKEN ${token}`);
|
||||
console.log(`THIS IS THE PATH ${path}`);
|
||||
const api = new Gitlab({
|
||||
host: integrationConfig.config.baseUrl,
|
||||
token: token,
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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: 'Create a GitLab issue with minimal options',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'gitlabIssue',
|
||||
name: 'Issues',
|
||||
action: 'gitlab:issues:create',
|
||||
input: {
|
||||
...commonGitlabConfigExample,
|
||||
projectId: 12,
|
||||
title: 'Test Issue',
|
||||
description: 'This is the description of the issue',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Create a GitLab issue with assignees and date options',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'gitlabIssue',
|
||||
name: 'Issues',
|
||||
action: 'gitlab:issues:create',
|
||||
input: {
|
||||
...commonGitlabConfigExample,
|
||||
projectId: 12,
|
||||
title: 'Test Issue',
|
||||
assignees: -18,
|
||||
description: 'This is the description of the issue',
|
||||
createdAt: '2022-09-27 18:00:00.000',
|
||||
dueDate: '2022-09-28 12:00:00.000',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Create a GitLab Issue with several options',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'gitlabIssue',
|
||||
name: 'Issues',
|
||||
action: 'gitlab:issues:create',
|
||||
input: {
|
||||
...commonGitlabConfigExample,
|
||||
projectId: 12,
|
||||
title: 'Test Issue',
|
||||
assignees: `
|
||||
- 18
|
||||
- 15 `,
|
||||
description: 'This is the description of the issue',
|
||||
confidential: false,
|
||||
createdAt: '2022-09-27 18:00:00.000',
|
||||
dueDate: '2022-09-28 12:00:00.000',
|
||||
discussionToResolve: 1,
|
||||
epicId: 1,
|
||||
labels: 'phase1:label1,phase2:label2',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
];
|
||||
@@ -16,14 +16,9 @@
|
||||
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import {
|
||||
TemplateExample,
|
||||
createTemplateAction,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import * as yaml from 'yaml';
|
||||
import commonGitlabConfig, {
|
||||
commonGitlabConfigExample,
|
||||
} from '../commonGitlabConfig';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import commonGitlabConfig from '../commonGitlabConfig';
|
||||
import { examples } from './createGitlabIssueAction.examples';
|
||||
import { z } from 'zod';
|
||||
import { checkEpicScope, convertDate, getClient, parseRepoUrl } from '../util';
|
||||
import { Gitlab, CreateIssueOptions, IssueSchema } from '@gitbeaker/rest';
|
||||
@@ -35,6 +30,100 @@ import { Gitlab, CreateIssueOptions, IssueSchema } from '@gitbeaker/rest';
|
||||
* @public
|
||||
*/
|
||||
|
||||
const enumIssueType = {
|
||||
ISSUE: 'issue',
|
||||
INCIDENT: 'incident',
|
||||
TEST: 'test_case',
|
||||
};
|
||||
|
||||
const issueInputProperties = z.object({
|
||||
projectId: z.number().describe('Project Id'),
|
||||
title: z.string({ description: 'Title of the issue' }),
|
||||
assignees: z
|
||||
.array(z.number(), {
|
||||
description: 'IDs of the users to assign the issue to.',
|
||||
})
|
||||
.optional(),
|
||||
confidential: z.boolean({ description: 'Issue Confidentiality' }).optional(),
|
||||
description: z.string().describe('Issue description').max(1048576).optional(),
|
||||
createdAt: z
|
||||
.string()
|
||||
.describe('Creation date/time')
|
||||
.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(),
|
||||
dueDate: z
|
||||
.string()
|
||||
.describe('Due date/time')
|
||||
.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(),
|
||||
discussionToResolve: z
|
||||
.string({
|
||||
description:
|
||||
'Id of a discussion to resolve. Use in combination with "merge_request_to_resolve_discussions_of"',
|
||||
})
|
||||
.optional(),
|
||||
epicId: z
|
||||
.number({ description: 'Id of the linked Epic' })
|
||||
.min(0, 'Valid values should be equal or greater than zero')
|
||||
.optional(),
|
||||
labels: z.string({ description: 'Labels to apply' }).optional(),
|
||||
issueType: z
|
||||
.string({
|
||||
description: 'Type of the issue',
|
||||
})
|
||||
.refine(issueType => {
|
||||
const isValid = Object.values(enumIssueType).includes(issueType);
|
||||
if (!isValid) {
|
||||
throw new z.ZodError([
|
||||
{
|
||||
code: 'invalid_enum_value',
|
||||
options: Object.values(enumIssueType),
|
||||
path: ['issueType'],
|
||||
message: `Invalid value for 'issueType'. Must be one of: ${Object.values(
|
||||
enumIssueType,
|
||||
).join(', ')}`,
|
||||
received: issueType,
|
||||
},
|
||||
]);
|
||||
}
|
||||
return isValid;
|
||||
})
|
||||
.default(enumIssueType.ISSUE)
|
||||
.optional(),
|
||||
mergeRequestToResolveDiscussionsOf: z
|
||||
.number({
|
||||
description: 'IID of a merge request in which to resolve all issues',
|
||||
})
|
||||
.optional(),
|
||||
milestoneId: z
|
||||
.number({ description: 'Global ID of a milestone to assign the issue' })
|
||||
.optional(),
|
||||
weight: z
|
||||
.number({ description: 'The issue weight' })
|
||||
.min(0)
|
||||
.refine(value => {
|
||||
const isValid = value >= 0;
|
||||
if (!isValid) {
|
||||
return {
|
||||
message: 'Valid values should be equal or greater than zero',
|
||||
};
|
||||
}
|
||||
return isValid;
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const issueOutputProperties = z.object({
|
||||
issueUrl: z.string({ description: 'Issue Url' }),
|
||||
issueId: z.number({ description: 'Issue Id' }),
|
||||
});
|
||||
|
||||
export const createGitlabIssueAction = (options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}) => {
|
||||
@@ -42,38 +131,10 @@ export const createGitlabIssueAction = (options: {
|
||||
return createTemplateAction({
|
||||
id: 'gitlab:issues:create',
|
||||
description: 'Creates a Gitlab issue.',
|
||||
examples: getExamples(),
|
||||
examples,
|
||||
schema: {
|
||||
input: commonGitlabConfig.merge(
|
||||
z.object({
|
||||
projectId: z.number({ description: 'Project Id' }),
|
||||
title: z.string({ description: 'Title of the issue' }),
|
||||
assignees: z
|
||||
.array(z.number(), {
|
||||
description: 'IDs of the users to assign the issue to.',
|
||||
})
|
||||
.optional(),
|
||||
confidential: z
|
||||
.boolean({ description: 'Issue Confidentiality' })
|
||||
.optional(),
|
||||
description: z
|
||||
.string({ description: 'Issue description' })
|
||||
.optional(),
|
||||
createdAt: z.string({ description: 'Creation date/time' }).optional(),
|
||||
dueDate: z.string({ description: 'Due date/time' }).optional(),
|
||||
discussionToResolve: z
|
||||
.string({
|
||||
description: 'Id of a discussion to resolve',
|
||||
})
|
||||
.optional(),
|
||||
epicId: z.number({ description: 'Id of the linked Epic' }).optional(),
|
||||
labels: z.string({ description: 'Labels to apply' }).optional(),
|
||||
}),
|
||||
),
|
||||
output: z.object({
|
||||
issueUrl: z.string({ description: 'Issue Url' }),
|
||||
issueId: z.number({ description: 'Issue Id' }),
|
||||
}),
|
||||
input: commonGitlabConfig.merge(issueInputProperties),
|
||||
output: issueOutputProperties,
|
||||
},
|
||||
async handler(ctx) {
|
||||
try {
|
||||
@@ -89,8 +150,12 @@ export const createGitlabIssueAction = (options: {
|
||||
discussionToResolve = '',
|
||||
epicId,
|
||||
labels = '',
|
||||
issueType,
|
||||
mergeRequestToResolveDiscussionsOf,
|
||||
milestoneId,
|
||||
weight,
|
||||
token,
|
||||
} = ctx.input;
|
||||
} = commonGitlabConfig.merge(issueInputProperties).parse(ctx.input);
|
||||
|
||||
const { host } = parseRepoUrl(repoUrl, integrations);
|
||||
|
||||
@@ -113,6 +178,8 @@ export const createGitlabIssueAction = (options: {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: do I really need the convertDate?
|
||||
const mappedCreatedAt = convertDate(
|
||||
String(createdAt),
|
||||
new Date().toISOString(),
|
||||
@@ -130,6 +197,10 @@ export const createGitlabIssueAction = (options: {
|
||||
createdAt: mappedCreatedAt,
|
||||
dueDate: mappedDueDate,
|
||||
discussionToResolve,
|
||||
issueType,
|
||||
mergeRequestToResolveDiscussionsOf,
|
||||
milestoneId,
|
||||
weight,
|
||||
};
|
||||
|
||||
const response = (await api.Issues.create(
|
||||
@@ -141,79 +212,13 @@ export const createGitlabIssueAction = (options: {
|
||||
ctx.output('issueId', response.id);
|
||||
ctx.output('issueUrl', response.web_url);
|
||||
} catch (error: any) {
|
||||
if (error instanceof z.ZodError) {
|
||||
// Handling Zod validation errors
|
||||
throw new InputError(`Validation error: ${error.message}`);
|
||||
}
|
||||
// Handling other errors
|
||||
throw new InputError(`Failed to create GitLab issue: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
function getExamples(): TemplateExample[] {
|
||||
return [
|
||||
{
|
||||
description: 'Create a GitLab issue with minimal options',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'gitlabIssue',
|
||||
name: 'Issues',
|
||||
action: 'gitlab:issues:create',
|
||||
input: {
|
||||
...commonGitlabConfigExample,
|
||||
projectId: 12,
|
||||
title: 'Test Issue',
|
||||
description: 'This is the description of the issue',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Create a GitLab issue with assignees and date options',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'gitlabIssue',
|
||||
name: 'Issues',
|
||||
action: 'gitlab:issues:create',
|
||||
input: {
|
||||
...commonGitlabConfigExample,
|
||||
projectId: 12,
|
||||
title: 'Test Issue',
|
||||
assignees: -18,
|
||||
description: 'This is the description of the issue',
|
||||
createdAt: '2022-09-27 18:00:00.000',
|
||||
dueDate: '2022-09-28 12:00:00.000',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Create a GitLab Issue with several options',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'gitlabIssue',
|
||||
name: 'Issues',
|
||||
action: 'gitlab:issues:create',
|
||||
input: {
|
||||
...commonGitlabConfigExample,
|
||||
projectId: 12,
|
||||
title: 'Test Issue',
|
||||
assignees: `
|
||||
- 18
|
||||
- 15 `,
|
||||
description: 'This is the description of the issue',
|
||||
confidential: false,
|
||||
createdAt: '2022-09-27 18:00:00.000',
|
||||
dueDate: '2022-09-28 12:00:00.000',
|
||||
discussionToResolve: 1,
|
||||
epicId: 1,
|
||||
labels: 'phase1:label1,phase2:label2',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2022 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 {
|
||||
// parseRepoHost,
|
||||
// getToken,
|
||||
// convertDate,
|
||||
getTopLevelParentGroup,
|
||||
} from './util';
|
||||
import { Gitlab, GroupSchema } from '@gitbeaker/rest';
|
||||
// import { ScmIntegrations } from '@backstage/integration';
|
||||
// import { ConfigReader } from '@backstage/config';
|
||||
|
||||
// Mock the Gitlab client and its methods
|
||||
jest.mock('@gitbeaker/rest', () => {
|
||||
const mockShow = jest.fn();
|
||||
return {
|
||||
Gitlab: jest.fn().mockImplementation(() => ({
|
||||
Groups: {
|
||||
show: mockShow,
|
||||
},
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
describe('getTopLevelParentGroup', () => {
|
||||
const config = {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
token: 'withToken',
|
||||
apiBaseUrl: 'gitlab.com/api/v4',
|
||||
},
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
apiBaseUrl: 'gitlab.com/api/v4',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
it('should return top-level parent group when parent_id is present', async () => {
|
||||
const mockGroupId = 123;
|
||||
const mockParentGroupId = 456;
|
||||
|
||||
const mockTopParentGroup: GroupSchema = {
|
||||
id: mockGroupId,
|
||||
parent_id: mockParentGroupId,
|
||||
path: '',
|
||||
description: '',
|
||||
visibility: '',
|
||||
share_with_group_lock: false,
|
||||
require_two_factor_authentication: false,
|
||||
two_factor_grace_period: 0,
|
||||
project_creation_level: '',
|
||||
subgroup_creation_level: '',
|
||||
lfs_enabled: false,
|
||||
default_branch_protection: 0,
|
||||
request_access_enabled: false,
|
||||
created_at: '',
|
||||
avatar_url: '',
|
||||
full_name: '',
|
||||
full_path: '',
|
||||
web_url: '',
|
||||
name: '',
|
||||
};
|
||||
|
||||
// Instance with token
|
||||
const mockGitlabClient = new Gitlab({
|
||||
host: config.gitlab[0].host,
|
||||
token: config.gitlab[0].token!,
|
||||
});
|
||||
|
||||
const action = getTopLevelParentGroup(mockGitlabClient, mockGroupId);
|
||||
|
||||
const result = await action; // Await the result
|
||||
|
||||
expect(result).toEqual(mockTopParentGroup);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user