Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Arc 2786 react backfill status update #2630

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions spa/src/api/subscriptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { axiosRest } from "../axiosInstance";
import { RestSyncReqBody } from "~/src/rest-interfaces";
import {
BackfillStatusUrlParams,
RestSyncReqBody,
} from "~/src/rest-interfaces";

export default {
getSubscriptions: () => axiosRest.get("/rest/subscriptions"),
getSubscriptionsBackfillStatus: (params: BackfillStatusUrlParams) =>
axiosRest.get(`/rest/subscriptions/backfill-status`, { params }),
deleteGHEServer: (serverUrl: string) =>
axiosRest.delete(`/rest/ghes-servers/${serverUrl}`),
deleteGHEApp: (uuid: string) =>
axiosRest.delete(`/rest/app/${uuid}`),
deleteGHEApp: (uuid: string) => axiosRest.delete(`/rest/app/${uuid}`),
deleteSubscription: (subscriptionId: number) =>
axiosRest.delete(`/rest/app/cloud/subscriptions/${subscriptionId}`),
syncSubscriptions: (subscriptionId: number, reqBody: RestSyncReqBody) =>
axiosRest.post(`/rest/app/cloud/subscriptions/${subscriptionId}/sync`, reqBody),
axiosRest.post(
`/rest/app/cloud/subscriptions/${subscriptionId}/sync`,
reqBody
),
};
61 changes: 53 additions & 8 deletions spa/src/pages/Connections/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ import {
GHSubscriptions,
BackfillPageModalTypes,
SuccessfulConnection,
GitHubEnterpriseApplication,
GitHubEnterpriseApplication
} from "../../rest-interfaces";
import SkeletonForLoading from "./SkeletonForLoading";
import SubscriptionManager from "../../services/subscription-manager";
import RestartBackfillModal from "./Modals/RestartBackfillModal";
import DisconnectSubscriptionModal from "./Modals/DisconnectSubscriptionModal";
import { DisconnectGHEServerModal, DeleteAppInGitHubModal, DisconnectGHEServerAppModal } from "./Modals/DisconnectGHEServerModal";
import {
DisconnectGHEServerModal,
DeleteAppInGitHubModal,
DisconnectGHEServerAppModal,
} from "./Modals/DisconnectGHEServerModal";
import { getInProgressSubIds, getUpdatedSubscriptions } from "../../utils";

