Skip to content

Commit

Permalink
[UI v2] feat: Start automations data queries
Browse files Browse the repository at this point in the history
  • Loading branch information
devinvillarosa committed Jan 2, 2025
1 parent 670fb80 commit 1e2acae
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 0 deletions.
95 changes: 95 additions & 0 deletions ui-v2/src/hooks/automations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { renderHook, waitFor } from "@testing-library/react";
import { createWrapper, server } from "@tests/utils";
import { http, HttpResponse } from "msw";
import { describe, expect, it } from "vitest";

import { createFakeAutomation } from "@/mocks";

import {
type Automation,
buildCountAllAutomationsQuery,
buildGetAutomationQuery,
buildListAutomationsQuery,
} from "./automations";

describe("automations queries", () => {
const seedAutomationsData = () => [
createFakeAutomation(),
createFakeAutomation(),
];

const mockFetchListAutomationsAPI = (automations: Array<Automation>) => {
server.use(
http.post("http://localhost:4200/api/automations/filter", () => {
return HttpResponse.json(automations);
}),
);
};

const mockFetchCountAutomationsAPI = (count: number) => {
server.use(
http.post("http://localhost:4200/api/automations/count", () => {
return HttpResponse.json(count);
}),
);
};

const mockFetchGetAutomationsAPI = (automation: Automation) => {
server.use(
http.get("http://localhost:4200/api/automations/:id", () => {
return HttpResponse.json(automation);
}),
);
};

const filter = { sort: "CREATED_DESC", offset: 0 } as const;
it("is stores automation list data", async () => {
// ------------ Mock API requests when cache is empty
const mockList = seedAutomationsData();
mockFetchListAutomationsAPI(mockList);

// ------------ Initialize hooks to test
const { result } = renderHook(
() => useSuspenseQuery(buildListAutomationsQuery(filter)),
{ wrapper: createWrapper() },
);

// ------------ Assert
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual(mockList);
});

it("is retrieves single automation data", async () => {
// ------------ Mock API requests when cache is empty
const MOCK_ID = "0";
const mockData = createFakeAutomation({ id: MOCK_ID });
mockFetchGetAutomationsAPI(mockData);

// ------------ Initialize hooks to test
const { result } = renderHook(
() => useSuspenseQuery(buildGetAutomationQuery(MOCK_ID)),
{ wrapper: createWrapper() },
);

// ------------ Assert
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual(mockData);
});

it("is retrieves all automations count", async () => {
// ------------ Mock API requests when cache is empty
const mockData = 3;
mockFetchCountAutomationsAPI(mockData);

// ------------ Initialize hooks to test
const { result } = renderHook(
() => useSuspenseQuery(buildCountAllAutomationsQuery()),
{ wrapper: createWrapper() },
);

// ------------ Assert
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual(mockData);
});
});
80 changes: 80 additions & 0 deletions ui-v2/src/hooks/automations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { components } from "@/api/prefect";
import { getQueryService } from "@/api/service";
import { queryOptions } from "@tanstack/react-query";

export type Automation = components["schemas"]["Automation"];
export type AutomationsFilter =
components["schemas"]["Body_read_automations_automations_filter_post"];
export type AutomationsCountFilter =
components["schemas"]["Body_read_automations_automations_filter_post"];

/**
* ```
* 🏗️ Automations queries construction 👷
* all => ['automations'] // key to match ['automationss', ...
* list => ['automations', 'list'] // key to match ['automations, 'list', ...
* ['automations', 'list', { ...filter1 }]
* ['automations', 'list', { ...filter2 }]
* count => ['automations', 'count'] // key to match ['automations, 'count', ...
* ['automations', 'count', 'all-count']
* details => ['automations', 'details'] // key to match ['automations', 'details', ...
* ['automations', 'details', id1]
* ['automations', 'details', id2]
* ```
* */
export const queryKeyFactory = {
all: () => ["automations"] as const,
lists: () => [...queryKeyFactory.all(), "list"] as const,
list: (filter: AutomationsFilter) =>
[...queryKeyFactory.lists(), filter] as const,
counts: () => [...queryKeyFactory.all(), "count"] as const,
countAll: () => [...queryKeyFactory.lists(), "all-count"] as const,
count: (filter: AutomationsFilter) =>
[...queryKeyFactory.lists(), filter] as const,
details: () => [...queryKeyFactory.all(), "details"] as const,
detail: (id: string) => [...queryKeyFactory.details(), id] as const,
};

