-
Notifications
You must be signed in to change notification settings - Fork 197
Arc 1192 update backfilling status #2482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kamaksheeAtl
wants to merge
63
commits into
main
Choose a base branch
from
ARC-1192-update-backfilling-status
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
63 commits
Select commit
Hold shift + click to select a range
28ee606
chore: spike for backfill status fetch
kamaksheeAtl bb37377
chore: spike for backfill status fetch
kamaksheeAtl 44a779b
chore: spike for backfill status fetch
kamaksheeAtl c8b0cf1
chore: add progress fetch fr backfill pg
kamaksheeAtl 93cb05e
chore: update test snapshots
kamaksheeAtl e7cb1c7
chore: remove get-connected-repo changes
kamaksheeAtl 291ac6d
chore: remove get-connected-repo changes
kamaksheeAtl 973ee29
chore: update test snapshots
kamaksheeAtl 99aec7a
fix: missing subscription id in data attri
kamaksheeAtl 603dc2a
chore: update failing test cases
kamaksheeAtl 3d7ddb7
chore: incorporate PR review comments
kamaksheeAtl e54d420
chore: incorporate PR review comments
kamaksheeAtl 5094704
chore: incorporate pr review comments
kamaksheeAtl 463fb6e
chore: fix test cases and add security middleware to new api route
kamaksheeAtl b9bcf6b
chore: update test cases to fix failing test
kamaksheeAtl f123a7f
chore: update api for 403 incase of missmached jirahost
kamaksheeAtl a5404df
fix: failing test cases
kamaksheeAtl 07edd59
chore: add the backfill since status
kamaksheeAtl f3701e9
chore: update code as per PR review comment
kamaksheeAtl dd6c60a
chore: update the jwt in backfill call
kamaksheeAtl 1d61632
chore: update the jwt in backfill call
kamaksheeAtl bc4286b
chore: merge main
kamaksheeAtl b267700
chore: add test cases
kamaksheeAtl 10aae00
chore: add test cases
kamaksheeAtl 195d2b6
chore: add test cases
kamaksheeAtl c7312a4
chore: update test cases to increase the coverage
kamaksheeAtl 2c4a3b1
chore: remove explicit jwt pass
kamaksheeAtl 7887a8f
chore: PR review comments
kamaksheeAtl 637389d
chore: PR review comments
kamaksheeAtl 63c421e
chore: PR review comments
kamaksheeAtl 50c78f2
chore: update testcases
kamaksheeAtl a023315
chore: update snaps
kamaksheeAtl 4df488e
chore: add feature flag
kamaksheeAtl 09b5c30
chore: update space changes
kamaksheeAtl 9a5ed36
chore: update space changes
kamaksheeAtl bec3e9c
Merge branch 'main' into ARC-1192-update-backfilling-status
kamaksheeAtl d95c309
fix: zero repo count scenario
kamaksheeAtl 87dac3c
Merge branch 'main' into ARC-1192-update-backfilling-status
kamaksheeAtl d98c7d5
fix: give results of subs with totalrepos more than zero
kamaksheeAtl a47cff2
Merge branch 'main' into ARC-1192-update-backfilling-status
kamaksheeAtl 8545cab
chore: simplify the logic
kamaksheeAtl 40fc18c
chore: chng logic of detected backfill complete
kamaksheeAtl 61e6241
fix: org with zero repo count display
kamaksheeAtl 2d874a8
Merge branch 'main' into ARC-316-fix-zerorepo-org
kamaksheeAtl 7138c4b
fix: org with zero repo count display
kamaksheeAtl dca1b90
fix: org with zero repo count display
kamaksheeAtl d332cec
chore: merge main brnch
kamaksheeAtl 20ea125
chore: merge main branch
kamaksheeAtl f8615bb
chore: add sync err handling
kamaksheeAtl a99a7ff
chore: modify test cases
kamaksheeAtl 208e8f9
chore: modify test cases
kamaksheeAtl 279e950
chore: merge main
kamaksheeAtl 737f208
chore: reformate code
kamaksheeAtl cd2ba30
chore: reformate code
kamaksheeAtl 6d04509
chore: reformate code
kamaksheeAtl d56a78e
chore: reformate code
kamaksheeAtl 6b308f6
chore: PR comment refactor
kamaksheeAtl 7929f06
Merge branch 'main' into ARC-1192-update-backfilling-status
kamaksheeAtl 9cfa423
Merge branch 'main' into ARC-1192-update-backfilling-status
kamaksheeAtl 7446db2
chore: PR comments
kamaksheeAtl b1d3aa3
chore: PR comments
kamaksheeAtl 13677c9
chore: PR comments
kamaksheeAtl 5e1d563
Merge branch 'main' into ARC-1192-update-backfilling-status
kamaksheeAtl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
191 changes: 191 additions & 0 deletions
191
src/routes/jira/jira-get-connections-backfill-status.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| import { getFrontendApp } from "~/src/app"; | ||
| import { Installation } from "models/installation"; | ||
| import { encodeSymmetric } from "atlassian-jwt"; | ||
| import { getLogger } from "config/logger"; | ||
| import { Subscription } from "models/subscription"; | ||
| import { DatabaseStateCreator } from "test/utils/database-state-creator"; | ||
| import supertest from "supertest"; | ||
| import { booleanFlag, BooleanFlags } from "config/feature-flags"; | ||
| import { when } from "jest-when"; | ||
| import { RepoSyncState } from "models/reposyncstate"; | ||
|
|
||
| jest.mock("config/feature-flags"); | ||
|
|
||
| describe("jira-get-connections-backfillStatus.test", () => { | ||
| let app; | ||
| let installation: Installation; | ||
| let subscription: Subscription; | ||
| let repoSyncState: RepoSyncState; | ||
| const generateJwt = async () => { | ||
| return encodeSymmetric( | ||
| { | ||
| qsh: "context-qsh", | ||
| iss: installation.plainClientKey, | ||
| sub: "myAccountId" | ||
| }, | ||
| await installation.decrypt("encryptedSharedSecret", getLogger("test")) | ||
| ); | ||
| }; | ||
|
|
||
| beforeEach(async () => { | ||
| app = getFrontendApp(); | ||
| const result = await new DatabaseStateCreator() | ||
| .withActiveRepoSyncState() | ||
| .create(); | ||
| installation = result.installation; | ||
| subscription = result.subscription; | ||
| repoSyncState = result.repoSyncState!; | ||
| when(booleanFlag) | ||
| .calledWith(BooleanFlags.JIRA_ADMIN_CHECK) | ||
| .mockResolvedValue(true); | ||
| }); | ||
|
|
||
| it("should return 401 when no JWT was provided", async () => { | ||
| const resp = await supertest(app).get( | ||
| `/jira/subscriptions/backfill-status/?subscriptionIds=${subscription.id}` | ||
| ); | ||
| expect(resp.status).toStrictEqual(401); | ||
| expect(resp.text).toBe("Unauthorised"); | ||
| }); | ||
|
|
||
| it("should return 403 when not an admin", async () => { | ||
| const resp = await supertest(app) | ||
| .get( | ||
| `/jira/subscriptions/backfill-status?subscriptionIds=${subscription.id}` | ||
| ) | ||
| .set( | ||
| "authorization", | ||
| `JWT ${await generateJwt()}` | ||
| ); | ||
| expect(resp.status).toStrictEqual(403); | ||
| }); | ||
|
|
||
| describe("admin and JWT are OK", () => { | ||
| beforeEach(() => { | ||
| const payload = { | ||
| accountId: "myAccountId", | ||
| globalPermissions: ["ADMINISTER"] | ||
| }; | ||
| jiraNock | ||
| .post("/rest/api/latest/permissions/check", payload) | ||
| .reply(200, { globalPermissions: ["ADMINISTER"] }); | ||
| }); | ||
|
|
||
| it("should return 400 when no subscriptions were found", async () => { | ||
| const resp = await supertest(app) | ||
| .get( | ||
| `/jira/subscriptions/backfill-status?subscriptionIds=${ | ||
| subscription.id + 1 | ||
| }` | ||
| ) | ||
| .set( | ||
| "authorization", | ||
| `JWT ${await generateJwt()}` | ||
| ); | ||
| expect(resp.status).toStrictEqual(400); | ||
| }); | ||
|
|
||
| it("should return 400 when no Missing Subscription IDs were found in query", async () => { | ||
| const resp = await supertest(app) | ||
| .get(`/jira/subscriptions/backfill-status`) | ||
| .set("authorization", `JWT ${await generateJwt()}`); | ||
| expect(resp.status).toStrictEqual(400); | ||
| expect(resp.text).toBe("Missing Subscription IDs"); | ||
| }); | ||
|
|
||
| it("should return 403 if the subscription belongs to a different user", async () => { | ||
| const result = await new DatabaseStateCreator() | ||
| .forJiraHost("https://another-one.atlassian.net") | ||
| .create(); | ||
| const resp = await supertest(app) | ||
| .get( | ||
| `/jira/subscriptions/backfill-status?subscriptionIds=${result.subscription.id}` | ||
| ) | ||
| .set( | ||
| "authorization", | ||
| `JWT ${await generateJwt()}` | ||
| ); | ||
|
|
||
| expect(resp.status).toStrictEqual(403); | ||
| }); | ||
|
|
||
| it("should return 200 if the subscription belongs to the same user", async () => { | ||
| const resp = await supertest(app) | ||
| .get( | ||
| `/jira/subscriptions/backfill-status?subscriptionIds=${subscription.id}` | ||
| ) | ||
| .set( | ||
| "authorization", | ||
| `JWT ${await generateJwt()}` | ||
| ); | ||
| expect(resp.status).toStrictEqual(200); | ||
| }); | ||
|
|
||
| describe("happy paths", () => { | ||
| beforeEach(async () => { | ||
| const newRepoSyncStatesData: any[] = []; | ||
| for (let newRepoStateNo = 1; newRepoStateNo < 50; newRepoStateNo++) { | ||
| const newRepoSyncState = { ...repoSyncState.dataValues }; | ||
| delete newRepoSyncState["id"]; | ||
| delete newRepoSyncState["commitStatus"]; | ||
| delete newRepoSyncState["branchStatus"]; | ||
| newRepoSyncState["repoId"] = repoSyncState.repoId + newRepoStateNo; | ||
| newRepoSyncState["repoName"] = | ||
| repoSyncState.repoName + newRepoStateNo.toString(); | ||
| newRepoSyncState["repoFullName"] = | ||
| repoSyncState.repoFullName + | ||
| String(newRepoStateNo).padStart(3, "0"); | ||
| if (newRepoStateNo === 1) { | ||
| newRepoSyncState["commitStatus"] = "pending"; | ||
| newRepoSyncState["branchStatus"] = "complete"; | ||
| newRepoSyncState["pullStatus"] = "complete"; | ||
| newRepoSyncState["buildStatus"] = "complete"; | ||
| newRepoSyncState["deploymentStatus"] = "pending"; | ||
| } else if (newRepoStateNo % 3 == 1) { | ||
| newRepoSyncState["commitStatus"] = "complete"; | ||
| newRepoSyncState["branchStatus"] = "complete"; | ||
| newRepoSyncState["pullStatus"] = "complete"; | ||
| newRepoSyncState["buildStatus"] = "complete"; | ||
| newRepoSyncState["deploymentStatus"] = "complete"; | ||
| } else if (newRepoStateNo % 3 == 2) { | ||
| newRepoSyncState["commitStatus"] = "failed"; | ||
| newRepoSyncState["branchStatus"] = "complete"; | ||
| newRepoSyncState["pullStatus"] = "complete"; | ||
| newRepoSyncState["buildStatus"] = "complete"; | ||
| newRepoSyncState["deploymentStatus"] = "failed"; | ||
| } | ||
| newRepoSyncStatesData.push(newRepoSyncState); | ||
| } | ||
| await RepoSyncState.bulkCreate(newRepoSyncStatesData); | ||
| }); | ||
|
|
||
| it("should return 200 if the subscription belongs to the same user", async () => { | ||
| const resp = await supertest(app) | ||
| .get( | ||
| `/jira/subscriptions/backfill-status?subscriptionIds=${subscription.id}` | ||
| ) | ||
| .set( | ||
| "authorization", | ||
| `JWT ${await generateJwt()}` | ||
| ); | ||
| expect(resp.status).toStrictEqual(200); | ||
|
|
||
| expect(resp.body).toMatchObject({ | ||
| data: { | ||
| subscriptions: { | ||
| [subscription.id]: { | ||
| isSyncComplete: false, | ||
| syncStatus: "IN PROGRESS", | ||
| totalRepos: 33, | ||
| syncedRepos: 17, | ||
| backfillSince: null | ||
| } | ||
| }, | ||
| isBackfillComplete: false, | ||
| subscriptionIds: [subscription.id] | ||
| } | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
130 changes: 130 additions & 0 deletions
130
src/routes/jira/jira-get-connections-backfill-status.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| import { NextFunction, Request, Response } from "express"; | ||
| import { groupBy } from "lodash"; | ||
| import { RepoSyncState } from "~/src/models/reposyncstate"; | ||
| import { Subscription, SyncStatus } from "~/src/models/subscription"; | ||
| import { | ||
| mapSyncStatus, | ||
| ConnectionSyncStatus, | ||
| getRetryableFailedSyncErrors | ||
| } from "~/src/util/github-installations-helper"; | ||
|
|
||
| type SubscriptionBackfillState = { | ||
| totalRepos?: number; | ||
| syncedRepos?: number; | ||
| syncStatus: ConnectionSyncStatus; | ||
| isSyncComplete: boolean; | ||
| backfillSince?: string; | ||
| failedSyncErrors?: Record<string, number>; | ||
| syncWarning?: string; | ||
| }; | ||
|
|
||
| type ErrorType = { | ||
| subscriptionId: string; | ||
| error: string; | ||
| }; | ||
| type BackFillType = { | ||
| [key: string]: SubscriptionBackfillState; | ||
| }; | ||
|
|
||
| export const JiraGetConnectionsBackfillStatus = async ( | ||
| req: Request, | ||
| res: Response, | ||
| next: NextFunction | ||
| ): Promise<void> => { | ||
| try { | ||
| const { jiraHost: localJiraHost } = res.locals; | ||
| const subscriptionIds = String(req.query?.subscriptionIds) | ||
| .split(",") | ||
| .map(Number) | ||
| .filter(Boolean); | ||
|
|
||
| if (subscriptionIds.length === 0) { | ||
| req.log.warn("Missing Subscription IDs"); | ||
| res.status(400).send("Missing Subscription IDs"); | ||
| return; | ||
| } | ||
|
|
||
| const subscriptions = await Subscription.findAllForSubscriptionIds( | ||
| subscriptionIds | ||
| ); | ||
|
|
||
| const resultSubscriptionIds = subscriptions.map( | ||
| (subscription) => subscription.id | ||
| ); | ||
|
|
||
| if (!subscriptions || subscriptions.length === 0) { | ||
| req.log.error("Missing Subscription"); | ||
| res.status(400).send("Missing Subscription"); | ||
| return; | ||
| } | ||
|
|
||
| const jiraHostsMatched = subscriptions.every( | ||
| (subscription) => subscription?.jiraHost === localJiraHost | ||
| ); | ||
|
|
||
| if (!jiraHostsMatched) { | ||
| req.log.error("mismatched Jira Host"); | ||
| res.status(403).send("mismatched Jira Host"); | ||
| return; | ||
| } | ||
| const subscriptionsById = groupBy(subscriptions, "id"); | ||
| const { backfillStatus, errors } = await getBackfillStatus( | ||
| subscriptionsById | ||
| ); | ||
| const isBackfillComplete = getBackfillCompletionStatus(backfillStatus); | ||
| res.status(200).send({ | ||
| data: { | ||
| subscriptions: backfillStatus, | ||
| isBackfillComplete, | ||
| subscriptionIds: resultSubscriptionIds, | ||
| errors | ||
| } | ||
| }); | ||
| } catch (error) { | ||
| req.log.error( | ||
| { error }, | ||
| "Failed to poll repo backfill status for provided subscription ID" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: pull this out into a const instead of repeating the same string. |
||
| ); | ||
| return next( | ||
| new Error( | ||
| `Failed to poll repo backfill status for provided subscription ID` | ||
| ) | ||
| ); | ||
| } | ||
| }; | ||
|
|
||
| const getBackfillCompletionStatus = (backfillStatus: BackFillType): boolean => | ||
| Object.values(backfillStatus).every( | ||
| (backFill: SubscriptionBackfillState): boolean => backFill?.isSyncComplete | ||
| ); | ||
|
|
||
| const getBackfillStatus = async ( | ||
| subscriptionsById | ||
| ): Promise<{ backfillStatus: BackFillType; errors?: ErrorType[] }> => { | ||
| const backfillStatus: BackFillType = {}; | ||
| const errors: ErrorType[] = []; | ||
| for (const subscriptionId in subscriptionsById) { | ||
| try { | ||
| const subscription = subscriptionsById[subscriptionId][0]; | ||
| const isSyncComplete = | ||
| subscription?.syncStatus === SyncStatus.COMPLETE || | ||
| subscription?.syncStatus === SyncStatus.FAILED; | ||
| const failedSyncErrors = await getRetryableFailedSyncErrors(subscription); | ||
|
|
||
| backfillStatus[subscriptionId] = { | ||
| isSyncComplete, | ||
| syncStatus: mapSyncStatus(subscription?.syncStatus), | ||
| totalRepos: subscription?.totalNumberOfRepos, | ||
| syncedRepos: await RepoSyncState.countFullySyncedReposForSubscription( | ||
| subscription | ||
| ), | ||
| failedSyncErrors, | ||
| backfillSince: subscription?.backfillSince || null, | ||
| syncWarning: subscription.syncWarning | ||
| }; | ||
| } catch (error) { | ||
| errors.push({ subscriptionId, error }); | ||
| } | ||
| } | ||
| return { backfillStatus, errors }; | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have an enum set up for this in error.ts. Just import
MISSING_SUBSCRIPTION