const hasGHCloudConnections = (subscriptions: GHSubscriptions): boolean =>
subscriptions?.ghCloudSubscriptions &&
Expand All @@ -33,6 +38,13 @@ const Connections = () => {
const [dataForModal, setDataForModal] = useState<
SuccessfulConnection | GitHubEnterpriseApplication | undefined
>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [subscriptions, setSubscriptions] = useState<GHSubscriptions | null>(
null
);
const [inProgressSubs, setInProgressSubs] = useState<Array<number> | null>(
null
);
const openedModal = () => {
switch (selectedModal) {
case "BACKFILL":
Expand Down Expand Up @@ -80,20 +92,20 @@ const Connections = () => {
}
};

const [isLoading, setIsLoading] = useState<boolean>(false);
const [subscriptions, setSubscriptions] = useState<GHSubscriptions | null>(
null
);

const fetchGHSubscriptions = async () => {
try {
setIsLoading(true);
const response = await SubscriptionManager.getSubscriptions();
if (response instanceof AxiosError) {
// TODO: Handle the error once we have the designs
console.error("Error", response);
} else {
const inProgressSubIds = getInProgressSubIds(response);
if (inProgressSubIds && inProgressSubIds.length > 0) {
setInProgressSubs(inProgressSubIds);
}
setSubscriptions(response as GHSubscriptions);
}
setSubscriptions(response as GHSubscriptions);
} catch (e) {
// TODO: handle this error in UI/Modal ?
console.error("Could not fetch ghe subscriptions: ", e);
Expand All @@ -102,6 +114,39 @@ const Connections = () => {
}
};

const fetchBackfillStatus = async (inProgressSubs: Array<number>) => {
try {
const response = await SubscriptionManager.getSubscriptionsBackfillStatus(
inProgressSubs.toString()
);
if (response instanceof AxiosError) {
// TODO: Handle the error once we have the designs
console.error("Error", response);
} else {
if(subscriptions){
const newSubscriptions = getUpdatedSubscriptions(response, subscriptions);
if(newSubscriptions) {
setSubscriptions(newSubscriptions);
}
}
if (!response.isBackfillComplete) {
setTimeout(() => {
fetchBackfillStatus(inProgressSubs);
}, 3000);
}
}
} catch (e) {
// TODO: handle this error in UI/Modal ?
console.error("Could not fetch ghe subscriptions: ", e);
}
};

useEffect(() => {
if (inProgressSubs && inProgressSubs.length > 0) {
fetchBackfillStatus(inProgressSubs);
}
}, [inProgressSubs]);

useEffect(() => {
fetchGHSubscriptions();
}, []);
Expand Down
25 changes: 22 additions & 3 deletions spa/src/services/subscription-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Api from "../../api";
import { AxiosError } from "axios";
import { reportError } from "../../utils";
import { GHSubscriptions } from "../../../../src/rest-interfaces";
import { RestSyncReqBody } from "~/src/rest-interfaces";
import { BackfillStatusResp, RestSyncReqBody } from "~/src/rest-interfaces";

async function syncSubscription(subscriptionId:number, reqBody: RestSyncReqBody): Promise<void | AxiosError> {
try {
Expand All @@ -26,14 +26,32 @@ async function getSubscriptions(): Promise<GHSubscriptions | AxiosError> {
const isSuccessful = response.status === 200;
if(!isSuccessful) {
reportError(
{ message: "Response status for getting subscriptions is not 204", status: response.status },
{ message: "Response status for getting subscriptions is not 200", status: response.status },
{ path: "getSubscriptions" }
);
}

return response.data;
} catch (e: unknown) {
reportError(new Error("Unable to delete subscription", { cause: e }), { path: "getSubscriptions" });
reportError(new Error("Unable to get subscription", { cause: e }), { path: "getSubscriptions" });
return e as AxiosError;
}
}

async function getSubscriptionsBackfillStatus(subscriptionIds: string): Promise<BackfillStatusResp | AxiosError> {
try {
const response= await Api.subscriptions.getSubscriptionsBackfillStatus({ subscriptionIds });
const isSuccessful = response.status === 200;
if(!isSuccessful) {
reportError(
{ message: "Response status for getting subscriptions backfill status is not 200", status: response.status },
{ path: "getSubscriptionsBackfillStatus" }
);
}

return response.data;
} catch (e: unknown) {
reportError(new Error("Unable to Get subscription backfill status update", { cause: e }), { path: "getSubscriptionsBackfillStatus" });
return e as AxiosError;
}
}
Expand Down Expand Up @@ -92,6 +110,7 @@ async function deleteGHEApp(uuid: string): Promise<boolean | AxiosError> {
}
export default {
getSubscriptions,
getSubscriptionsBackfillStatus,
deleteSubscription,
deleteGHEServer,
syncSubscription,
Expand Down
160 changes: 159 additions & 1 deletion spa/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as Sentry from "@sentry/react";
import { AxiosError } from "axios";
import { BackfillStatusResp, GHSubscriptions, SubscriptionBackfillState } from "../rest-interfaces";

export const getJiraJWT = (): Promise<string> => new Promise(resolve => {
return AP.context.getToken((token: string) => {
Expand All @@ -22,7 +23,7 @@ export function reportError(err: unknown, extra: {
try {

const cause = (err as Record<string, unknown>).cause || {};
delete (err as Record<string, unknown>).cause; //so that Sentry doesn't group all axios error together
delete (err as Record<string, unknown>).cause; //so that Sentry doesn"t group all axios error together

Sentry.captureException(err, {
extra: {
Expand Down Expand Up @@ -58,3 +59,160 @@ export function openChildWindow(url: string) {
}, 100);
return child;
}

export const getInProgressSubIds = (response: GHSubscriptions): Array<number> => {
const successfulCloudConnections =
response.ghCloudSubscriptions.successfulCloudConnections;
const inProgressCloudConnections = successfulCloudConnections.filter(
(connection) =>
connection.syncStatus === "IN PROGRESS" ||
connection.syncStatus === "PENDING"
);
const inProgressCloudSubIds = inProgressCloudConnections.map(
(connection) => connection.subscriptionId
);
let inProgressGHESubIds: Array<number> = [];
const ghEnterpriseServers = response.ghEnterpriseServers;
for (const ghEnterpriseServer of ghEnterpriseServers) {
const applications = ghEnterpriseServer.applications;
for (const application of applications) {
const successfulGHEConnections = application.successfulConnections;
const inProgressGHEConnections = successfulGHEConnections.filter(
(connection) =>
connection.syncStatus === "IN PROGRESS" ||
connection.syncStatus === "PENDING"
);
inProgressGHESubIds = inProgressGHEConnections.map(
(connection) => connection.subscriptionId
);
}
}
return [...inProgressCloudSubIds, ...inProgressGHESubIds];
};

const getUpdatedCloudSubs = (
currentSubs: GHSubscriptions,
subscriptionId: number,
subscription: SubscriptionBackfillState
) => {
let matchedIndex;
const successfulCloudConnections =
currentSubs?.ghCloudSubscriptions.successfulCloudConnections;
if (successfulCloudConnections) {
matchedIndex = successfulCloudConnections.findIndex(
(connection) => connection.subscriptionId === subscriptionId
);
successfulCloudConnections[matchedIndex] = {
...successfulCloudConnections[matchedIndex],
numberOfSyncedRepos: subscription.syncedRepos,
syncStatus: subscription.syncStatus,
};
if (subscription.backfillSince) {
successfulCloudConnections[matchedIndex]["backfillSince"] =
subscription.backfillSince;
}
return {
...currentSubs,
ghCloudSubscriptions: {
...currentSubs.ghCloudSubscriptions,
successfulCloudConnections: successfulCloudConnections,
},
};
}
};

const getUpdatedGHESubs = (
currentSubs: GHSubscriptions,
subscription: SubscriptionBackfillState
) => {
const ghEnterpriseServers = currentSubs.ghEnterpriseServers;
let ghEnterpriseServerIndex;
let applicationIndex;
for (const [
ghEnterpriseServerI,
ghEnterpriseServer,
] of ghEnterpriseServers.entries()) {
const applications = ghEnterpriseServer.applications;

for (const [appIndex, app] of applications.entries()) {
if (app.id === subscription.gitHubAppId) {
ghEnterpriseServerIndex = ghEnterpriseServerI;
applicationIndex = appIndex;
break;
}
}
}
if (
typeof ghEnterpriseServerIndex === "number" &&
!isNaN(ghEnterpriseServerIndex) &&
typeof applicationIndex === "number" &&
!isNaN(applicationIndex)
) {
const newGHEnterpriseServers = ghEnterpriseServers;
const apps = ghEnterpriseServers[ghEnterpriseServerIndex].applications;
const newApps = [...apps];

if (subscription.gitHubAppId) {
const successfulConnections =
newApps[applicationIndex]?.successfulConnections;
const newSuccessfulConnections = successfulConnections.map(
(connection) => {
if (connection.subscriptionId === subscription.id) {
const result = {
...connection,
syncStatus: subscription.syncStatus,
numberOfSyncedRepos: subscription.syncedRepos,

};
if (subscription.backfillSince) {
result["backfillSince"] =
subscription.backfillSince;
}
return result;
}
return connection;
}
);

newApps[applicationIndex] = {
...newApps[applicationIndex],
successfulConnections: [...newSuccessfulConnections],
};
}

newGHEnterpriseServers[ghEnterpriseServerIndex] = {
...ghEnterpriseServers[ghEnterpriseServerIndex],
applications: newApps,
};
const result = {
...currentSubs,
ghEnterpriseServers: newGHEnterpriseServers,
};
return result;
}
};

export const getUpdatedSubscriptions = (
response: BackfillStatusResp,
subscriptions: GHSubscriptions
): GHSubscriptions | undefined => {
const currentSubs = subscriptions;
let newSubs;
const subscriptionIds = response.subscriptionIds || [];
if (currentSubs) {
for (const subscriptionId of subscriptionIds) {
const subscriptions = response.subscriptions;
const subscription = subscriptions[subscriptionId];
if (!subscription.gitHubAppId) {
newSubs = getUpdatedCloudSubs(
currentSubs,
subscriptionId,
subscription
);
} else {
newSubs = getUpdatedGHESubs(currentSubs, subscription);
}
}
return newSubs;
}
};
Loading
Loading