From 8cd556bb642d51d2bcaa317c1836e83d5c05c6b2 Mon Sep 17 00:00:00 2001 From: LazyAfternoons Date: Wed, 31 Jan 2024 17:14:49 +0100 Subject: [PATCH] chore(Cross): [IOAPPX-213] Remove pivotal from danger and changelog scripts (#5422) ## Short description This PR addresses the removal of Pivotal Tracker integration from our danger and changelog scripts, as it has been replaced by Jira. ## List of changes proposed in this pull request - Removes `danger-plugin-digitalcitizenship` and `pivotaljs`, with the former being a plugin with custom rules over Danger; - Removes any dead code related to cross-linking with Pivotal Tracker stories based on the PR title; - Removes assigning scope based on ticket labels, given that they have remained unused since the transition to Jira while keeping the association between the project ID and a scope; - Removes a generalization layer created to manage both Jira and Pivotal tickets, making the scripts more coincise; - Removes old scopes from inactive Jira boards while adding a scope for the `IOAPPX` board; - Refactors file and folders structure to make them coherent with the declared functions and utilities; ## How to test Static checks and tests. I also made a few tests by editing this PR title and running Danger to check its behavior, as you can tell by this messages history. The changelog can be generated locally by testing any release command or the script directly. --------- Co-authored-by: Mario Perrotta --- Dangerfile.ts | 6 +- package.json | 4 +- ...nger-plugin-digitalcitizenship+0.3.1.patch | 26 -- patches/patches.md | 6 - ...pivotal_stories.js => add_jira_stories.js} | 67 +---- .../{ticket/jira => jiraTicket}/index.ts | 39 ++- .../{ticket/jira => jiraTicket}/types.ts | 20 +- scripts/ts/common/ticket/pivotal/types.d.ts | 39 --- scripts/ts/common/ticket/types.ts | 117 -------- scripts/ts/danger/__mocks__/genericTicket.ts | 21 ++ .../ts/danger/__tests__/updatePrTitle.test.ts | 118 ++++++++ ...mmentPrWithTicketsInfo.ts => commentPr.ts} | 51 ++-- scripts/ts/danger/updatePrTitle.tsx | 208 ++++++++++++++ .../ts/danger/updatePrTitleForChangelog.tsx | 92 ------ .../ts/danger/utils/__mocks__/storyMock.ts | 86 ------ .../danger/utils/__tests__/changelog.test.ts | 179 ------------ scripts/ts/danger/utils/changelog.ts | 217 -------------- scripts/ts/danger/utils/titleParser.tsx | 63 ----- yarn.lock | 265 +----------------- 19 files changed, 452 insertions(+), 1172 deletions(-) delete mode 100644 patches/danger-plugin-digitalcitizenship+0.3.1.patch rename scripts/changelog/{add_pivotal_stories.js => add_jira_stories.js} (51%) rename scripts/ts/common/{ticket/jira => jiraTicket}/index.ts (66%) rename scripts/ts/common/{ticket/jira => jiraTicket}/types.ts (72%) delete mode 100644 scripts/ts/common/ticket/pivotal/types.d.ts delete mode 100644 scripts/ts/common/ticket/types.ts create mode 100644 scripts/ts/danger/__mocks__/genericTicket.ts create mode 100644 scripts/ts/danger/__tests__/updatePrTitle.test.ts rename scripts/ts/danger/{commentPrWithTicketsInfo.ts => commentPr.ts} (60%) create mode 100644 scripts/ts/danger/updatePrTitle.tsx delete mode 100644 scripts/ts/danger/updatePrTitleForChangelog.tsx delete mode 100644 scripts/ts/danger/utils/__mocks__/storyMock.ts delete mode 100644 scripts/ts/danger/utils/__tests__/changelog.test.ts delete mode 100644 scripts/ts/danger/utils/changelog.ts delete mode 100644 scripts/ts/danger/utils/titleParser.tsx diff --git a/Dangerfile.ts b/Dangerfile.ts index ee40be811e8..1b7979adc54 100644 --- a/Dangerfile.ts +++ b/Dangerfile.ts @@ -1,9 +1,9 @@ // Import custom DangerJS rules. // See http://danger.systems/js import { DangerDSLType } from "danger/distribution/dsl/DangerDSL"; -import { commentPrWithTicketsInfo } from "./scripts/ts/danger/commentPrWithTicketsInfo"; -import { updatePrTitleForChangelog } from "./scripts/ts/danger/updatePrTitleForChangelog"; -import { getTicketsFromTitle } from "./scripts/ts/danger/utils/titleParser"; +import { commentPrWithTicketsInfo } from "./scripts/ts/danger/commentPr"; +import { updatePrTitleForChangelog } from "./scripts/ts/danger/updatePrTitle"; +import { getTicketsFromTitle } from "./scripts/ts/common/jiraTicket"; declare const danger: DangerDSLType; diff --git a/package.json b/package.json index f0fbe566344..0206c077df8 100644 --- a/package.json +++ b/package.json @@ -264,7 +264,6 @@ "babel-preset-react-native": "^4.0.1", "chalk": "^2.4.1", "danger": "^10.3.0", - "danger-plugin-digitalcitizenship": "^0.3.1", "eslint": "^8.6.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-functional": "^4.1.1", @@ -286,7 +285,6 @@ "node-fetch": "^2.6.7", "npm-run-all": "^4.1.3", "patch-package": "6.5.1", - "pivotaljs": "^1.0.3", "plist": "^3.0.5", "postinstall-postinstall": "^1.0.0", "prettier": "2.8.8", @@ -320,7 +318,7 @@ }, "standard-version": { "scripts": { - "postchangelog": "node scripts/changelog/add_pivotal_stories.js" + "postchangelog": "node scripts/changelog/add_jira_stories.js" } } } diff --git a/patches/danger-plugin-digitalcitizenship+0.3.1.patch b/patches/danger-plugin-digitalcitizenship+0.3.1.patch deleted file mode 100644 index 6da95378c8f..00000000000 --- a/patches/danger-plugin-digitalcitizenship+0.3.1.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff --git a/node_modules/danger-plugin-digitalcitizenship/dist/utils.js b/node_modules/danger-plugin-digitalcitizenship/dist/utils.js -index 82e1d17..3d9a783 100644 ---- a/node_modules/danger-plugin-digitalcitizenship/dist/utils.js -+++ b/node_modules/danger-plugin-digitalcitizenship/dist/utils.js -@@ -6,7 +6,7 @@ const Pivotal = require("pivotaljs"); - * see https://www.pivotaltracker.com/help/articles/githubs_service_hook_for_tracker/ - */ - function getPivotalStoryIDs(message) { -- const matches = message.match(/^\[(#\d+(,#\d+)*)\]\s.+/); -+ const matches = message.match(/\[(#\d+(,#\d+)*)\]\s.+/); - if (matches) { - return matches[1] - .split(",") -diff --git a/node_modules/danger-plugin-digitalcitizenship/src/utils.ts b/node_modules/danger-plugin-digitalcitizenship/src/utils.ts -index 5037cb8..7e815d9 100644 ---- a/node_modules/danger-plugin-digitalcitizenship/src/utils.ts -+++ b/node_modules/danger-plugin-digitalcitizenship/src/utils.ts -@@ -5,7 +5,7 @@ import Pivotal = require("pivotaljs"); - * see https://www.pivotaltracker.com/help/articles/githubs_service_hook_for_tracker/ - */ - export function getPivotalStoryIDs(message: string): ReadonlyArray { -- const matches = message.match(/^\[(#\d+(,#\d+)*)\]\s.+/); -+ const matches = message.match(/\[(#\d+(,#\d+)*)\]\s.+/); - if(matches) { - return matches[1] - .split(",") diff --git a/patches/patches.md b/patches/patches.md index 86d924da6df..2f1166338a2 100644 --- a/patches/patches.md +++ b/patches/patches.md @@ -77,12 +77,6 @@ Updated on **29/08/2022** - This patch is going to fix a gradle issue that breaks the compile on android platform, due to gradle imcompatibility -### danger-plugin-digitalcitizenship+0.3.1 -Created on **06/08/2020** - -#### Reason: -- Recognizes the ids of pivotal stories even if they are not at the beginning of the line - ### react-native-push-notification+7.3.1 Created on **10/05/2021** diff --git a/scripts/changelog/add_pivotal_stories.js b/scripts/changelog/add_jira_stories.js similarity index 51% rename from scripts/changelog/add_pivotal_stories.js rename to scripts/changelog/add_jira_stories.js index a886b6bb01a..71025612d03 100644 --- a/scripts/changelog/add_pivotal_stories.js +++ b/scripts/changelog/add_jira_stories.js @@ -1,42 +1,12 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires const fs = require("fs-extra"); -const Pivotal = require("pivotaljs"); -const pivotal = new Pivotal(); const jiraTicketBaseUrl = "https://pagopa.atlassian.net/browse/"; -/** - * Skip the use of API to find the story url if the url is already retrieved (contains pivotaltracker.com) - * or the id length is < 9 (not a pivotal id) - * @param id - * @param url - * @return {*|boolean} - */ -function skipStoryCheck(id, url) { - return url.includes("pivotaltracker.com") || id.length < 9; -} - -async function replacePivotalUrl(match, storyId, url) { - const sameUrl = `[${storyId}](${url})`; - try { - // avoid to use the api if story is already linked to pivotal - if (skipStoryCheck(storyId, url)) { - return sameUrl; - } - // try to get the story with the specified id - const story = await getStory(storyId.substr(1)); - - // if the story doesn't exists (eg: story deleted) remove the markdown link - return story.url !== undefined - ? `[${storyId}](${story.url})` - : `${storyId}`; - } catch (err) { - return sameUrl; - } -} - async function addJiraUrl(match, ticketKeys) { return `[${ticketKeys .split(",") + // eslint-disable-next-line sonarjs/no-nested-template-literals .map(x => `[${x}](${new URL(x, jiraTicketBaseUrl).toString()})`)}]`; } @@ -47,7 +17,7 @@ async function replaceJiraStories(content) { } /** - * replace the changelog content by removing the repetition of "closes [#idPivotalorJiraStory](url)" + * replace the changelog content by removing the repetition of "closes [#idJiraStory](url)" * @param content * @return {Promise} */ @@ -61,8 +31,6 @@ async function addTasksUrls() { const rawChangelog = fs.readFileSync("CHANGELOG.md").toString("utf8"); const updatedChangelog = await [ - // Add pivotal stories url - replacePivotalStories, // Add jira ticket url replaceJiraStories, // clean closes @@ -75,14 +43,6 @@ async function addTasksUrls() { fs.writeFileSync("CHANGELOG.md", updatedChangelog); } -async function replacePivotalStories(content) { - // identify the pattern [#XXXXX](url) for markdown link - const pivotalTagRegex = /\[(#\d+)\]\(([a-zA-z/\d\-@:%._+~#=]+)\)/g; - - // check for all the matches if is a pivotal story and update the url - return await replaceAsync(content, pivotalTagRegex, replacePivotalUrl); -} - /** * A custom method created to allows the replace in a string using an async function * @param str @@ -94,29 +54,18 @@ async function replaceAsync(str, regex, asyncFn) { const promises = []; str.replace(regex, (match, ...args) => { const promise = asyncFn(match, ...args); + // eslint-disable-next-line functional/immutable-data promises.push(promise); }); const data = await Promise.all(promises); + // eslint-disable-next-line functional/immutable-data return str.replace(regex, () => data.shift()); } -/** - * Wrap the getStory callback with a promise - * @param storyId - * @return {Promise} - */ -getStory = storyId => - new Promise((resolve, reject) => { - pivotal.getStory(storyId, (err, story) => { - if (err) { - return reject(err); - } - resolve(story); - }); - }); - -// Execute the script to find the pivotal stories and jira ticket id in order to associate +// Execute the script to find jira ticket id in order to associate // the right url in the changelog addTasksUrls() + // eslint-disable-next-line no-console .then(() => console.log("done")) + // eslint-disable-next-line no-console .catch(ex => console.log(ex)); diff --git a/scripts/ts/common/ticket/jira/index.ts b/scripts/ts/common/jiraTicket/index.ts similarity index 66% rename from scripts/ts/common/ticket/jira/index.ts rename to scripts/ts/common/jiraTicket/index.ts index 87574219e9c..ffc7e493e99 100644 --- a/scripts/ts/common/ticket/jira/index.ts +++ b/scripts/ts/common/jiraTicket/index.ts @@ -3,7 +3,8 @@ import { flow, pipe } from "fp-ts/lib/function"; import * as TE from "fp-ts/lib/TaskEither"; import { Errors } from "io-ts"; import fetch from "node-fetch"; -import { RemoteJiraTicket } from "./types"; +import * as O from "fp-ts/lib/Option"; +import { JiraTicketRetrievalResults, RemoteJiraTicket } from "./types"; const jiraOrgBaseUrl = "https://pagopa.atlassian.net/rest/api/3/issue/"; export const jiraTicketBaseUrl = "https://pagopa.atlassian.net/browse/"; @@ -58,3 +59,39 @@ export const getJiraTickets = async ( jiraIds: ReadonlyArray ): Promise>> => await Promise.all(jiraIds.map(getJiraTicket)); + +const jiraRegex = /\[([A-Z0-9]+-\d+(,[A-Z0-9]+-\d+)*)]\s.+/; + +/** + * Extracts Jira ticket ids from the pr title (if any) + * @param title + */ +export const getJiraIdFromPrTitle = ( + title: string +): O.Option> => + pipe( + title.match(jiraRegex), + O.fromNullable, + O.map(a => a[1].split(",")) + ); + +/** + * Try to retrieve Jira tickets from pr title + * and transforms them into {@link GenericTicket} + * @param title + */ +export const getTicketsFromTitle = async ( + title: string +): Promise => { + const maybeJiraId = await pipe( + getJiraIdFromPrTitle(title), + O.map(getJiraTickets), + O.toUndefined + ); + + if (maybeJiraId) { + return maybeJiraId; + } else { + return [E.left(new Error("No Jira ticket found"))]; + } +}; diff --git a/scripts/ts/common/ticket/jira/types.ts b/scripts/ts/common/jiraTicket/types.ts similarity index 72% rename from scripts/ts/common/ticket/jira/types.ts rename to scripts/ts/common/jiraTicket/types.ts index d4d6aefb192..8a2654778d6 100644 --- a/scripts/ts/common/ticket/jira/types.ts +++ b/scripts/ts/common/jiraTicket/types.ts @@ -1,4 +1,6 @@ import * as t from "io-ts"; +import * as E from "fp-ts/lib/Either"; +import { Errors } from "io-ts"; const JiraIssueType = t.keyof({ Epic: null, @@ -12,18 +14,18 @@ const JiraIssueType = t.keyof({ export type JiraIssueType = t.TypeOf; -const IssueType = t.interface({ +const IssueType = t.type({ name: JiraIssueType, subtask: t.boolean }); -const Project = t.interface({ +const Project = t.type({ id: t.string, key: t.string, name: t.string }); -const FieldsR = t.interface({ +const FieldsR = t.type({ issuetype: IssueType, project: Project, labels: t.array(t.string), @@ -31,9 +33,9 @@ const FieldsR = t.interface({ }); const FieldsP = t.partial({ - parent: t.interface({ + parent: t.type({ key: t.string, - fields: t.interface({ + fields: t.type({ summary: t.string, issuetype: IssueType }) @@ -53,3 +55,11 @@ export const RemoteJiraTicket = t.interface({ }); export type RemoteJiraTicket = t.TypeOf; + +export type RemoteJiraTicketParent = Required< + t.TypeOf +>["parent"]; + +export type JiraTicketRetrievalResults = ReadonlyArray< + E.Either +>; diff --git a/scripts/ts/common/ticket/pivotal/types.d.ts b/scripts/ts/common/ticket/pivotal/types.d.ts deleted file mode 100644 index fdb6f5da873..00000000000 --- a/scripts/ts/common/ticket/pivotal/types.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -// TODO: create types for pivotaljs - -import { IUnitTag } from "@pagopa/ts-commons/lib/units"; - -export type PivotalStoryType = "feature" | "bug" | "chore" | "release"; -export type PivotalStoryCurrentState = - | "accepted" - | "delivered" - | "finished" - | "started" - | "rejected" - | "planned" - | "unstarted" - | "unscheduled"; - -export type PivotalLabel = { - id: number; - project_id: number; - kind: string; - name: string; - // TODO: should be date - created_at: string; - updated_at: string; -}; - -export type PivotalId = string & IUnitTag<"PivotalId">; - -export type PivotalStory = { - id: string; - story_type: PivotalStoryType; - created_at: string; - updated_at: string; - estimate: number; - name: string; - current_state: PivotalStoryCurrentState; - url: string; - project_id: number; - labels: ReadonlyArray; -}; diff --git a/scripts/ts/common/ticket/types.ts b/scripts/ts/common/ticket/types.ts deleted file mode 100644 index e980641bd76..00000000000 --- a/scripts/ts/common/ticket/types.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { jiraTicketBaseUrl } from "./jira"; -import { JiraIssueType, RemoteJiraTicket } from "./jira/types"; -import { PivotalStory, PivotalStoryType } from "./pivotal/types"; - -export type GenericTicketType = "feat" | "fix" | "chore" | "epic"; - -/** - * A generic representation of a ticket, platform independent - */ -export type GenericTicket = { - id: string; - // a prefix that should be used to represent the ticket id - idPrefix?: string; - title: string; - type: GenericTicketType; - projectId: string; - tags: ReadonlyArray; - // the url to reach the ticket - url: string; - // is a subtask linked with a father ticket? - parent?: GenericTicket; -}; - -/** - * From {@link JiraIssueType} to {@link GenericTicketType} - * @param jiraType - */ -const convertJiraTypeToGeneric = ( - jiraType: JiraIssueType -): GenericTicketType => { - switch (jiraType) { - case "Bug": - return "fix"; - case "Epic": - return "epic"; - case "Sub-task": - case "Sottotask": - case "Subtask": - return "chore"; - case "Story": - return "feat"; - case "Task": - return "chore"; - } -}; - -/** - * Extracts the Jira ticket type. If is a subtask, use the father ticket for the type - * @param jira - */ -const getTypeFromJira = (jira: RemoteJiraTicket) => { - // if this is a subtask with a parent, the type is the type of the parent - if (jira.fields.parent !== undefined && jira.fields.issuetype.subtask) { - return convertJiraTypeToGeneric(jira.fields.parent.fields.issuetype.name); - } - return convertJiraTypeToGeneric(jira.fields.issuetype.name); -}; - -/** - * From {@link RemoteJiraTicket} to {@link GenericTicket} - * @param jira - */ -export const fromJiraToGenericTicket = ( - jira: RemoteJiraTicket -): GenericTicket => ({ - id: jira.key, - title: jira.fields.summary, - type: getTypeFromJira(jira), - projectId: jira.fields.project.key, - tags: jira.fields.labels, - url: new URL(jira.key, jiraTicketBaseUrl).toString(), - parent: jira.fields.parent - ? fromJiraToGenericTicket({ - ...jira.fields.parent, - fields: { - ...jira.fields.parent.fields, - project: jira.fields.project, - labels: [] - } - }) - : undefined -}); - -/** - * From {@link PivotalStoryType} to {@link GenericTicketType} - * @param pivotalType - */ -const convertPivotalTypeToGeneric = ( - pivotalType: PivotalStoryType -): GenericTicketType => { - switch (pivotalType) { - case "bug": - return "fix"; - case "feature": - return "feat"; - case "chore": - return "chore"; - case "release": - return "feat"; - } -}; - -/** - * From {@link PivotalStory} to {@link GenericTicket} - * @param pivotalStory - */ -export const fromPivotalToGenericTicket = ( - pivotalStory: PivotalStory -): GenericTicket => ({ - id: pivotalStory.id, - idPrefix: "#", - title: pivotalStory.name, - type: convertPivotalTypeToGeneric(pivotalStory.story_type), - projectId: pivotalStory.project_id.toString(), - tags: pivotalStory.labels.map(x => x.name), - url: pivotalStory.url -}); diff --git a/scripts/ts/danger/__mocks__/genericTicket.ts b/scripts/ts/danger/__mocks__/genericTicket.ts new file mode 100644 index 00000000000..435ff4d096a --- /dev/null +++ b/scripts/ts/danger/__mocks__/genericTicket.ts @@ -0,0 +1,21 @@ +import { RemoteJiraTicket } from "../../common/jiraTicket/types"; + +export const getJiraTicketExample = ( + issueType: RemoteJiraTicket["fields"]["issuetype"]["name"], + projectKey: RemoteJiraTicket["fields"]["project"]["key"] = "IO" +): RemoteJiraTicket => ({ + key: "IO-123", + fields: { + issuetype: { + name: issueType, + subtask: false + }, + project: { + id: "123", + key: projectKey, + name: "IO" + }, + labels: [], + summary: "Test" + } +}); diff --git a/scripts/ts/danger/__tests__/updatePrTitle.test.ts b/scripts/ts/danger/__tests__/updatePrTitle.test.ts new file mode 100644 index 00000000000..b456566b0c3 --- /dev/null +++ b/scripts/ts/danger/__tests__/updatePrTitle.test.ts @@ -0,0 +1,118 @@ +import * as O from "fp-ts/lib/Option"; +import * as E from "fp-ts/lib/Either"; +import { pipe } from "fp-ts/lib/function"; +import { getJiraTicketExample } from "../__mocks__/genericTicket"; +import { + allStoriesSameType, + getChangelogPrefixByStories, + getChangelogScope, + getTicketChangelogScope +} from "../updatePrTitle"; +import { RemoteJiraTicket } from "../../common/jiraTicket/types"; + +describe("changelog", () => { + const storyTicket = getJiraTicketExample("Story"); + const subTaskTicket = getJiraTicketExample("Sub-task"); + const bugTicket = getJiraTicketExample("Bug"); + const storyCrossTicket = getJiraTicketExample("Story", "IOAPPX"); + const storyFciTicket = getJiraTicketExample("Story", "SFEQS"); + + describe("allStoriesSameType", () => { + it("should return true if all stories have the same type", () => { + const stories: Array = [storyTicket, storyTicket]; + const result = allStoriesSameType(stories); + expect(result).toBe(true); + }); + + it("should return false if stories have different types", () => { + const stories: Array = [storyTicket, subTaskTicket]; + const result = allStoriesSameType(stories); + expect(result).toBe(false); + }); + }); + + describe("getChangelogPrefixByStories", () => { + it("should return the common prefix if all stories have the same type", () => { + const stories: Array = [storyTicket, storyTicket]; + const result = getChangelogPrefixByStories(stories); + expect(O.isSome(result)).toBe(true); + expect(O.getOrElse(() => "")(result)).toBe("feat"); + }); + + it("should return feat if stories have different types but there's at least one feat", () => { + const stories: Array = [storyTicket, subTaskTicket]; + const result = getChangelogPrefixByStories(stories); + expect(O.isSome(result)).toBe(true); + expect(O.getOrElse(() => "")(result)).toBe("feat"); + }); + + it("should return fix if stories has a Bug ticket type", () => { + const stories: Array = [bugTicket]; + const result = getChangelogPrefixByStories(stories); + expect(O.isSome(result)).toBe(true); + expect(O.getOrElse(() => "")(result)).toBe("fix"); + }); + }); + + describe("getTicketChangelogScope", () => { + it("should return the changelog scope if the story belongs to a scope project", () => { + const result = getTicketChangelogScope(storyCrossTicket); + expect(O.isSome(result)).toBe(true); + expect(O.getOrElse(() => "")(result)).toBe("Cross"); + }); + + it("should return None if the story doesn't belong to a scope project", () => { + const result = getTicketChangelogScope(storyTicket); + expect(O.isNone(result)).toBe(true); + }); + }); + + describe("getChangelogScope", () => { + it("should return Right(None) if no scope is found", () => { + const stories: Array = [storyTicket, subTaskTicket]; + const result = getChangelogScope(stories); + pipe( + result, + E.fold( + () => + fail( + `getChangelogScope shouldn't return Left if no scope is found` + ), + scope => { + expect(O.isNone(scope)).toBe(true); + } + ) + ); + }); + + it("should return Right(Some(scope)) if one of the stories have a scope or all the stories that have scope have the same scope", () => { + const stories: Array = [ + storyCrossTicket, + storyCrossTicket + ]; + const result = getChangelogScope(stories); + pipe( + result, + E.fold( + () => + fail( + `getChangelogScope shouldn't return Left if one of the stories have a scope or all the stories that have scope have the same scope` + ), + scope => { + expect(O.isSome(scope)).toBe(true); + expect(O.getOrElse(() => "")(scope)).toBe("Cross"); + } + ) + ); + }); + + it("should return Left(errors) if two stories have different scope or one of the story have different scope", () => { + const stories: Array = [ + storyFciTicket, + storyCrossTicket + ]; + const result = getChangelogScope(stories); + expect(E.isLeft(result)).toBe(true); + }); + }); +}); diff --git a/scripts/ts/danger/commentPrWithTicketsInfo.ts b/scripts/ts/danger/commentPr.ts similarity index 60% rename from scripts/ts/danger/commentPrWithTicketsInfo.ts rename to scripts/ts/danger/commentPr.ts index 31d0b325974..f4af572230f 100644 --- a/scripts/ts/danger/commentPrWithTicketsInfo.ts +++ b/scripts/ts/danger/commentPr.ts @@ -2,33 +2,35 @@ import { DangerDSLType } from "danger/distribution/dsl/DangerDSL"; import * as E from "fp-ts/lib/Either"; import { Errors } from "io-ts"; import { readableReport } from "@pagopa/ts-commons/lib/reporters"; -import { GenericTicket, GenericTicketType } from "../common/ticket/types"; -import { GenericTicketRetrievalResults } from "./utils/titleParser"; +import { + JiraTicketRetrievalResults, + JiraIssueType, + RemoteJiraTicket, + RemoteJiraTicketParent +} from "../common/jiraTicket/types"; +import { jiraTicketBaseUrl } from "../common/jiraTicket"; declare const danger: DangerDSLType; export declare function warn(message: string): void; export declare function markdown(message: string): void; -const StoryEmoji: Record = { - feat: "🌟", - fix: "🐞", - chore: "⚙️", - epic: "⚡" +const StoryEmoji: Record = { + Epic: "⚡", + Story: "🌟", + Task: "⚙️", + Subtask: "⚙️", + "Sub-task": "⚙️", + Sottotask: "⚙️", + Bug: "🐞" }; /** - * Display how should be used the Jira / Pivotal id in the pr title + * Display how should be used the Jira id in the pr title */ const warningNoTicket = () => { - warn( - "Please include a Pivotal story or Jira ticket at the beginning of the PR title" - ); - markdown(` - Example of PR titles that include pivotal stories: - * single story: \`[#123456] my PR title\` - * multiple stories: \`[#123456,#123457,#123458] my PR title\` - + warn("Please include a Jira ticket at the beginning of the PR title"); + markdown(` Example of PR titles that include Jira tickets: * single story: \`[PROJID-123] my PR title\` * multiple stories: \`[PROJID-1,PROJID-2,PROJID-3] my PR title\` @@ -38,16 +40,17 @@ const warningNoTicket = () => { * Comments with the ticket type, id and title * @param ticket */ -const renderTicket = (ticket: GenericTicket) => - `${StoryEmoji[ticket.type]} [${ticket.idPrefix ? ticket.idPrefix : ""}${ - ticket.id - }](${ticket.url}): ${ticket.title}`; +const renderTicket = (ticket: RemoteJiraTicket | RemoteJiraTicketParent) => { + const ticketType = StoryEmoji[ticket.fields.issuetype.name]; + const ticketUrl = new URL(ticket.key, jiraTicketBaseUrl).toString(); + return `${ticketType} [${ticket.key}](${ticketUrl}): ${ticket.fields.summary}`; +}; -const renderTickets = (ticketList: ReadonlyArray) => { +const renderTickets = (ticketList: ReadonlyArray) => { const ticketListToString = ticketList .map(s => { - const subtask = s.parent - ? ` \n _subtask of_\n * ${renderTicket(s.parent)}` + const subtask = s.fields.parent + ? ` \n _subtask of_\n * ${renderTicket(s.fields.parent)}` : ""; return ` * ${renderTicket(s)}${subtask}`; }) @@ -76,7 +79,7 @@ const renderFailure = (errors: ReadonlyArray) => { * @param foundTicket */ export const commentPrWithTicketsInfo = ( - foundTicket: GenericTicketRetrievalResults + foundTicket: JiraTicketRetrievalResults ) => { if (foundTicket.length === 0) { warningNoTicket(); diff --git a/scripts/ts/danger/updatePrTitle.tsx b/scripts/ts/danger/updatePrTitle.tsx new file mode 100644 index 00000000000..4dc968dada8 --- /dev/null +++ b/scripts/ts/danger/updatePrTitle.tsx @@ -0,0 +1,208 @@ +import { DangerDSLType } from "danger/distribution/dsl/DangerDSL"; +import * as E from "fp-ts/lib/Either"; +import { flow, pipe } from "fp-ts/lib/function"; +import * as O from "fp-ts/lib/Option"; +import { + JiraTicketRetrievalResults, + JiraIssueType, + RemoteJiraTicket +} from "../common/jiraTicket/types"; + +declare const danger: DangerDSLType; +export declare function warn(message: string): void; + +const multipleTypesWarning = + "Multiple stories with different types are associated with this Pull request.\n" + + "Only one tag will be added, following the order: `feature > bug > chore`"; + +const atLeastOneLeftTicket = + "An error occurred during the retrieval of a ticket," + + " the title of the pull request is updated taking into account only the recovered tickets "; + +const storyTag = new Map([ + ["Epic", "feat"], + ["Story", "feat"], + ["Bug", "fix"], + ["Sub-task", "chore"], + ["Sottotask", "chore"], + ["Subtask", "chore"], + ["Task", "chore"] +]); + +const storyOrder = new Map([ + ["Epic", 3], + ["Story", 2], + ["Bug", 1], + ["Sub-task", 0], + ["Sottotask", 0], + ["Subtask", 0], + ["Task", 0] +]); + +// a list of project ids associated with a specific scope +const projectToScope = new Map([ + ["IOAPPX", "Cross"], + ["SFEQS", "Firma con IO"], + ["IODPAY", "IDPay"] +]); + +/** + * Calculate the Changelog prefix for the provided stories. + * @param stories + */ +export const getChangelogPrefixByStories = ( + stories: ReadonlyArray +): O.Option => { + // In case of multiple stories, only one tag can be added, following the order feature > bug > chore + const storyType = stories.reduce>((acc, val) => { + const currentStoryOrder = O.fromNullable( + storyOrder.get(val.fields.issuetype.name) + ); + const prevStoryOrder = pipe( + acc, + O.chain(v => O.fromNullable(storyOrder.get(v))) + ); + + if (O.isSome(currentStoryOrder) && O.isSome(prevStoryOrder)) { + return currentStoryOrder.value > prevStoryOrder.value + ? O.some(val.fields.issuetype.name) + : acc; + } else if (O.isSome(currentStoryOrder)) { + return O.some(val.fields.issuetype.name); + } + return acc; + }, O.none); + + return pipe( + storyType, + O.chain(st => O.fromNullable(storyTag.get(st))) + ); +}; + +/** + * Append the changelog tag and scope to the pull request title, based on the story found + */ +export const updatePrTitleForChangelog = async ( + tickets: JiraTicketRetrievalResults +) => { + if (tickets.some(E.isLeft)) { + warn(atLeastOneLeftTicket); + } + + const foundTicket = tickets.filter(E.isRight).map(x => x.right); + + if (!allStoriesSameType(foundTicket)) { + warn(multipleTypesWarning); + } + const maybePrTag = getChangelogPrefixByStories(foundTicket); + const eitherScope = getChangelogScope(foundTicket); + + if (E.isLeft(eitherScope)) { + eitherScope.left.map(err => warn(err.message)); + } + const scope = pipe( + eitherScope, + E.map( + flow( + O.map(s => `(${s})`), + O.getOrElse(() => "") + ) + ), + E.getOrElseW(() => "") + ); + + const cleanChangelogRegex = + /^(fix(\(.+\))?!?: |feat(\(.+\))?!?: |chore(\(.+\))?!?: )?(.*)$/; + const title = pipe( + danger.github.pr.title.match(cleanChangelogRegex), + O.fromNullable, + O.map(matches => matches.pop() || danger.github.pr.title), + O.getOrElse(() => danger.github.pr.title) + ); + + const labelScope = scope.replace("(", "").replace(")", ""); + await danger.github.utils.createOrAddLabel({ + name: labelScope, + // The color is not used and can be customized from the "label" tab in the github page + color: "#FFFFFF", + description: labelScope + }); + + // Ensure the first char after the ticket id is uppercase + // [JIRA-12] pr title -> [JIRA-12] Pr title + const titleSplitter = new RegExp(/(\[.*]\s*)(.+)/g); + const splittingResults = titleSplitter.exec(title); + const upperCaseTitle = + splittingResults && splittingResults.length === 3 + ? `${splittingResults[1]}${ + splittingResults[2].charAt(0).toUpperCase() + + splittingResults[2].slice(1) + }` + : title; + + O.map(tag => + danger.github.api.pulls.update({ + owner: danger.github.thisPR.owner, + repo: danger.github.thisPR.repo, + pull_number: danger.github.thisPR.number, + title: `${tag}${scope}: ${upperCaseTitle}` + }) + )(maybePrTag); +}; + +/** + * Return true if all the stories have the same `story_type` + * @param stories + */ +export const allStoriesSameType = ( + stories: ReadonlyArray +): boolean => + stories.every( + (val, _, arr) => val.fields.issuetype === arr[0].fields.issuetype + ); + +/** + * Calculate the changelog scope for the story + * Return: + * - O.None if the story doesn't belong to a scope project + * - O.Some>` if the story belongs to a scope project + * @param story + */ +export const getTicketChangelogScope = ( + story: RemoteJiraTicket +): O.Option => + O.fromNullable(projectToScope.get(story.fields.project.key)); + +/** + * Calculate the Changelog scope for the stories. + * Return: + * - `Right` if no scope is found + * - `Right>` if one of the stories have a scope or all the stories that have scope have the same scope + * - `Left>` if two stories have different scope or one of the story have different scope + * @param stories + */ + +export const getChangelogScope = ( + stories: ReadonlyArray +): E.Either, O.Option> => { + const eitherChangelogScopes = stories.map(getTicketChangelogScope); + + const scopesList = eitherChangelogScopes + .filter((maybeScope): maybeScope is O.Some => O.isSome(maybeScope)) + .map(scope => scope.value); + + if (scopesList.length === 0) { + return E.right(O.none); + } + + return scopesList.every((scope, _, arr) => scope === arr[0]) + ? E.right(O.some(scopesList[0])) + : E.left([ + new Error( + `Different scopes were found on the stories related to the pull request: [${scopesList.join( + "," + )}].\n + It is not possible to assign a single scope to this pull request!` + ) + ]); +}; diff --git a/scripts/ts/danger/updatePrTitleForChangelog.tsx b/scripts/ts/danger/updatePrTitleForChangelog.tsx deleted file mode 100644 index eae6f61b5c0..00000000000 --- a/scripts/ts/danger/updatePrTitleForChangelog.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { DangerDSLType } from "danger/distribution/dsl/DangerDSL"; -import * as E from "fp-ts/lib/Either"; -import { flow, pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { - allStoriesSameType, - getChangelogPrefixByStories, - getChangelogScope -} from "./utils/changelog"; -import { GenericTicketRetrievalResults } from "./utils/titleParser"; - -declare const danger: DangerDSLType; -export declare function warn(message: string): void; - -const multipleTypesWarning = - "Multiple stories with different types are associated with this Pull request.\n" + - "Only one tag will be added, following the order: `feature > bug > chore`"; - -const atLeastOneLeftTicket = - "An error occurred during the retrieval of a ticket," + - " the title of the pull request is updated taking into account only the recovered tickets "; - -/** - * Append the changelog tag and scope to the pull request title, based on the story found - */ -export const updatePrTitleForChangelog = async ( - tickets: GenericTicketRetrievalResults -) => { - if (tickets.some(E.isLeft)) { - warn(atLeastOneLeftTicket); - } - - const foundTicket = tickets.filter(E.isRight).map(x => x.right); - - if (!allStoriesSameType(foundTicket)) { - warn(multipleTypesWarning); - } - const maybePrTag = getChangelogPrefixByStories(foundTicket); - const eitherScope = getChangelogScope(foundTicket); - - if (E.isLeft(eitherScope)) { - eitherScope.left.map(err => warn(err.message)); - } - const scope = pipe( - eitherScope, - E.map( - flow( - O.map(s => `(${s})`), - O.getOrElse(() => "") - ) - ), - E.getOrElseW(() => "") - ); - - const cleanChangelogRegex = - /^(fix(\(.+\))?!?: |feat(\(.+\))?!?: |chore(\(.+\))?!?: )?(.*)$/; - const title = pipe( - danger.github.pr.title.match(cleanChangelogRegex), - O.fromNullable, - O.map(matches => matches.pop() || danger.github.pr.title), - O.getOrElse(() => danger.github.pr.title) - ); - - const labelScope = scope.replace("(", "").replace(")", ""); - await danger.github.utils.createOrAddLabel({ - name: labelScope, - // The color is not used and can be customized from the "label" tab in the github page - color: "#FFFFFF", - description: labelScope - }); - - // Ensure the first char after the ticket id is uppercase - // [JIRA-12] pr title -> [JIRA-12] Pr title - const titleSplitter = new RegExp(/(\[.*]\s*)(.+)/g); - const splittingResults = titleSplitter.exec(title); - const upperCaseTitle = - splittingResults && splittingResults.length === 3 - ? `${splittingResults[1]}${ - splittingResults[2].charAt(0).toUpperCase() + - splittingResults[2].slice(1) - }` - : title; - - O.map(tag => - danger.github.api.pulls.update({ - owner: danger.github.thisPR.owner, - repo: danger.github.thisPR.repo, - pull_number: danger.github.thisPR.number, - title: `${tag}${scope}: ${upperCaseTitle}` - }) - )(maybePrTag); -}; diff --git a/scripts/ts/danger/utils/__mocks__/storyMock.ts b/scripts/ts/danger/utils/__mocks__/storyMock.ts deleted file mode 100644 index f312ef135a5..00000000000 --- a/scripts/ts/danger/utils/__mocks__/storyMock.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - PivotalLabel, - PivotalStory -} from "../../../common/ticket/pivotal/types"; - -export const baseStory: PivotalStory = { - id: "123", - story_type: "bug", - created_at: "date", - updated_at: "date", - estimate: 3, - name: "Come CIT voglio poter utilizzare l'app", - current_state: "started", - url: "url", - project_id: 132, - labels: [] -}; - -export const baseLabel: PivotalLabel = { - id: 5, - project_id: 21, - kind: "label", - name: "generic-label", - created_at: "date", - updated_at: "date" -}; - -export const scopeLabeliOS: PivotalLabel = { - ...baseLabel, - name: "changelog-scope:ios" -}; - -export const scopeLabelAndroid: PivotalLabel = { - ...baseLabel, - name: "changelog-scope:android" -}; - -export const scopeLabelEpicBpd: PivotalLabel = { - ...baseLabel, - name: "epic-bpd" -}; - -export const scopeLabelNotAllowed: PivotalLabel = { - ...baseLabel, - name: "changelog-scope:not-allowed" -}; - -export const bonusVacanzeStory: PivotalStory = { - ...baseStory, - project_id: 2449547 -}; - -export const baseStoryWithGenericLabel: PivotalStory = { - ...baseStory, - labels: [baseLabel] -}; - -export const bonusVacanzeStoryWithScopeLabel: PivotalStory = { - ...bonusVacanzeStory, - labels: [scopeLabeliOS] -}; - -export const singleAndroidLabelStory: PivotalStory = { - ...baseStory, - labels: [scopeLabelAndroid] -}; - -export const singleEpicBpdStory: PivotalStory = { - ...baseStory, - labels: [scopeLabelEpicBpd] -}; - -export const androidLabelAndOtherStory: PivotalStory = { - ...baseStory, - labels: [scopeLabelAndroid, baseLabel] -}; - -export const clashScopeLabelStory: PivotalStory = { - ...baseStory, - labels: [scopeLabelAndroid, scopeLabeliOS] -}; - -export const scopeLabelNotAllowedStory: PivotalStory = { - ...baseStory, - labels: [scopeLabelNotAllowed] -}; diff --git a/scripts/ts/danger/utils/__tests__/changelog.test.ts b/scripts/ts/danger/utils/__tests__/changelog.test.ts deleted file mode 100644 index 360b56b36aa..00000000000 --- a/scripts/ts/danger/utils/__tests__/changelog.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as E from "fp-ts/lib/Either"; -import { - androidLabelAndOtherStory, - baseStory, - baseStoryWithGenericLabel, - bonusVacanzeStory, - bonusVacanzeStoryWithScopeLabel, - clashScopeLabelStory, - scopeLabelNotAllowedStory, - singleAndroidLabelStory, - singleEpicBpdStory -} from "../__mocks__/storyMock"; -import { getChangelogScope, getStoryChangelogScope } from "../changelog"; -import { fromPivotalToGenericTicket } from "../../../common/ticket/types"; - -describe("Test pivotal Utility", () => { - it("getStoryChangelogScope on a story without labels should return Right O.none", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(baseStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope)) { - expect(eitherScope.right).toBe(O.none); - } - }); - it("getStoryChangelogScope on a story with a normal label (not scope tag) should return Right,O.none", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(baseStoryWithGenericLabel) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope)) { - expect(eitherScope.right).toBe(O.none); - } - }); - it("getStoryChangelogScope on a story without label but belonging to a project that assigns a scope should return Right,string", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(bonusVacanzeStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Bonus Vacanze"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getStoryChangelogScope on a story with scope label should return Right,string", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(singleAndroidLabelStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Android"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getStoryChangelogScope on a story with epic label should return Right,string", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(singleEpicBpdStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Bonus Pagamenti Digitali"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getStoryChangelogScope on a story with scope label and other labels should return Right,string", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(androidLabelAndOtherStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Android"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getStoryChangelogScope on a story with different scope labels should return Left,Error", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(clashScopeLabelStory) - ); - expect(E.isLeft(eitherScope)).toBeTruthy(); - }); - it("getStoryChangelogScope on a story with scope label not allowed should return Right,O.none", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(scopeLabelNotAllowedStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope)) { - expect(eitherScope.right).toBe(O.none); - } - }); - it("getStoryChangelogScope on a story with a scope labels and belonging to a project that assigns a scope should return Left,Error", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(bonusVacanzeStoryWithScopeLabel) - ); - expect(E.isLeft(eitherScope)).toBeTruthy(); - }); - - it("getChangelogScope on an empty array should return O.none", () => { - const eitherScope = getChangelogScope([]); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope)) { - expect(eitherScope.right).toBe(O.none); - } - }); - - it("getChangelogScope with a single story without scope label should return Right, O.none", () => { - const eitherScope = getChangelogScope([ - fromPivotalToGenericTicket(baseStoryWithGenericLabel) - ]); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope)) { - expect(eitherScope.right).toBe(O.none); - } - }); - it("getChangelogScope with a single story with scope label should return Right, some", () => { - const eitherScope = getChangelogScope([ - fromPivotalToGenericTicket(singleAndroidLabelStory) - ]); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Android"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getChangelogScope with a multiple stories with the same scope label should return Right, some", () => { - const eitherScope = getChangelogScope([ - fromPivotalToGenericTicket(singleAndroidLabelStory), - fromPivotalToGenericTicket(singleAndroidLabelStory), - fromPivotalToGenericTicket(singleAndroidLabelStory) - ]); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Android"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getChangelogScope with stories with different scopes should return Left, error[] with 1 error", () => { - const eitherScope = getChangelogScope([ - fromPivotalToGenericTicket(singleAndroidLabelStory), - fromPivotalToGenericTicket(bonusVacanzeStory) - ]); - expect(E.isLeft(eitherScope)).toBeTruthy(); - if (E.isLeft(eitherScope)) { - expect(eitherScope.left.length).toBe(1); - } - }); - it( - "getChangelogScope with stories with different scopes (foreach story) should return Left, " + - "error[] with 2 error", - () => { - const eitherScope = getChangelogScope([ - fromPivotalToGenericTicket(clashScopeLabelStory), - fromPivotalToGenericTicket(bonusVacanzeStoryWithScopeLabel) - ]); - expect(E.isLeft(eitherScope)).toBeTruthy(); - if (E.isLeft(eitherScope)) { - expect(eitherScope.left.length).toBe(2); - } - } - ); -}); diff --git a/scripts/ts/danger/utils/changelog.ts b/scripts/ts/danger/utils/changelog.ts deleted file mode 100644 index 40b370a298b..00000000000 --- a/scripts/ts/danger/utils/changelog.ts +++ /dev/null @@ -1,217 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -// The script need to be executed by the danger bot that doesn't support the optional chaining operator -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { GenericTicket, GenericTicketType } from "../../common/ticket/types"; - -const storyTag = new Map([ - ["feat", "feat"], - ["fix", "fix"], - ["chore", "chore"], - ["epic", "feat"] -]); - -const storyOrder = new Map([ - ["epic", 3], - ["feat", 2], - ["fix", 1], - ["chore", 0] -]); - -const allowedScope = new Map([ - ["android", "Android"], - ["ios", "iOS"], - ["bonus_vacanze", "Bonus Vacanze"], - ["messages", "Messages"], - ["payments", "Payments"], - ["services", "Services"], - ["profile", "Profile"], - ["privacy", "Privacy"], - ["security", "Security"], - ["accessibility", "Accessibility"], - ["bpd", "Bonus Pagamenti Digitali"], - ["cgn", "Carta Giovani Nazionale"], - ["fims", "Federated Identity Management System"], - ["myportal", "MyPortal"] -]); - -// a list of project ids associated with a specific scope -const projectToScope = new Map([ - ["2449547", "Bonus Vacanze"], - ["2463683", "My Portal"], - ["2477137", "Bonus Pagamenti Digitali"], - ["IAC", "Bonus Pagamenti Digitali"], - ["IOACGN", "Carta Giovani Nazionale"], - ["IASV", "Sicilia Vola"], - ["IAGP", "EU Covid Certificate"], - ["IARS", "Redesign Servizi"], - ["ASZ", "Zendesk"], - ["IAMVL", "Piattaforma Notifiche"], - ["IAFIMS", "Federated Identity Management System"], - ["AP", "Carta della cultura"], - ["SFEQS", "Firma con IO"], - ["IODPAY", "IDPay"] -]); - -const cleanChangelogRegex = - /^(fix(\(.+\))?!?: |feat(\(.+\))?!?: |chore(\(.+\))?!?: )?(.*)$/; - -// pattern used to recognize a scope label -const regex = /(changelog-scope:|epic-)(.*)/m; - -/** - * Clean the title from previous changelog prefix to update in case of changes - * @param title - */ -export const getRawTitle = (title: string): string => { - // clean the title from existing tags (multiple commit on the same branch) - const rawTitle = title.match(cleanChangelogRegex)!.pop(); - return rawTitle !== undefined ? rawTitle : title; -}; - -/** - * Return true if all the stories have the same `story_type` - * @param stories - */ -export const allStoriesSameType = ( - stories: ReadonlyArray -): boolean => stories.every((val, _, arr) => val.type === arr[0].type); - -/** - * Calculate the Changelog prefix for the provided stories. - * @param stories - */ -export const getChangelogPrefixByStories = ( - stories: ReadonlyArray -): O.Option => { - // In case of multiple stories, only one tag can be added, following the order feature > bug > chore - const storyType = stories.reduce>((acc, val) => { - const currentStoryOrder = O.fromNullable(storyOrder.get(val.type)); - const prevStoryOrder = pipe( - acc, - O.chain(v => O.fromNullable(storyOrder.get(v))) - ); - - if (O.isSome(currentStoryOrder) && O.isSome(prevStoryOrder)) { - return currentStoryOrder.value > prevStoryOrder.value - ? O.some(val.type) - : acc; - } else if (O.isSome(currentStoryOrder)) { - return O.some(val.type); - } - return acc; - }, O.none); - - return pipe( - storyType, - O.chain(st => O.fromNullable(storyTag.get(st))) - ); -}; - -/** - * Calculate the changelog scope for the story - * Return: - * - `Right` if no labels with scope are associated with the story and the story doesn't belong to a scope project. - * - `Right>` if the story have a scope label or the story belong to a scope project - * - `Left` if the story have multiple different scope label or the story have a scope label and belong to a scope project. - * @param story - */ -export const getStoryChangelogScope = ( - story: GenericTicket -): E.Either> => { - // try to retrieve the project scope (if any) - const maybeProjectScope = O.fromNullable(projectToScope.get(story.projectId)); - // search for scope labels associated with the story - const maybeChangelogScopeTag = story.tags - .filter(l => l.match(regex)) - .map(l => l.match(regex)!.pop()) - .filter(tag => tag && allowedScope.has(tag)); - - // multiple scope labels found on the story - if (maybeChangelogScopeTag.length > 1) { - return E.left( - new Error( - `Multiple labels match the expression \`${regex}\` for the story [#${story.id}].\n - It is not possible to assign a single scope to this pull request!` - ) - ); - } - // the story matches a project scope and also have scope label - if (O.isSome(maybeProjectScope) && maybeChangelogScopeTag.length >= 1) { - return E.left( - new Error( - `The story [#${story.id}] have the project_id ${story.projectId} associated with the scope ${maybeProjectScope.value} but also have labels matching the expression \`${regex}\`.\n - It is not possible to assign a single scope to this pull request!` - ) - ); - } - if (O.isSome(maybeProjectScope)) { - return E.right(maybeProjectScope); - } - if ( - maybeChangelogScopeTag.length === 1 && - maybeChangelogScopeTag[0] !== undefined - ) { - // check if is allowed - const scopeDisplayName = allowedScope.get(maybeChangelogScopeTag[0]); - return scopeDisplayName !== undefined - ? E.right(O.some(scopeDisplayName)) - : E.left( - new Error( - `The scope ${ - maybeChangelogScopeTag[0] - } is not present in the allowed scopes: ${Array.from( - allowedScope.keys() - ).join(",")}` - ) - ); - } - // neither project scope nor scope label found - return E.right(O.none); -}; - -/** - * Calculate the Changelog scope for the stories. - * Return: - * - `Right` if no scope is found - * - `Right>` if one of the stories have a scope or all the stories that have scope have the same scope - * - `Left>` if two stories have different scope or one of the story have different scope - * @param stories - */ -export const getChangelogScope = ( - stories: ReadonlyArray -): E.Either, O.Option> => { - const eitherChangelogScopes = stories.map(getStoryChangelogScope); - - // if there is some error, forward the errors - if (eitherChangelogScopes.some(E.isLeft)) { - return E.left( - eitherChangelogScopes.reduce>( - (acc, val) => (E.isLeft(val) ? [...acc, val.left] : acc), - [] - ) - ); - } - const scopesList = eitherChangelogScopes - .filter( - (maybeScope): maybeScope is E.Right> => - E.isRight(maybeScope) && O.isSome(maybeScope.right) - ) - .map(scope => scope.right.value); - - if (scopesList.length === 0) { - return E.right(O.none); - } - - return scopesList.every((scope, _, arr) => scope === arr[0]) - ? E.right(O.some(scopesList[0])) - : E.left([ - new Error( - `Different scopes were found on the stories related to the pull request: [${scopesList.join( - "," - )}].\n - It is not possible to assign a single scope to this pull request!` - ) - ]); -}; diff --git a/scripts/ts/danger/utils/titleParser.tsx b/scripts/ts/danger/utils/titleParser.tsx deleted file mode 100644 index a4cc538792e..00000000000 --- a/scripts/ts/danger/utils/titleParser.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { - getPivotalStories, - getPivotalStoryIDs -} from "danger-plugin-digitalcitizenship/dist/utils"; -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { Errors } from "io-ts"; -import { getJiraTickets } from "../../common/ticket/jira"; -import { - fromJiraToGenericTicket, - fromPivotalToGenericTicket, - GenericTicket -} from "../../common/ticket/types"; - -const jiraRegex = /\[([A-Z0-9]+-\d+(,[A-Z0-9]+-\d+)*)]\s.+/; - -export type GenericTicketRetrievalResults = ReadonlyArray< - E.Either ->; - -/** - * Extracts Jira ticket ids from the pr title (if any) - * @param title - */ -export const getJiraIdFromPrTitle = ( - title: string -): O.Option> => - pipe( - title.match(jiraRegex), - O.fromNullable, - O.map(a => a[1].split(",")) - ); - -/** - * Try to retrieve Jira tickets (or Pivotal stories as fallback) from pr title - * and transforms them into {@link GenericTicket} - * ⚠️ Mixed Jira and Pivotal id in the pr title are not supported ⚠️ - * @param title - */ -export const getTicketsFromTitle = async ( - title: string -): Promise => { - const maybeJiraId = await pipe( - getJiraIdFromPrTitle(title), - O.map(getJiraTickets), - O.toUndefined - ); - - if (maybeJiraId) { - return maybeJiraId.map(E.map(fromJiraToGenericTicket)); - } - - const maybePivotalId = await getPivotalStories(getPivotalStoryIDs(title)); - return maybePivotalId - ? pipe( - maybePivotalId - .filter(s => s.story_type !== undefined) - .map(fromPivotalToGenericTicket) - .map(E.right) - ) - : [E.left(new Error("No Pivotal stories found"))]; -}; diff --git a/yarn.lock b/yarn.lock index 3a737a2cc91..e7d8801e656 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4452,7 +4452,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.10.0, ajv@^6.12.3: +ajv@^6.10.0: version "6.12.3" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA== @@ -4766,18 +4766,6 @@ asn1.js@^5.2.0: minimalistic-assert "^1.0.0" safer-buffer "^2.1.0" -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -4887,16 +4875,6 @@ auto-changelog@^2.4.0: parse-github-url "^1.0.2" semver "^7.3.5" -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" - integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== - babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -5557,13 +5535,6 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - before-after-hook@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" @@ -6017,11 +5988,6 @@ capture-exit@^2.0.0: dependencies: rsvp "^4.8.4" -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - catharsis@^0.8.11: version "0.8.11" resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.8.11.tgz#d0eb3d2b82b7da7a3ce2efb1a7b00becc6643468" @@ -6382,7 +6348,7 @@ colors@1.4.0, colors@^1.0.3, colors@^1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -6776,7 +6742,7 @@ core-js@^2.6.5: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== -core-util-is@1.0.2, core-util-is@~1.0.0: +core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -7020,25 +6986,6 @@ cycle@^1.0.3: resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI= -danger-plugin-digitalcitizenship@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/danger-plugin-digitalcitizenship/-/danger-plugin-digitalcitizenship-0.3.1.tgz#ed941ee5905788bb9f1f61f171029d76c985b995" - integrity sha512-AxaWGdLzm+GN7Q385ICCTxgj15JVIiFnpw03v1FcsCmDF1trnFhZfJzjESAJVi510p0C+bchJe9yX4SdsiUPXA== - dependencies: - danger-plugin-yarn "^1.2.1" - pivotaljs "^1.0.3" - -danger-plugin-yarn@^1.2.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/danger-plugin-yarn/-/danger-plugin-yarn-1.3.2.tgz#c1fc2b9ad24477775fd828473a0a33c62bd9a9b5" - integrity sha512-g+7guMig986giNyUxM15sFH32HKBGzHcKHMrAojvGOU9lpB53KwxX5yrYnokRlfW0aSaAwm9GCInXTQc9MoW3w== - dependencies: - date-fns "^1.28.5" - lodash.flatten "^4.4.0" - lodash.includes "^4.3.0" - node-fetch "^1.7.1" - semver "^5.4.1" - danger@^10.3.0: version "10.3.0" resolved "https://registry.yarnpkg.com/danger/-/danger-10.3.0.tgz#9d104a030998ac3491f05019e13b76e877111fad" @@ -7088,13 +7035,6 @@ dargs@^4.0.1: dependencies: number-is-nan "^1.0.0" -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - data-uri-to-buffer@3: version "3.0.1" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" @@ -7114,7 +7054,7 @@ date-and-time@0.9.0: resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-0.9.0.tgz#1524579e56dc07675c640b41735a7665c0659240" integrity sha512-4JybB6PbR+EebpFx/KyR5Ybl+TcdXMLIJkyYsCx3P4M4CWGMuDyFF19yh6TyasMAIF5lrsgIxiSHBXh2FFc7Fg== -date-fns@^1.28.5, date-fns@^1.29.0: +date-fns@^1.29.0: version "1.30.1" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== @@ -7569,14 +7509,6 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - ecdsa-sig-formatter@1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" @@ -8323,7 +8255,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -8342,16 +8274,6 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - fast-base64-decode@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418" @@ -8649,11 +8571,6 @@ for-own@^0.1.3: dependencies: for-in "^1.0.1" -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - form-data@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" @@ -8672,15 +8589,6 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -8970,13 +8878,6 @@ getenv@0.7.0: resolved "https://registry.yarnpkg.com/getenv/-/getenv-0.7.0.tgz#39b91838707e2086fd1cf6ef8777d1c93e14649e" integrity sha1-ObkYOHB+IIb9HPbvh3fRyT4UZJ4= -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - git-config-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/git-config-path/-/git-config-path-1.0.1.tgz#6d33f7ed63db0d0e118131503bab3aca47d54664" @@ -9177,19 +9078,6 @@ handlebars@^4.7.6, handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -9538,15 +9426,6 @@ http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - http2-wrapper@2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.0.5.tgz#d4464509df69b6f82125b6cb337dbb80101f406c" @@ -10139,7 +10018,7 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" -is-typedarray@^1.0.0, is-typedarray@~1.0.0: +is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== @@ -10218,11 +10097,6 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - istanbul-lib-coverage@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" @@ -10881,11 +10755,6 @@ jsbarcode@^3.8.0: resolved "https://registry.yarnpkg.com/jsbarcode/-/jsbarcode-3.11.0.tgz#20623e008b101ef45d0cce9c8022cdf49be28547" integrity sha512-/ozCd7wsa+VIHo9sUc03HneVEQrH7cVWfJolUT/WOW1m8mJ2e3iYZje6C9X3LFHdczlesqFHRpxLtbVsNtjyow== -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - jsc-android@^250230.2.1: version "250230.2.1" resolved "https://registry.yarnpkg.com/jsc-android/-/jsc-android-250230.2.1.tgz#3790313a970586a03ab0ad47defbc84df54f1b83" @@ -11034,11 +10903,6 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - json-set-map@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/json-set-map/-/json-set-map-1.1.2.tgz#536cbc6549d06e8af11f76cb4fbd441ed2389e6e" @@ -11049,7 +10913,7 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= @@ -11120,16 +10984,6 @@ jsonwebtoken@^8.4.0: ms "^2.1.1" semver "^5.6.0" -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.2.0" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" @@ -11385,11 +11239,6 @@ lodash.find@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E= -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= - lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -12505,7 +12354,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@~2.1.19: +mime-types@^2.1.12: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== @@ -12855,7 +12704,7 @@ node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0, node- dependencies: whatwg-url "^5.0.0" -node-fetch@^1.0.1, node-fetch@^1.7.1: +node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== @@ -13030,11 +12879,6 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - ob1@0.58.0: version "0.58.0" resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.58.0.tgz#484a1e9a63a8b79d9ea6f3a83b2a42110faac973" @@ -13782,14 +13626,6 @@ pirates@^4.0.5: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== -pivotaljs@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pivotaljs/-/pivotaljs-1.0.3.tgz#fafd4bac7bb921ca4509e6e768f3c10772a1f34f" - integrity sha1-+v1LrHu5IcpFCebnaPPBB3Kh808= - dependencies: - request latest - underscore latest - pkg-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" @@ -14044,7 +13880,7 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.28, psl@^1.1.33: +psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== @@ -14086,11 +13922,6 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" -qs@~6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" - integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== - query-string@6.10.1: version "6.10.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.10.1.tgz#30b3505f6fca741d5ae541964d1b3ae9dc2a0de8" @@ -15251,32 +15082,6 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@latest: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -15534,7 +15339,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -16011,21 +15816,6 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" @@ -16686,14 +16476,6 @@ tough-cookie@^4.0.0: punycode "^2.1.1" universalify "^0.1.2" -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -16811,23 +16593,11 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - tween-functions@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/tween-functions/-/tween-functions-1.2.0.tgz#1ae3a50e7c60bb3def774eac707acbca73bbc3ff" integrity sha1-GuOlDnxguz3vd06scHrLynO7w/8= -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -16992,7 +16762,7 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -underscore@latest, underscore@~1.10.2: +underscore@~1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.10.2.tgz#73d6aa3668f3188e4adb0f1943bd12cfd7efaaaf" integrity sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg== @@ -17268,7 +17038,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2: +uuid@^3.0.1, uuid@^3.1.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -17315,15 +17085,6 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - vfile-message@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.0.2.tgz#db7eaebe7fecb853010f2ef1664427f52baf8f74"