Skip to content

Commit f185c99

Browse files
authored
Merge pull request #336 from switcherapi/feat_335
Closes #335 - Added Slack/Settings ignored/frozen envs
2 parents b2bd4c1 + 6471b4e commit f185c99

File tree

8 files changed

+135
-30
lines changed

8 files changed

+135
-30
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Switching fast. Adapt everywhere.
1111
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=switcherapi_switcher-api&metric=alert_status)](https://sonarcloud.io/dashboard?id=switcherapi_switcher-api)
1212
[![Known Vulnerabilities](https://snyk.io/test/github/switcherapi/switcher-api/badge.svg)](https://snyk.io/test/github/switcherapi/switcher-api)
1313
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
14+
[![Docker Hub](https://img.shields.io/docker/pulls/trackerforce/switcher-api.svg)](https://hub.docker.com/r/trackerforce/switcher-api)
1415
[![Slack: Switcher-HQ](https://img.shields.io/badge/slack-@switcher/hq-blue.svg?logo=slack)](https://switcher-hq.slack.com/)
1516

1617
</div>

src/api-docs/schemas/slack.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@ const ticket = {
2828
enum: Object.values(TicketStatusType),
2929
default: TicketStatusType.OPENED
3030
},
31-
ticket_approvals: {
32-
type: 'number',
33-
description: 'The number of approvals'
34-
},
3531
date_closed: {
3632
type: 'string',
3733
description: 'The date closed'
@@ -175,9 +171,19 @@ const slack = {
175171
settings: {
176172
type: 'object',
177173
properties: {
178-
approvals: {
179-
type: 'number',
180-
description: 'The number of approvals required to approve a ticket'
174+
ignored_environments: {
175+
type: 'array',
176+
description: 'Environments that should be ignored for the approval request',
177+
items: {
178+
type: 'string'
179+
}
180+
},
181+
frozen_environments: {
182+
type: 'array',
183+
description: 'Environments that should not change',
184+
items: {
185+
type: 'string'
186+
}
181187
}
182188
}
183189
},

src/models/slack.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ const slackSchema = new mongoose.Schema({
2828
type: Object
2929
},
3030
settings: {
31-
approvals: {
32-
type: Number,
33-
required: true,
34-
default: 1
35-
}
31+
ignored_environments: [{
32+
type: String
33+
}],
34+
frozen_environments: [{
35+
type: String
36+
}]
3637
},
3738
tickets: [slackTicketSchema]
3839
}, {

src/models/slack_ticket.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ export const TicketStatusType = Object.freeze({
77
DENIED: 'DENIED'
88
});
99

10+
export const TicketValidationType = Object.freeze({
11+
VALIDATED: 'VALIDATED',
12+
IGNORED_ENVIRONMENT: 'IGNORED_ENVIRONMENT',
13+
FROZEN_ENVIRONMENT: 'FROZEN_ENVIRONMENT'
14+
});
15+
1016
export const slackTicketSchema = new mongoose.Schema({
1117
environment: {
1218
type: String,
@@ -32,10 +38,6 @@ export const slackTicketSchema = new mongoose.Schema({
3238
default: TicketStatusType.OPENED,
3339
required: true
3440
},
35-
ticket_approvals: {
36-
type: Number,
37-
default: 0
38-
},
3941
date_closed: {
4042
type: Date
4143
}

src/routers/slack.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,10 @@ router.post('/slack/v1/ticket/validate', slackAuth, [
103103
], validate, async (req, res) => {
104104
try {
105105
const ticket_content = createTicketContent(req);
106-
await Services.validateTicket(
106+
const validation = await Services.validateTicket(
107107
ticket_content, req.body.enterprise_id, req.body.team_id);
108-
109-
res.status(200).send({ message: 'Ticket verified' });
108+
109+
res.status(200).send({ message: 'Ticket validated', result: validation.result });
110110
} catch (e) {
111111
responseException(res, e, 400);
112112
}

src/services/slack.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Slack from '../models/slack';
22
import { checkValue, Switcher } from 'switcher-client';
3-
import { TicketStatusType, SLACK_SUB } from '../models/slack_ticket';
3+
import { TicketStatusType, SLACK_SUB, TicketValidationType } from '../models/slack_ticket';
44
import { NotFoundError, PermissionError } from '../exceptions';
55
import { checkSlackIntegration, checkFeature, SwitcherKeys } from '../external/switcher-api-facade';
66
import { getConfig } from './config';
@@ -141,7 +141,19 @@ export async function resetTicketHistory(enterprise_id, team_id, admin) {
141141

142142
export async function validateTicket(ticket_content, enterprise_id, team_id) {
143143
const slack = await getSlackOrError({ enterprise_id, team_id });
144-
return canCreateTicket(slack, ticket_content);
144+
145+
const ticket = await canCreateTicket(slack, ticket_content);
146+
const { ignored_environments, frozen_environments } = slack.settings;
147+
148+
if (frozen_environments?.includes(ticket_content.environment))
149+
return { result: TicketValidationType.FROZEN_ENVIRONMENT };
150+
151+
if (ignored_environments?.includes(ticket_content.environment)) {
152+
await approveChange(slack.domain, ticket_content);
153+
return { result: TicketValidationType.IGNORED_ENVIRONMENT };
154+
}
155+
156+
return { result: TicketValidationType.VALIDATED, ticket};
145157
}
146158

147159
export async function createTicket(ticket_content, enterprise_id, team_id) {
@@ -176,12 +188,8 @@ export async function processTicket(enterprise_id, team_id, ticket_id, approved)
176188
throw new NotFoundError('Ticket not found');
177189

178190
if (approved) {
179-
ticket[0].ticket_approvals += 1;
180-
181-
if (slack.settings.approvals >= ticket[0].ticket_approvals) {
182-
ticket[0].ticket_status = TicketStatusType.APPROVED;
183-
await closeTicket(slack.domain, ticket[0]);
184-
}
191+
ticket[0].ticket_status = TicketStatusType.APPROVED;
192+
await closeTicket(slack.domain, ticket[0]);
185193
} else {
186194
ticket[0].ticket_status = TicketStatusType.DENIED;
187195
await closeTicket(null, ticket[0]);

tests/fixtures/db_api.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ export const environment1 = {
7171
owner: adminMasterAccountId
7272
};
7373

74+
export const environment2Id = new mongoose.Types.ObjectId();
75+
export const environment2 = {
76+
_id: environment2Id,
77+
name: 'dev',
78+
domain: domainId,
79+
owner: adminMasterAccountId
80+
};
81+
82+
export const environment3Id = new mongoose.Types.ObjectId();
83+
export const environment3 = {
84+
_id: environment3Id,
85+
name: 'staging',
86+
domain: domainId,
87+
owner: adminMasterAccountId
88+
};
89+
7490
export const groupConfigId = new mongoose.Types.ObjectId();
7591
export const groupConfigDocument = {
7692
_id: groupConfigId,
@@ -178,6 +194,10 @@ export const slack = {
178194
team_id: 'TEAM_ID',
179195
user_id: 'USER_ID',
180196
domain: domainId,
197+
settings: {
198+
ignored_environments: ['dev'],
199+
frozen_environments: ['staging']
200+
},
181201
installation_payload : {
182202
incoming_webhook_channel : 'Approval Team',
183203
incoming_webhook_channel_id : 'CHANNEL_ID'
@@ -208,6 +228,8 @@ export const setupDatabase = async () => {
208228
await new Admin(adminAccount).save();
209229

210230
await new Environment(environment1).save();
231+
await new Environment(environment2).save();
232+
await new Environment(environment3).save();
211233
await new Domain(domainDocument).save();
212234
await new GroupConfig(groupConfigDocument).save();
213235
await new Config(config1Document).save();

tests/slack.test.js

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import jwt from 'jsonwebtoken';
55
import app from '../src/app';
66
import * as Services from '../src/services/slack';
77
import { getDomainById } from '../src/services/domain';
8+
import { getConfig } from '../src/services/config';
89
import { mock1_slack_installation } from './fixtures/db_slack';
910
import { EnvType } from '../src/models/environment';
1011
import Slack from '../src/models/slack';
@@ -17,6 +18,7 @@ import {
1718
groupConfigDocument,
1819
adminAccountToken
1920
} from './fixtures/db_api';
21+
import { TicketValidationType } from '../src/models/slack_ticket';
2022

2123
afterAll(async () => {
2224
await Slack.deleteMany();
@@ -474,16 +476,21 @@ describe('Slack Route - Create Ticket', () => {
474476
};
475477

476478
//validate
477-
await request(app)
479+
let response = await request(app)
478480
.post('/slack/v1/ticket/validate')
479481
.set('Authorization', `Bearer ${generateToken('30s')}`)
480482
.send({
481483
team_id: slack.team_id,
482484
ticket_content
483485
}).expect(200);
484486

487+
expect(response.body).toMatchObject({
488+
message: 'Ticket validated',
489+
result: TicketValidationType.VALIDATED
490+
});
491+
485492
//test - create
486-
const response = await request(app)
493+
response = await request(app)
487494
.post('/slack/v1/ticket/create')
488495
.set('Authorization', `Bearer ${generateToken('30s')}`)
489496
.send({
@@ -498,6 +505,64 @@ describe('Slack Route - Create Ticket', () => {
498505
});
499506
});
500507

508+
test('SLACK_SUITE - Should NOT create a ticket - Environment Ignored', async () => {
509+
//given
510+
const ticket_content = {
511+
environment: 'dev',
512+
group: groupConfigDocument.name,
513+
switcher: config1Document.key,
514+
status: false
515+
};
516+
517+
//validate
518+
let switcher = await getConfig({ key: config1Document.key, domain: slack.domain });
519+
expect(switcher.activated.get('dev')).toBe(undefined);
520+
521+
const response = await request(app)
522+
.post('/slack/v1/ticket/validate')
523+
.set('Authorization', `Bearer ${generateToken('30s')}`)
524+
.send({
525+
team_id: slack.team_id,
526+
ticket_content
527+
}).expect(200);
528+
529+
switcher = await getConfig({ key: config1Document.key, domain: slack.domain });
530+
expect(switcher.activated.get('dev')).toBe(false);
531+
expect(response.body).toMatchObject({
532+
message: 'Ticket validated',
533+
result: TicketValidationType.IGNORED_ENVIRONMENT
534+
});
535+
});
536+
537+
test('SLACK_SUITE - Should NOT create a ticket - Environment frozen', async () => {
538+
//given
539+
const ticket_content = {
540+
environment: 'staging',
541+
group: groupConfigDocument.name,
542+
switcher: config1Document.key,
543+
status: false
544+
};
545+
546+
//validate
547+
let switcher = await getConfig({ key: config1Document.key, domain: slack.domain });
548+
expect(switcher.activated.get('staging')).toBe(undefined);
549+
550+
const response = await request(app)
551+
.post('/slack/v1/ticket/validate')
552+
.set('Authorization', `Bearer ${generateToken('30s')}`)
553+
.send({
554+
team_id: slack.team_id,
555+
ticket_content
556+
}).expect(200);
557+
558+
switcher = await getConfig({ key: config1Document.key, domain: slack.domain });
559+
expect(switcher.activated.get('staging')).toBe(undefined);
560+
expect(response.body).toMatchObject({
561+
message: 'Ticket validated',
562+
result: TicketValidationType.FROZEN_ENVIRONMENT
563+
});
564+
});
565+
501566
test('SLACK_SUITE - Should NOT create a ticket - Invalid', async () => {
502567
const ticket_content = {
503568
environment: EnvType.DEFAULT,
@@ -527,7 +592,7 @@ describe('Slack Route - Create Ticket', () => {
527592
};
528593

529594
// Retrieve existing ticket
530-
const ticket = await Services.validateTicket(
595+
const { ticket } = await Services.validateTicket(
531596
ticket_content, undefined, slack.team_id);
532597

533598
const response = await request(app)

0 commit comments

Comments
 (0)