Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

Commit

Permalink
Merge branch 'main' into DEVPROD-1976_execution-tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
minnakt committed Mar 20, 2024
2 parents 3d130eb + 97c3fb4 commit 51545bc
Show file tree
Hide file tree
Showing 11 changed files with 351 additions and 118 deletions.
23 changes: 17 additions & 6 deletions .evergreen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ post:
- func: assume-ec2-role
- func: attach-codegen-diff
- func: attach-cypress-results
- func: attach-logkeeper-logs
- func: attach-source-map
- func: attach-storybook
- func: attach-test-results
Expand Down Expand Up @@ -251,12 +252,9 @@ functions:
shell: bash
script: |
${PREPARE_SHELL}
# Only record to cypress cloud if this is a pr, mainline commit or an intentional patch.
# And only allow spec filtering for an intentional patch.
if [[ "${requester}" == "github_pr" || "${requester}" == "commit" ]]; then
yarn cy:run --record --key "${spruce_cypress_record_key}" --reporter junit
elif [[ "${requester}" == "patch" ]]; then
yarn cy:run --record --key "${spruce_cypress_record_key}" --reporter junit --spec "${cypress_spec}"
# Allow spec filtering for an intentional patch.
if [[ "${requester}" == "patch" ]]; then
yarn cy:run --reporter junit --spec "${cypress_spec}"
else
yarn cy:run --reporter junit
fi
Expand Down Expand Up @@ -297,6 +295,19 @@ functions:
files:
- "./spruce/bin/cypress/*.xml"

attach-logkeeper-logs:
command: s3.put
type: system
params:
aws_key: ${AWS_ACCESS_KEY_ID}
aws_secret: ${AWS_SECRET_ACCESS_KEY}
aws_session_token: ${AWS_SESSION_TOKEN}
local_file: spruce/logkeeper/logkeeperapp.log
remote_file: spruce/${task_id}/${execution}/logkeeperapp.log
bucket: mciuploads
content_type: text/plain
permissions: public-read

attach-source-map:
command: s3.put
type: system
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spruce",
"version": "3.0.223",
"version": "3.0.226",
"private": true,
"scripts": {
"bootstrap-logkeeper": "./scripts/bootstrap-logkeeper.sh",
Expand Down Expand Up @@ -65,7 +65,7 @@
"@leafygreen-ui/card": "10.0.5",
"@leafygreen-ui/checkbox": "12.0.5",
"@leafygreen-ui/code": "14.2.18",
"@leafygreen-ui/combobox": "7.2.0",
"@leafygreen-ui/combobox": "8.1.1",
"@leafygreen-ui/confirmation-modal": "5.0.9",
"@leafygreen-ui/emotion": "4.0.7",
"@leafygreen-ui/expandable-card": "3.0.5",
Expand Down
7 changes: 6 additions & 1 deletion src/analytics/projectHealth/useProjectHealthAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ type Action =
name: "Add Notification";
subscription: SaveSubscriptionForUserMutationVariables["subscription"];
}
| { name: "Toggle view"; toggle: ProjectHealthView };
| { name: "Toggle view"; toggle: ProjectHealthView }
| {
name: "Redirect to project identifier";
projectId: string;
projectIdentifier: string;
};

export const useProjectHealthAnalytics = (p: { page: pageType }) =>
useAnalyticsRoot<Action>("ProjectHealthPages", { page: p.page });
17 changes: 12 additions & 5 deletions src/components/Header/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ import { useAuthStateContext } from "context/Auth";
import { UserQuery, SpruceConfigQuery } from "gql/generated/types";
import { USER, SPRUCE_CONFIG } from "gql/queries";
import { useLegacyUIURL } from "hooks";
import { validators } from "utils";
import { AuxiliaryDropdown } from "./AuxiliaryDropdown";
import { UserDropdown } from "./UserDropdown";

const { validateObjectId } = validators;

const { blue, gray, white } = palette;

