Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrate the changelog command #21

Merged
merged 5 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ SLACK_BOT_USER_O_AUTH_ACCESS_TOKEN=SLACK_BOT_USER_O_AUTH_ACCESS_TOKEN
SLACK_SIGNING_SECRET=SLACK_SIGNING_SECRET
EMAIL_DOMAINS=my-domain.com,ext.my-domain.com
GITLAB_URL=https://my-git.domain.com
TICKET_MANAGEMENT_URL_PATTERN=https://my-ticket-management.com/view/{ticketId}
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ Create a `.env` file containing the following variables:

The gitlab URL of your organization

- `TICKET_MANAGEMENT_URL_PATTERN`

The ticket management URL pattern for your organization, this is used to generate a link to the ticket in the changelog.
It must contain the `{ticketId}` matcher to be replaced by the ticket ID, for instance `https://my-ticket-management.com/view/{ticketId}`.

If you want Homer to connect to an **external PostgreSQL database**, you can set
the following variables:

Expand Down
181 changes: 181 additions & 0 deletions src/changelog/buildChangelogModalView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import type { Block, KnownBlock, View } from '@slack/types';
import { generateChangelog } from '@/changelog/utils/generateChangelog';
import { slackifyChangelog } from '@/changelog/utils/slackifyChangelog';
import { getProjectsByChannelId } from '@/core/services/data';
import { fetchProjectById, fetchProjectTags } from '@/core/services/gitlab';
import type { SlackOption } from '@/core/typings/SlackOption';

interface ChangelogModalData {
channelId?: string;
projectId?: number;
projectOptions?: SlackOption[];
releaseTagName?: string;
}

export async function buildChangelogModalView({
channelId,
projectId,
projectOptions,
releaseTagName,
}: ChangelogModalData): Promise<View> {
if (channelId !== undefined && projectOptions === undefined) {
const dataProjects = await getProjectsByChannelId(channelId);
const projects = await Promise.all(
dataProjects.map(async (dataProject) =>
fetchProjectById(Number(dataProject.projectId))
)
);

projectOptions = projects
.sort((a, b) =>
a.path_with_namespace.localeCompare(b.path_with_namespace)
)
.map((project) => ({
text: {
type: 'plain_text',
text: project.path_with_namespace,
},
value: project.id.toString(),
})) as SlackOption[];
}

if (!projectOptions || projectOptions.length === 0) {
throw new Error(
'No Gitlab project has been found on this channel :homer-stressed:'
);
}

if (projectId === undefined) {
projectId = parseInt(projectOptions[0].value, 10);
}

const tags = (await fetchProjectTags(projectId)).slice(0, 3);
const previousReleaseTagName = releaseTagName ?? tags[0]?.name;
const changelog = previousReleaseTagName
? await generateChangelog(projectId, previousReleaseTagName)
: '';

const previousReleaseOptions = tags.map(({ name }) => ({
text: {
type: 'plain_text',
text: name,
},
value: name,
})) as SlackOption[];

return {
type: 'modal',
callback_id: 'changelog-modal',
title: {
type: 'plain_text',
text: 'Changelog',
},
submit: {
type: 'plain_text',
text: 'Ok',
},
notify_on_close: false,
blocks: [
{
type: 'input',
block_id: 'changelog-project-block',
dispatch_action: true,
element: {
type: 'static_select',
action_id: 'changelog-select-project-action',
options: projectOptions,
initial_option: projectOptions?.[0],
placeholder: {
type: 'plain_text',
text: 'Select the project',
},
},
label: {
type: 'plain_text',
text: 'Project',
},
},
previousReleaseOptions.length > 0
? [
{
type: 'input',
block_id: 'changelog-release-tag-block',
dispatch_action: true,
element: {
type: 'static_select',
action_id: 'changelog-select-release-tag-action',
initial_option: previousReleaseOptions[0],
options: previousReleaseOptions,
placeholder: {
type: 'plain_text',
text: 'Select the previous release tag',
},
},
label: {
type: 'plain_text',
text: 'Previous release tag',
},
},
{
type: 'context',
block_id: 'changelog-release-tag-info-block',
elements: [
{
type: 'plain_text',
text: 'This should be changed only if the previous release has been aborted.',
},
],
},
]
: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*Previous release tag*',
},
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: 'No previous release tag has been found.',
},
},
],
{
type: 'section',
block_id: 'changelog-preview-title-block',
text: {
type: 'mrkdwn',
text: '*Preview*',
},
},
{
type: 'section',
block_id: 'changelog-preview-block',
text: {
type: 'mrkdwn',
text: changelog
? slackifyChangelog(changelog)
: 'No change has been found.',
},
},
changelog && {
type: 'input',
block_id: 'changelog-markdown-block',
label: {
type: 'plain_text',
text: 'Markdown',
},
element: {
type: 'plain_text_input',
multiline: true,
initial_value: changelog,
},
},
]
.flat()
.filter(Boolean) as (KnownBlock | Block)[],
};
}
27 changes: 27 additions & 0 deletions src/changelog/changelogBlockActionsHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { logger } from '@/core/services/logger';
import type { BlockActionsPayload } from '@/core/typings/BlockActionPayload';
import { updateChangelog } from './utils/updateChangelog';
import { updateChangelogProject } from './utils/updateChangelogProject';

export async function changelogBlockActionsHandler(
payload: BlockActionsPayload
): Promise<void> {
await Promise.all(
payload.actions.map(async (action) => {
const { action_id } = action;

switch (action_id) {
case 'changelog-select-project-action':
return updateChangelogProject(payload);

case 'changelog-select-release-tag-action':
return updateChangelog(payload);

default:
logger.error(
new Error(`Unknown changelog block action: ${action_id}`)
);
}
})
);
}
27 changes: 27 additions & 0 deletions src/changelog/changelogRequestHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Response } from 'express';
import { GENERIC_ERROR_MESSAGE, HTTP_STATUS_NO_CONTENT } from '@/constants';
import { slackBotWebClient } from '@/core/services/slack';
import type { SlackExpressRequest } from '@/core/typings/SlackSlashCommand';
import { buildChangelogModalView } from './buildChangelogModalView';

export async function changelogRequestHandler(
req: SlackExpressRequest,
res: Response
) {
const { channel_id, trigger_id, user_id } = req.body;

res.sendStatus(HTTP_STATUS_NO_CONTENT);

try {
await slackBotWebClient.views.open({
trigger_id,
view: await buildChangelogModalView({ channelId: channel_id }),
});
} catch (error) {
await slackBotWebClient.chat.postEphemeral({
channel: channel_id,
user: user_id,
text: error instanceof Error ? error.message : GENERIC_ERROR_MESSAGE,
});
}
}
Loading
Loading