Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ node_modules
.cache
.DS_Store
.idea/
*.iml
7 changes: 7 additions & 0 deletions __tests__/__fixtures__/reviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ export const reviewMessagePostFixture = {
accessory: {
action_id: 'review-message-actions',
options: [
{
text: {
type: 'plain_text',
text: 'Refresh',
},
value: 'review-refresh~~1148~~1',
},
{
text: {
type: 'plain_text',
Expand Down
109 changes: 109 additions & 0 deletions __tests__/review/refreshReview.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import request from 'supertest';
import { app } from '@/app';
import { HTTP_STATUS_OK } from '@/constants';
import { addReviewToChannel } from '@/core/services/data';
import { slackBotWebClient } from '@/core/services/slack';
import { mergeRequestDetailsFixture } from '../__fixtures__/mergeRequestDetailsFixture';
import { mergeRequestFixture } from '../__fixtures__/mergeRequestFixture';
import { getSlackHeaders } from '../utils/getSlackHeaders';
import { mockBuildReviewMessageCalls } from '../utils/mockBuildReviewMessageCalls';
import { mockGitlabCall } from '../utils/mockGitlabCall';

describe('review > refreshReview', () => {
const channelId = 'channelId';
const mergeRequestIid = mergeRequestFixture.iid;
const projectId = mergeRequestFixture.project_id;
const ts = 'ts';
const userId = 'userId';

const buildBody = () => ({
payload: JSON.stringify({
actions: [
{
action_id: 'review-message-actions',
selected_option: {
value: `review-refresh~~${projectId}~~${mergeRequestIid}`,
},
},
],
channel: { id: channelId },
message: { ts },
type: 'block_actions',
user: { id: userId },
}),
});

const permalink =
'https://manomano-team.slack.com/archives/CKXA1FASF/p1640343776000900';

beforeEach(() => {
(slackBotWebClient.users.lookupByEmail as jest.Mock).mockImplementation(
({ email }: { email: string }) => {
const name = email.split('@')[0];
return Promise.resolve({
user: {
name,
profile: { image_72: 'image_72' },
real_name: `${name}.real`,
},
});
},
);
(slackBotWebClient.chat.update as jest.Mock).mockResolvedValue({});
(slackBotWebClient.chat.getPermalink as jest.Mock).mockResolvedValue({
permalink,
});
});

it('should refresh the review message and keep it in DB when MR is merged', async () => {
mockBuildReviewMessageCalls();
mockGitlabCall(`/projects/${projectId}/merge_requests/${mergeRequestIid}`, {
...mergeRequestDetailsFixture,
state: 'merged',
});

await addReviewToChannel({ channelId, mergeRequestIid, projectId, ts });
const body = buildBody();

const response = await request(app)
.post('/api/v1/homer/interactive')
.set(getSlackHeaders(body))
.send(body);

const { hasModelEntry } = (await import('sequelize')) as any;
expect(response.status).toEqual(HTTP_STATUS_OK);
expect(slackBotWebClient.chat.update).toHaveBeenCalled();
expect(slackBotWebClient.chat.postEphemeral).toHaveBeenNthCalledWith(1, {
channel: channelId,
user: userId,
text: expect.stringContaining(permalink),
});
expect(
await hasModelEntry('Review', { channelId, mergeRequestIid, ts }),
).toEqual(true);
});

it('should refresh the review message and keep it in DB when MR is still open', async () => {
mockBuildReviewMessageCalls(); // mergeRequestDetailsFixture has state: 'opened'

await addReviewToChannel({ channelId, mergeRequestIid, projectId, ts });
const body = buildBody();

const response = await request(app)
.post('/api/v1/homer/interactive')
.set(getSlackHeaders(body))
.send(body);

const { hasModelEntry } = (await import('sequelize')) as any;
expect(response.status).toEqual(HTTP_STATUS_OK);
expect(slackBotWebClient.chat.update).toHaveBeenCalled();
expect(slackBotWebClient.chat.postEphemeral).toHaveBeenNthCalledWith(1, {
channel: channelId,
user: userId,
text: expect.stringContaining(permalink),
});
expect(
await hasModelEntry('Review', { channelId, mergeRequestIid, ts }),
).toEqual(true);
});
});
7 changes: 5 additions & 2 deletions src/review/commands/share/utils/handleMessageActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import type { BlockActionsPayloadWithChannel } from '@/core/typings/BlockActionP
import type { StaticSelectAction } from '@/core/typings/StaticSelectAction';
import { createPipeline } from './createPipeline';
import { rebaseSourceBranch } from './rebaseSourceBranch';
import { refreshReview } from './refreshReview';

export async function handleMessageAction(
payload: BlockActionsPayloadWithChannel,
action: StaticSelectAction
action: StaticSelectAction,
) {
const mergeRequestAction = action.selected_option.value;

Expand All @@ -22,9 +23,11 @@ export async function handleMessageAction(
await removeReview(ts);
} else if (mergeRequestAction.startsWith('review-rebase-source-branch')) {
await rebaseSourceBranch(payload, action);
} else if (mergeRequestAction.startsWith('review-refresh')) {
await refreshReview(payload, action);
} else {
logger.error(
new Error(`Unknown review message action: ${mergeRequestAction}.`)
new Error(`Unknown review message action: ${mergeRequestAction}.`),
);
}
}
42 changes: 42 additions & 0 deletions src/review/commands/share/utils/refreshReview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { logger } from '@/core/services/logger';
import { getPermalink, slackBotWebClient } from '@/core/services/slack';
import type { BlockActionsPayloadWithChannel } from '@/core/typings/BlockActionPayload';
import type { StaticSelectAction } from '@/core/typings/StaticSelectAction';
import { extractActionParameters } from '@/core/utils/slackActions';
import { buildReviewMessage } from '../viewBuilders/buildReviewMessage';

export async function refreshReview(
payload: BlockActionsPayloadWithChannel,
action: StaticSelectAction,
) {
const mergeRequestAction = action.selected_option.value;
const { channel, user, message } = payload;
const [projectIdStr, mergeRequestIidStr] =
extractActionParameters(mergeRequestAction);

if (!projectIdStr || !mergeRequestIidStr) {
logger.error(
new Error(
`Unable to get projectId and mergeRequestIid for action ${mergeRequestAction}.`,
),
);
return;
}

const projectId = parseInt(projectIdStr, 10);
const mergeRequestIid = parseInt(mergeRequestIidStr, 10);
const { ts } = message;

const [reviewMessage, permalink] = await Promise.all([
buildReviewMessage(channel.id, projectId, mergeRequestIid, ts),
getPermalink(channel.id, ts),
]);

await slackBotWebClient.chat.update(reviewMessage);

await slackBotWebClient.chat.postEphemeral({
channel: channel.id,
user: user.id,
text: `Review message refreshed :homer-happy: ${permalink}`,
});
}
8 changes: 8 additions & 0 deletions src/review/commands/share/viewBuilders/buildReviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ function buildHeaderBlock(
type: 'overflow',
action_id: 'review-message-actions',
options: [
{
text: { type: 'plain_text', text: 'Refresh' },
value: injectActionsParameters(
'review-refresh',
projectId,
mergeRequest.iid,
),
},
{
text: { type: 'plain_text', text: 'Create a pipeline' },
value: injectActionsParameters(
Expand Down
Loading