export const Navbar: React.FC = () => {
Expand All @@ -33,16 +36,20 @@ export const Navbar: React.FC = () => {
const { projectIdentifier: projectFromUrl } = useParams<{
projectIdentifier: string;
}>();
const currProject = Cookies.get(CURRENT_PROJECT);

// Update current project cookie if the project in the URL does not equal the cookie value.
// Update current project cookie if the project in the URL is not an objectId and is not equal
// to the current project.
// This will inform future navigations to the /commits page.
useEffect(() => {
if (projectFromUrl && projectFromUrl !== Cookies.get(CURRENT_PROJECT)) {
if (
projectFromUrl &&
!validateObjectId(projectFromUrl) &&
projectFromUrl !== currProject
) {
Cookies.set(CURRENT_PROJECT, projectFromUrl);
}
}, [projectFromUrl]);

const currProject = projectFromUrl ?? Cookies.get(CURRENT_PROJECT);
}, [currProject, projectFromUrl]);

const { data: configData } = useQuery<SpruceConfigQuery>(SPRUCE_CONFIG, {
skip: currProject !== undefined,
Expand Down
9 changes: 9 additions & 0 deletions src/gql/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7416,6 +7416,15 @@ export type ProjectSettingsQuery = {
};
};

export type ProjectQueryVariables = Exact<{
idOrIdentifier: Scalars["String"]["input"];
}>;

export type ProjectQuery = {
__typename?: "Query";
project: { __typename?: "Project"; id: string; identifier: string };
};

export type ProjectsQueryVariables = Exact<{ [key: string]: never }>;

export type ProjectsQuery = {
Expand Down
2 changes: 2 additions & 0 deletions src/gql/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import PROJECT_EVENT_LOGS from "./project-event-logs.graphql";
import PROJECT_HEALTH_VIEW from "./project-health-view.graphql";
import PROJECT_PATCHES from "./project-patches.graphql";
import PROJECT_SETTINGS from "./project-settings.graphql";
import PROJECT from "./project.graphql";
import PROJECTS from "./projects.graphql";
import MY_PUBLIC_KEYS from "./public-keys.graphql";
import REPO_EVENT_LOGS from "./repo-event-logs.graphql";
Expand Down Expand Up @@ -124,6 +125,7 @@ export {
PATCH,
POD_EVENTS,
POD,
PROJECT,
PROJECT_BANNER,
PROJECT_EVENT_LOGS,
PROJECT_HEALTH_VIEW,
Expand Down
6 changes: 6 additions & 0 deletions src/gql/queries/project.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
query Project($idOrIdentifier: String!) {
project(projectIdentifier: $idOrIdentifier) {
id
identifier
}
}
45 changes: 45 additions & 0 deletions src/hooks/useProjectRedirect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useQuery } from "@apollo/client";
import { useParams, useLocation, useNavigate } from "react-router-dom";
import { ProjectQuery, ProjectQueryVariables } from "gql/generated/types";
import { PROJECT } from "gql/queries";
import { validators } from "utils";

const { validateObjectId } = validators;

interface UseProjectRedirectProps {
sendAnalyticsEvent: (projectId: string, projectIdentifier: string) => void;
}

/**
* useProjectRedirect will replace the project id with the project identifier in the URL.
* @param props - Object containing the following:
* @param props.sendAnalyticsEvent - analytics event to send upon redirect
* @returns isRedirecting - boolean to indicate if a redirect is in progress
*/
export const useProjectRedirect = ({
sendAnalyticsEvent = () => {},
}: UseProjectRedirectProps) => {
const { projectIdentifier: project } = useParams<{
projectIdentifier: string;
}>();
const navigate = useNavigate();
const location = useLocation();

const needsRedirect = validateObjectId(project);

const { loading } = useQuery<ProjectQuery, ProjectQueryVariables>(PROJECT, {
skip: !needsRedirect,
variables: {
idOrIdentifier: project,
},
onCompleted: (projectData) => {
const { identifier } = projectData.project;
const currentUrl = location.pathname.concat(location.search);
const redirectPathname = currentUrl.replace(project, identifier);
sendAnalyticsEvent(project, identifier);
navigate(redirectPathname);
},
});

return { isRedirecting: needsRedirect && loading };
};
157 changes: 157 additions & 0 deletions src/hooks/useProjectRedirect/useProjectRedirect.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { MockedProvider } from "@apollo/client/testing";
import { GraphQLError } from "graphql";
import { MemoryRouter, Routes, Route, useLocation } from "react-router-dom";
import { ProjectQuery, ProjectQueryVariables } from "gql/generated/types";
import { PROJECT } from "gql/queries";
import { renderHook, waitFor } from "test_utils";
import { ApolloMock } from "types/gql";
import { useProjectRedirect } from ".";

const useJointHook = ({
sendAnalyticsEvent,
}: {
sendAnalyticsEvent: (projectId: string, projectIdentifier: string) => void;
}) => {
const { isRedirecting } = useProjectRedirect({ sendAnalyticsEvent });
const { pathname, search } = useLocation();
return { isRedirecting, pathname, search };
};

const ProviderWrapper: React.FC<{
children: React.ReactNode;
location: string;
}> = ({ children, location }) => (
<MockedProvider mocks={[repoMock, projectMock]}>
<MemoryRouter initialEntries={[location]}>
<Routes>
<Route element={children} path="/commits/:projectIdentifier" />
</Routes>
</MemoryRouter>
</MockedProvider>
);

describe("useProjectRedirect", () => {
it("should not redirect if URL has project identifier", async () => {
const sendAnalyticsEvent = jest.fn();
const { result } = renderHook(() => useJointHook({ sendAnalyticsEvent }), {
wrapper: ({ children }) =>
ProviderWrapper({ children, location: "/commits/my-project" }),
});
expect(result.current).toMatchObject({
isRedirecting: false,
pathname: "/commits/my-project",
search: "",
});
expect(sendAnalyticsEvent).toHaveBeenCalledTimes(0);
});

it("should redirect if URL has project ID", async () => {
const sendAnalyticsEvent = jest.fn();
const { result } = renderHook(() => useJointHook({ sendAnalyticsEvent }), {
wrapper: ({ children }) =>
ProviderWrapper({ children, location: `/commits/${projectId}` }),
});
expect(result.current).toMatchObject({
isRedirecting: true,
pathname: "/commits/5f74d99ab2373627c047c5e5",
search: "",
});
await waitFor(() => {
expect(result.current).toMatchObject({
isRedirecting: false,
pathname: "/commits/my-project",
search: "",
});
});
expect(sendAnalyticsEvent).toHaveBeenCalledTimes(1);
expect(sendAnalyticsEvent).toHaveBeenCalledWith(
"5f74d99ab2373627c047c5e5",
"my-project",
);
});

it("should preserve query params when redirecting", async () => {
const sendAnalyticsEvent = jest.fn();
const { result } = renderHook(() => useJointHook({ sendAnalyticsEvent }), {
wrapper: ({ children }) =>
ProviderWrapper({
children,
location: `/commits/${projectId}?taskName=thirdparty`,
}),
});
expect(result.current).toMatchObject({
isRedirecting: true,
pathname: "/commits/5f74d99ab2373627c047c5e5",
search: "?taskName=thirdparty",
});
await waitFor(() => {
expect(result.current).toMatchObject({
isRedirecting: false,
pathname: "/commits/my-project",
search: "?taskName=thirdparty",
});
});
expect(sendAnalyticsEvent).toHaveBeenCalledTimes(1);
expect(sendAnalyticsEvent).toHaveBeenCalledWith(
"5f74d99ab2373627c047c5e5",
"my-project",
);
});

it("should attempt redirect if URL has repo ID but stop attempting after query", async () => {
const sendAnalyticsEvent = jest.fn();
const { result } = renderHook(() => useJointHook({ sendAnalyticsEvent }), {
wrapper: ({ children }) =>
ProviderWrapper({ children, location: `/commits/${repoId}` }),
});
expect(result.current).toMatchObject({
isRedirecting: true,
pathname: "/commits/5e6bb9e23066155a993e0f1a",
search: "",
});
await waitFor(() => {
expect(result.current).toMatchObject({
isRedirecting: false,
pathname: "/commits/5e6bb9e23066155a993e0f1a",
search: "",
});
});
expect(sendAnalyticsEvent).toHaveBeenCalledTimes(0);
});
});

const projectId = "5f74d99ab2373627c047c5e5";
const projectMock: ApolloMock<ProjectQuery, ProjectQueryVariables> = {
request: {
query: PROJECT,
variables: {
idOrIdentifier: projectId,
},
},
result: {
data: {
project: {
__typename: "Project",
id: projectId,
identifier: "my-project",
},
},
},
};

const repoId = "5e6bb9e23066155a993e0f1a";
const repoMock: ApolloMock<ProjectQuery, ProjectQueryVariables> = {
request: {
query: PROJECT,
variables: {
idOrIdentifier: repoId,
},
},
result: {
errors: [
new GraphQLError(
`Error finding project by id ${repoId}: 404 (Not Found): project '${repoId}' not found`,
),
],
},
};
Loading

0 comments on commit 51545bc

Please sign in to comment.