chore(site): move templates fetching to react-query (#9622)

This commit is contained in:
Bruno Quaresma
2023-09-12 14:09:16 -03:00
committed by GitHub
parent b7550bfda5
commit 6db89b0372
6 changed files with 88 additions and 160 deletions
+17
View File
@@ -0,0 +1,17 @@
import * as API from "api/api";
const getTemplatesQueryKey = (orgId: string) => [orgId, "templates"];
export const templates = (orgId: string) => {
return {
queryKey: getTemplatesQueryKey(orgId),
queryFn: () => API.getTemplates(orgId),
};
};
export const templateExamples = (orgId: string) => {
return {
queryKey: [...getTemplatesQueryKey(orgId), "examples"],
queryFn: () => API.getTemplateExamples(orgId),
};
};
@@ -9,7 +9,6 @@ import { TemplateExampleCard } from "components/TemplateExampleCard/TemplateExam
import { FC } from "react";
import { Link as RouterLink } from "react-router-dom";
import { docs } from "utils/docs";
import { Permissions } from "xServices/auth/authXService";
// Those are from https://github.com/coder/coder/tree/main/examples/templates
const featuredExampleIds = [
@@ -37,13 +36,13 @@ const findFeaturedExamples = (examples: TemplateExample[]) => {
};
export const EmptyTemplates: FC<{
permissions: Permissions;
canCreateTemplates: boolean;
examples: TemplateExample[];
}> = ({ permissions, examples }) => {
}> = ({ canCreateTemplates, examples }) => {
const styles = useStyles();
const featuredExamples = findFeaturedExamples(examples);
if (permissions.createTemplates) {
if (canCreateTemplates) {
return (
<TableEmpty
message="Create a Template"
+13 -8
View File
@@ -1,28 +1,33 @@
import { useMachine } from "@xstate/react";
import { useOrganizationId } from "hooks/useOrganizationId";
import { usePermissions } from "hooks/usePermissions";
import { FC } from "react";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "../../utils/page";
import { templatesMachine } from "../../xServices/templates/templatesXService";
import { TemplatesPageView } from "./TemplatesPageView";
import { templateExamples, templates } from "api/queries/templates";
import { useQuery } from "@tanstack/react-query";
export const TemplatesPage: FC = () => {
const organizationId = useOrganizationId();
const permissions = usePermissions();
const [templatesState] = useMachine(templatesMachine, {
context: {
organizationId,
permissions,
},
const templatesQuery = useQuery(templates(organizationId));
const examplesQuery = useQuery({
...templateExamples(organizationId),
enabled: permissions.createTemplates,
});
const error = templatesQuery.error || examplesQuery.error;
return (
<>
<Helmet>
<title>{pageTitle("Templates")}</title>
</Helmet>
<TemplatesPageView context={templatesState.context} />
<TemplatesPageView
error={error}
canCreateTemplates={permissions.createTemplates}
examples={examplesQuery.data}
templates={templatesQuery.data}
/>
</>
);
};
@@ -1,7 +1,5 @@
import {
mockApiError,
MockOrganization,
MockPermissions,
MockTemplate,
MockTemplateExample,
MockTemplateExample2,
@@ -19,32 +17,29 @@ type Story = StoryObj<typeof TemplatesPageView>;
export const WithTemplates: Story = {
args: {
context: {
organizationId: MockOrganization.id,
permissions: MockPermissions,
error: undefined,
templates: [
MockTemplate,
{
...MockTemplate,
active_user_count: -1,
description: "🚀 Some new template that has no activity data",
icon: "/icon/goland.svg",
},
{
...MockTemplate,
active_user_count: 150,
description: "😮 Wow, this one has a bunch of usage!",
icon: "",
},
{
...MockTemplate,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
},
],
examples: [],
},
canCreateTemplates: true,
error: undefined,
templates: [
MockTemplate,
{
...MockTemplate,
active_user_count: -1,
description: "🚀 Some new template that has no activity data",
icon: "/icon/goland.svg",
},
{
...MockTemplate,
active_user_count: 150,
description: "😮 Wow, this one has a bunch of usage!",
icon: "",
},
{
...MockTemplate,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
},
],
examples: [],
},
};
@@ -59,44 +54,29 @@ export const WithTemplatesSmallViewPort: Story = {
export const EmptyCanCreate: Story = {
args: {
context: {
organizationId: MockOrganization.id,
permissions: MockPermissions,
error: undefined,
templates: [],
examples: [MockTemplateExample, MockTemplateExample2],
},
canCreateTemplates: true,
error: undefined,
templates: [],
examples: [MockTemplateExample, MockTemplateExample2],
},
};
export const EmptyCannotCreate: Story = {
args: {
context: {
organizationId: MockOrganization.id,
permissions: {
...MockPermissions,
createTemplates: false,
},
error: undefined,
templates: [],
examples: [MockTemplateExample, MockTemplateExample2],
},
error: undefined,
templates: [],
examples: [MockTemplateExample, MockTemplateExample2],
canCreateTemplates: false,
},
};
export const Error: Story = {
args: {
context: {
organizationId: MockOrganization.id,
permissions: {
...MockPermissions,
createTemplates: false,
},
error: mockApiError({
message: "Something went wrong fetching templates.",
}),
templates: undefined,
examples: undefined,
},
error: mockApiError({
message: "Something went wrong fetching templates.",
}),
templates: undefined,
examples: undefined,
canCreateTemplates: false,
},
};
@@ -16,18 +16,18 @@ import {
formatTemplateBuildTime,
formatTemplateActiveDevelopers,
} from "utils/templates";
import { AvatarData } from "../../components/AvatarData/AvatarData";
import { Margins } from "../../components/Margins/Margins";
import { AvatarData } from "components/AvatarData/AvatarData";
import { Margins } from "components/Margins/Margins";
import {
PageHeader,
PageHeaderSubtitle,
PageHeaderTitle,
} from "../../components/PageHeader/PageHeader";
import { Stack } from "../../components/Stack/Stack";
} from "components/PageHeader/PageHeader";
import { Stack } from "components/Stack/Stack";
import {
TableLoaderSkeleton,
TableRowSkeleton,
} from "../../components/TableLoader/TableLoader";
} from "components/TableLoader/TableLoader";
import {
HelpTooltip,
HelpTooltipLink,
@@ -36,9 +36,8 @@ import {
HelpTooltipTitle,
} from "components/HelpTooltip/HelpTooltip";
import { EmptyTemplates } from "./EmptyTemplates";
import { TemplatesContext } from "xServices/templates/templatesXService";
import { useClickableTableRow } from "hooks/useClickableTableRow";
import { Template } from "api/typesGenerated";
import { Template, TemplateExample } from "api/typesGenerated";
import { combineClasses } from "utils/combineClasses";
import { colors } from "theme/colors";
import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined";
@@ -141,13 +140,18 @@ const TemplateRow: FC<{ template: Template }> = ({ template }) => {
};
export interface TemplatesPageViewProps {
context: TemplatesContext;
error?: unknown;
examples: TemplateExample[] | undefined;
templates: Template[] | undefined;
canCreateTemplates: boolean;
}
export const TemplatesPageView: FC<
React.PropsWithChildren<TemplatesPageViewProps>
> = ({ context }) => {
const { templates, error, examples, permissions } = context;
export const TemplatesPageView: FC<TemplatesPageViewProps> = ({
templates,
error,
examples,
canCreateTemplates,
}) => {
const isLoading = !templates;
const isEmpty = Boolean(templates && templates.length === 0);
@@ -155,7 +159,7 @@ export const TemplatesPageView: FC<
<Margins>
<PageHeader
actions={
<Maybe condition={permissions.createTemplates}>
<Maybe condition={canCreateTemplates}>
<Button component={RouterLink} to="/starter-templates">
Starter Templates
</Button>
@@ -208,7 +212,7 @@ export const TemplatesPageView: FC<
<ChooseOne>
<Cond condition={isEmpty}>
<EmptyTemplates
permissions={permissions}
canCreateTemplates={canCreateTemplates}
examples={examples ?? []}
/>
</Cond>
@@ -1,77 +0,0 @@
import { Permissions } from "xServices/auth/authXService";
import { assign, createMachine } from "xstate";
import * as API from "../../api/api";
import * as TypesGen from "../../api/typesGenerated";
export interface TemplatesContext {
organizationId: string;
permissions: Permissions;
templates?: TypesGen.Template[];
examples?: TypesGen.TemplateExample[];
error?: unknown;
}
export const templatesMachine = createMachine(
{
id: "templatesState",
predictableActionArguments: true,
tsTypes: {} as import("./templatesXService.typegen").Typegen0,
schema: {
context: {} as TemplatesContext,
services: {} as {
load: {
data: {
templates: TypesGen.Template[];
examples: TypesGen.TemplateExample[];
};
};
},
},
initial: "loading",
states: {
loading: {
invoke: {
src: "load",
id: "load",
onDone: {
actions: ["assignData"],
target: "idle",
},
onError: {
actions: "assignError",
target: "idle",
},
},
},
idle: {
type: "final",
},
},
},
{
actions: {
assignData: assign({
templates: (_, event) => event.data.templates,
examples: (_, event) => event.data.examples,
}),
assignError: assign({
error: (_, { data }) => data,
}),
},
services: {
load: async ({ organizationId, permissions }) => {
const [templates, examples] = await Promise.all([
API.getTemplates(organizationId),
permissions.createTemplates
? API.getTemplateExamples(organizationId)
: Promise.resolve([]),
]);
return {
templates,
examples,
};
},
},
},
);