// ----- 🔑 Queries 🗄️
// ----------------------------
export const buildListAutomationsQuery = (
filter: AutomationsFilter = { sort: "CREATED_DESC", offset: 0 },
) =>
queryOptions({
queryKey: queryKeyFactory.list(filter),
queryFn: async () => {
const res = await getQueryService().POST("/automations/filter", {
body: filter,
});
if (!res.data) {
throw new Error("'data' expected");
}
return res.data;
},
});

export const buildCountAllAutomationsQuery = () =>
queryOptions({
queryKey: queryKeyFactory.countAll(),
queryFn: async () => {
const res = await getQueryService().POST("/automations/count");
if (!res.data) {
throw new Error("'data' expected");
}
return res.data;
},
});

export const buildGetAutomationQuery = (id: string) =>
queryOptions({
queryKey: queryKeyFactory.detail(id),
queryFn: async () => {
const res = await getQueryService().GET("/automations/{id}", {
params: { path: { id } },
});
if (!res.data) {
throw new Error("'data' expected");
}
return res.data;
},
});
37 changes: 37 additions & 0 deletions ui-v2/src/mocks/create-fake-automation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { components } from "@/api/prefect";
import { faker } from "@faker-js/faker";

export const createFakeAutomation = (
overrides?: Partial<components["schemas"]["Automation"]>,
): components["schemas"]["Automation"] => {
return {
name: `${faker.word.adjective()} automation`,
description: `${faker.word.adjective()} ${faker.word.noun()}`,
enabled: faker.datatype.boolean(),
trigger: {
type: "event",
id: faker.string.uuid(),
match: {
"prefect.resource.id": "prefect.deployment.*",
},
match_related: {},
after: [],
expect: ["prefect.deployment.not-ready"],
for_each: ["prefect.resource.id"],
posture: "Reactive",
threshold: 1,
within: 0,
},
actions: [
{
type: "cancel-flow-run",
},
],
actions_on_trigger: [],
actions_on_resolve: [],
id: faker.string.uuid(),
created: faker.date.past().toISOString(),
updated: faker.date.past().toISOString(),
...overrides,
};
};
1 change: 1 addition & 0 deletions ui-v2/src/mocks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { createFakeAutomation } from "./create-fake-automation";
5 changes: 5 additions & 0 deletions ui-v2/src/routes/automations/automation.$id.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { createFileRoute } from "@tanstack/react-router";

import { buildGetAutomationQuery } from "@/hooks/automations";

export const Route = createFileRoute("/automations/automation/$id")({
component: RouteComponent,
wrapInSuspense: true,
loader: ({ context, params }) =>
context.queryClient.ensureQueryData(buildGetAutomationQuery(params.id)),
});

function RouteComponent() {
Expand Down
11 changes: 11 additions & 0 deletions ui-v2/src/routes/automations/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import {
buildCountAllAutomationsQuery,
buildListAutomationsQuery,
} from "@/hooks/automations";
import { createFileRoute } from "@tanstack/react-router";

// nb: Currently, there is no filtering or search params used on this page
export const Route = createFileRoute("/automations/")({
component: RouteComponent,
wrapInSuspense: true,
loader: ({ context }) =>
Promise.all([
context.queryClient.ensureQueryData(buildCountAllAutomationsQuery()),
context.queryClient.ensureQueryData(buildListAutomationsQuery()),
]),
});

function RouteComponent() {
Expand Down

0 comments on commit 1e2acae

Please sign in to comment.