Skip to content

Commit

Permalink
DEVPROD-10189: Add pagination to waterfall (#461)
Browse files Browse the repository at this point in the history
  • Loading branch information
minnakt authored Nov 6, 2024
1 parent 9651b7f commit ba2ac38
Show file tree
Hide file tree
Showing 19 changed files with 546 additions and 109 deletions.
61 changes: 61 additions & 0 deletions apps/spruce/cypress/integration/waterfall/pagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
describe("pagination", () => {
beforeEach(() => {
cy.visit("/project/spruce/waterfall");
});

it("url query params update as page changes", () => {
cy.location("search").should("equal", "");

cy.dataCy("next-page-button").click();
cy.dataCy("waterfall-skeleton").should("not.exist");
cy.location("search").should("contain", "maxOrder");

cy.dataCy("prev-page-button").click();
cy.dataCy("waterfall-skeleton").should("not.exist");
cy.location("search").should("contain", "minOrder");
});

it("versions update correctly as page changes", () => {
const firstPageFirstCommit = "2ab1c56";
const secondPageFirstCommit = "e391612";

cy.dataCy("version-labels").children().should("have.length", 6);
cy.dataCy("version-labels").children().eq(0).contains(firstPageFirstCommit);

cy.dataCy("next-page-button").click();
cy.dataCy("version-labels").children().should("have.length", 5);
cy.dataCy("version-labels")
.children()
.eq(0)
.contains(secondPageFirstCommit);

cy.dataCy("prev-page-button").click();
cy.dataCy("version-labels").children().should("have.length", 6);
cy.dataCy("version-labels").children().eq(0).contains(firstPageFirstCommit);
});

it("builds update correctly as page changes", () => {
cy.dataCy("build-group").children().as("builds");
cy.get("@builds").eq(0).children().should("have.length", 1);
cy.get("@builds").eq(1).children().should("have.length", 8);
cy.get("@builds").eq(2).children().should("have.length", 0);
cy.get("@builds").eq(3).children().should("have.length", 1);
cy.get("@builds").eq(4).children().should("have.length", 8);
cy.get("@builds").eq(5).children().should("have.length", 8);

cy.dataCy("next-page-button").click();
cy.get("@builds").eq(0).children().should("have.length", 8);
cy.get("@builds").eq(1).children().should("have.length", 0);
cy.get("@builds").eq(2).children().should("have.length", 8);
cy.get("@builds").eq(3).children().should("have.length", 8);
cy.get("@builds").eq(4).children().should("have.length", 1);

cy.dataCy("prev-page-button").click();
cy.get("@builds").eq(0).children().should("have.length", 1);
cy.get("@builds").eq(1).children().should("have.length", 8);
cy.get("@builds").eq(2).children().should("have.length", 0);
cy.get("@builds").eq(3).children().should("have.length", 1);
cy.get("@builds").eq(4).children().should("have.length", 8);
cy.get("@builds").eq(5).children().should("have.length", 8);
});
});
5 changes: 3 additions & 2 deletions apps/spruce/src/analytics/waterfall/useWaterfallAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ type Action =
| { name: "Clicked variant label" }
| { name: "Clicked task box"; "task.status": string }
| { name: "Changed project"; project: string }
| { name: "Filtered by requester"; requesters: string[] }
| { name: "Changed page"; direction: "next" | "previous" }
| { name: "Created build variant filter" }
| { name: "Deleted one filter badge" }
| { name: "Deleted all filter badges" }
| { name: "Filtered by requester"; requesters: string[] };
| { name: "Deleted all filter badges" };

export const useWaterfallAnalytics = () => {
const { [slugs.projectIdentifier]: projectIdentifier } = useParams();
Expand Down
3 changes: 3 additions & 0 deletions apps/spruce/src/gql/client/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,8 @@ export const cache = new InMemoryCache({
},
},
},
WaterfallBuildVariant: {
keyFields: ["version", "id"],
},
},
});
1 change: 1 addition & 0 deletions apps/spruce/src/gql/fragments/waterfall.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ fragment WaterfallVersion on Version {
}
id
message
order
requester
revision
...UpstreamProject
Expand Down
75 changes: 29 additions & 46 deletions apps/spruce/src/gql/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4946,6 +4946,7 @@ export type WaterfallVersionFragment = {
errors: Array<string>;
id: string;
message: string;
order: number;
requester: string;
revision: string;
gitTags?: Array<{ __typename?: "GitTag"; tag: string }> | null;
Expand Down Expand Up @@ -9629,54 +9630,36 @@ export type WaterfallQuery = {
}>;
}>;
}>;
versions: Array<{
__typename?: "WaterfallVersion";
inactiveVersions?: Array<{
__typename?: "Version";
activated?: boolean | null;
author: string;
createTime: Date;
errors: Array<string>;
id: string;
message: string;
requester: string;
revision: string;
gitTags?: Array<{ __typename?: "GitTag"; tag: string }> | null;
upstreamProject?: {
__typename?: "UpstreamProject";
owner: string;
project: string;
repo: string;
revision: string;
triggerID: string;
triggerType: string;
task?: { __typename?: "Task"; execution: number; id: string } | null;
version?: { __typename?: "Version"; id: string } | null;
} | null;
}> | null;
version?: {
__typename?: "Version";
activated?: boolean | null;
author: string;
createTime: Date;
errors: Array<string>;
id: string;
message: string;
requester: string;
flattenedVersions: Array<{
__typename?: "Version";
activated?: boolean | null;
author: string;
createTime: Date;
errors: Array<string>;
id: string;
message: string;
order: number;
requester: string;
revision: string;
gitTags?: Array<{ __typename?: "GitTag"; tag: string }> | null;
upstreamProject?: {
__typename?: "UpstreamProject";
owner: string;
project: string;
repo: string;
revision: string;
gitTags?: Array<{ __typename?: "GitTag"; tag: string }> | null;
upstreamProject?: {
__typename?: "UpstreamProject";
owner: string;
project: string;
repo: string;
revision: string;
triggerID: string;
triggerType: string;
task?: { __typename?: "Task"; execution: number; id: string } | null;
version?: { __typename?: "Version"; id: string } | null;
} | null;
triggerID: string;
triggerType: string;
task?: { __typename?: "Task"; execution: number; id: string } | null;
version?: { __typename?: "Version"; id: string } | null;
} | null;
}>;
pagination: {
__typename?: "WaterfallPagination";
hasNextPage: boolean;
hasPrevPage: boolean;
nextPageOrder: number;
prevPageOrder: number;
};
};
};
15 changes: 8 additions & 7 deletions apps/spruce/src/gql/queries/waterfall.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ query Waterfall($options: WaterfallOptions!) {
id
version
}
versions {
inactiveVersions {
...WaterfallVersion
}
version {
...WaterfallVersion
}
flattenedVersions {
...WaterfallVersion
}
pagination {
hasNextPage
hasPrevPage
nextPageOrder
prevPageOrder
}
}
}
9 changes: 3 additions & 6 deletions apps/spruce/src/pages/waterfall/BuildRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ import { useWaterfallAnalytics } from "analytics";
import { StyledLink } from "components/styles";
import { getTaskRoute, getVariantHistoryRoute } from "constants/routes";
import { size } from "constants/tokens";
import {
WaterfallBuild,
WaterfallBuildVariant,
WaterfallQuery,
} from "gql/generated/types";
import { WaterfallBuild, WaterfallBuildVariant } from "gql/generated/types";
import { statusColorMap, statusIconMap } from "./icons";
import {
BuildVariantTitle,
Expand All @@ -21,13 +17,14 @@ import {
InactiveVersion,
Row,
} from "./styles";
import { WaterfallVersion } from "./types";

const { black, gray, white } = palette;

export const BuildRow: React.FC<{
build: WaterfallBuildVariant;
projectIdentifier: string;
versions: WaterfallQuery["waterfall"]["versions"];
versions: WaterfallVersion[];
}> = ({ build, projectIdentifier, versions }) => {
const { sendEvent } = useWaterfallAnalytics();
const handleVariantClick = useCallback(
Expand Down
71 changes: 71 additions & 0 deletions apps/spruce/src/pages/waterfall/PaginationButtons/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useTransition } from "react";
import styled from "@emotion/styled";
import Button from "@leafygreen-ui/button";
import { useWaterfallAnalytics } from "analytics";
import Icon from "components/Icon";
import { size } from "constants/tokens";
import { WaterfallPagination } from "gql/generated/types";
import { useQueryParams } from "hooks/useQueryParam";
import { WaterfallFilterOptions } from "types/waterfall";

interface PaginationButtonsProps {
pagination: WaterfallPagination | undefined;
}

export const PaginationButtons: React.FC<PaginationButtonsProps> = ({
pagination,
}) => {
const { sendEvent } = useWaterfallAnalytics();
const [, startTransition] = useTransition();
const [queryParams, setQueryParams] = useQueryParams();

const { hasNextPage, hasPrevPage, nextPageOrder, prevPageOrder } =
pagination ?? {};

const onNextClick = () => {
sendEvent({ name: "Changed page", direction: "next" });
startTransition(() => {
setQueryParams({
...queryParams,
[WaterfallFilterOptions.MaxOrder]: nextPageOrder,
[WaterfallFilterOptions.MinOrder]: undefined,
});
});
};

const onPrevClick = () => {
sendEvent({
name: "Changed page",
direction: "previous",
});
startTransition(() => {
setQueryParams({
...queryParams,
[WaterfallFilterOptions.MaxOrder]: undefined,
[WaterfallFilterOptions.MinOrder]: prevPageOrder,
});
});
};

return (
<ButtonContainer>
<Button
data-cy="prev-page-button"
disabled={!hasPrevPage}
leftGlyph={<Icon glyph="ChevronLeft" />}
onClick={onPrevClick}
/>
<Button
data-cy="next-page-button"
disabled={!hasNextPage}
leftGlyph={<Icon glyph="ChevronRight" />}
onClick={onNextClick}
/>
</ButtonContainer>
);
};

const ButtonContainer = styled.div`
display: flex;
gap: ${size.xs};
`;
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const RequesterFilter = () => {
>
{commitRequesters.map((requester) => (
<ComboboxOption
key={`${requester}-option`}
data-cy={`${requester}-option`}
displayName={requesterToTitle[requester]}
value={requester}
Expand Down
7 changes: 6 additions & 1 deletion apps/spruce/src/pages/waterfall/WaterfallFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import { useWaterfallAnalytics } from "analytics";
import { ProjectSelect } from "components/ProjectSelect";
import { getWaterfallRoute } from "constants/routes";
import { size } from "constants/tokens";
import { WaterfallPagination } from "gql/generated/types";
import { NameFilter } from "./NameFilter";
import { PaginationButtons } from "./PaginationButtons";
import { RequesterFilter } from "./RequesterFilter";

type WaterfallFiltersProps = {
projectIdentifier: string;
pagination: WaterfallPagination | undefined;
};
export const WaterfallFilters: React.FC<WaterfallFiltersProps> = ({
pagination,
projectIdentifier,
}) => {
const { sendEvent } = useWaterfallAnalytics();
Expand All @@ -34,6 +38,7 @@ export const WaterfallFilters: React.FC<WaterfallFiltersProps> = ({
selectedProjectIdentifier={projectIdentifier}
/>
</FilterItem>
<PaginationButtons pagination={pagination} />
</Container>
);
};
Expand All @@ -46,6 +51,6 @@ const FilterItem = styled.div`
const Container = styled.div`
display: flex;
justify-content: space-between;
align-items: end;
align-items: flex-end;
gap: ${size.s};
`;
Loading

0 comments on commit ba2ac38

Please sign in to comment.