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 50dfa9a commit b4654fc
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 18 deletions.
70 changes: 70 additions & 0 deletions ui-v2/src/hooks/automations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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,
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 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);
});
});
60 changes: 60 additions & 0 deletions ui-v2/src/hooks/automations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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"];

/**
* ```
* 🏗️ Automations queries construction 👷
* all => ['automations'] // key to match ['automationss', ...
* list => ['automations', 'list'] // key to match ['automations, 'list', ...
* ['automations', 'list', { ...filter1 }]
* ['automations', 'list', { ...filter2 }]
* 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,
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 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,
loader: ({ context, params }) =>
context.queryClient.ensureQueryData(buildGetAutomationQuery(params.id)),
wrapInSuspense: true,
});

function RouteComponent() {
Expand Down
5 changes: 5 additions & 0 deletions ui-v2/src/routes/automations/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { 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,
loader: ({ context }) =>
context.queryClient.ensureQueryData(buildListAutomationsQuery()),
wrapInSuspense: true,
});

function RouteComponent() {
Expand Down
58 changes: 40 additions & 18 deletions ui-v2/tests/utils/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
import { http, HttpResponse } from "msw";

const automationsHandlers = [
http.post("http://localhost:4200/api/automations/filter", () => {
return HttpResponse.json([]);
}),

http.post("http://localhost:4200/api/automations/", () => {
return HttpResponse.json({ status: "success" }, { status: 201 });
}),

http.patch("http://localhost:4200/api/automations/:id", () => {
return new HttpResponse(null, { status: 204 });
}),
http.delete("http://localhost:4200/api/api/:id", () => {
return HttpResponse.json({ status: 204 });
}),
];

const flowHandlers = [
http.post("http://localhost:4200/api/flows/paginate", () => {
return HttpResponse.json({
results: [
{ id: "1", name: "Flow 1", tags: [] },
{ id: "2", name: "Flow 2", tags: [] },
],
});
}),
http.post("http://localhost:4200/api/flow_runs/filter", () => {
return HttpResponse.json([
{ id: "1", name: "Flow 1", tags: [] },
{ id: "2", name: "Flow 2", tags: [] },
]);
}),

http.post("http://localhost:4200/api/deployments/count", () => {
return HttpResponse.json(1);
}),
];

const globalConcurrencyLimitsHandlers = [
http.post("http://localhost:4200/api/v2/concurrency_limits/filter", () => {
return HttpResponse.json([]);
Expand Down Expand Up @@ -58,24 +96,8 @@ const variablesHandlers = [
];

export const handlers = [
http.post("http://localhost:4200/api/flows/paginate", () => {
return HttpResponse.json({
results: [
{ id: "1", name: "Flow 1", tags: [] },
{ id: "2", name: "Flow 2", tags: [] },
],
});
}),
http.post("http://localhost:4200/api/flow_runs/filter", () => {
return HttpResponse.json([
{ id: "1", name: "Flow 1", tags: [] },
{ id: "2", name: "Flow 2", tags: [] },
]);
}),

http.post("http://localhost:4200/api/deployments/count", () => {
return HttpResponse.json(1);
}),
...automationsHandlers,
...flowHandlers,
...globalConcurrencyLimitsHandlers,
...taskRunConcurrencyLimitsHandlers,
...variablesHandlers,
Expand Down

0 comments on commit b4654fc

Please sign in to comment.