chore: add paywall to provisioners page (#14803)

* chore: add paywall to provisioners page

* きれい

* move some things into the page view

* I guess I'm not allowed to use proper nouns

* :|
This commit is contained in:
Kayla Washburn-Love
2024-09-25 16:54:49 -06:00
committed by GitHub
parent aef400c2c5
commit 4dcf5ef323
4 changed files with 84 additions and 56 deletions

View File

@ -30,7 +30,7 @@ import { ProvisionerTag } from "./ProvisionerTag";
type ProvisionerGroupType = "builtin" | "psk" | "key";
interface ProvisionerGroupProps {
readonly buildInfo?: BuildInfoResponse;
readonly buildInfo: BuildInfoResponse;
readonly keyName: string;
readonly keyTags: Record<string, string>;
readonly type: ProvisionerGroupType;
@ -80,7 +80,7 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
let warnings = 0;
let provisionersWithWarnings = 0;
const provisionersWithWarningInfo = provisioners.map((it) => {
const outOfDate = Boolean(buildInfo) && it.version !== buildInfo?.version;
const outOfDate = it.version !== buildInfo.version;
const warningCount = outOfDate ? 1 : 0;
warnings += warningCount;
if (warnings > 0) {
@ -292,7 +292,7 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
};
interface ProvisionerVersionPopoverProps {
buildInfo?: BuildInfoResponse;
buildInfo: BuildInfoResponse;
provisioner: ProvisionerDaemon;
}
@ -304,11 +304,9 @@ const ProvisionerVersionPopover: FC<ProvisionerVersionPopoverProps> = ({
<Popover mode="hover">
<PopoverTrigger>
<span>
{buildInfo
? provisioner.version === buildInfo.version
? "Up to date"
: "Out of date"
: provisioner.version}
{provisioner.version === buildInfo.version
? "Up to date"
: "Out of date"}
</span>
</PopoverTrigger>
<PopoverContent
@ -324,7 +322,7 @@ const ProvisionerVersionPopover: FC<ProvisionerVersionPopoverProps> = ({
<p css={styles.text}>{provisioner.version}</p>
<h4 css={styles.versionPopoverTitle}>Protocol version</h4>
<p css={styles.text}>{provisioner.api_version}</p>
{provisioner.api_version !== buildInfo?.provisioner_api_version && (
{provisioner.api_version !== buildInfo.provisioner_api_version && (
<p css={[styles.text, { fontSize: 13 }]}>
This provisioner is out of date. You may experience issues when
using a provisioner version that doesnt match your Coder

View File

@ -3,15 +3,17 @@ import {
organizationsPermissions,
provisionerDaemonGroups,
} from "api/queries/organizations";
import type { Organization, ProvisionerDaemon } from "api/typesGenerated";
import type { Organization } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { EmptyState } from "components/EmptyState/EmptyState";
import { Loader } from "components/Loader/Loader";
import { Paywall } from "components/Paywall/Paywall";
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
import NotFoundPage from "pages/404Page/404Page";
import { useDashboard } from "modules/dashboard/useDashboard";
import type { FC } from "react";
import { useQuery } from "react-query";
import { useParams } from "react-router-dom";
import { docs } from "utils/docs";
import { useOrganizationSettings } from "./ManagementSettingsLayout";
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView";
@ -20,6 +22,7 @@ const OrganizationProvisionersPage: FC = () => {
organization: string;
};
const { organizations } = useOrganizationSettings();
const { entitlements } = useDashboard();
const { metadata } = useEmbeddedMetadata();
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
@ -27,42 +30,18 @@ const OrganizationProvisionersPage: FC = () => {
const organization = organizations
? getOrganizationByName(organizations, organizationName)
: undefined;
const permissionsQuery = useQuery(
organizationsPermissions(organizations?.map((o) => o.id)),
);
const provisionersQuery = useQuery(provisionerDaemonGroups(organizationName));
if (!organization) {
return <EmptyState message="Organization not found" />;
}
if (permissionsQuery.isLoading || provisionersQuery.isLoading) {
return <Loader />;
}
const permissions = permissionsQuery.data;
const provisioners = provisionersQuery.data;
const error = permissionsQuery.error || provisionersQuery.error;
if (error || !permissions || !provisioners) {
return <ErrorAlert error={error} />;
}
// The user may not be able to edit this org but they can still see it because
// they can edit members, etc. In this case they will be shown a read-only
// summary page instead of the settings form.
// Similarly, if the feature is not entitled then the user will not be able to
// edit the organization.
if (!permissions[organization.id]?.viewProvisioners) {
// This probably doesn't work with the layout................fix this pls
// Kayla, hey, yes you, you gotta fix this.
// Don't scroll past this. It's important. Fix it!!!
return <NotFoundPage />;
}
return (
<OrganizationProvisionersPageView
showPaywall={!entitlements.features.multiple_organizations.enabled}
error={provisionersQuery.error}
buildInfo={buildInfoQuery.data}
provisioners={provisioners}
provisioners={provisionersQuery.data}
/>
);
};

View File

@ -9,6 +9,7 @@ import {
MockProvisionerPskKey,
MockProvisionerWithTags,
MockUserProvisioner,
mockApiError,
} from "testHelpers/entities";
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView";
@ -112,3 +113,18 @@ export const Empty: Story = {
provisioners: [],
},
};
export const WithError: Story = {
args: {
error: mockApiError({
message: "Fern is mad",
detail: "Frieren slept in and didn't get groceries",
}),
},
};
export const Paywall: Story = {
args: {
showPaywall: true,
},
};

View File

@ -5,7 +5,10 @@ import type {
ProvisionerKey,
ProvisionerKeyDaemons,
} from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { EmptyState } from "components/EmptyState/EmptyState";
import { Loader } from "components/Loader/Loader";
import { Paywall } from "components/Paywall/Paywall";
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
import { Stack } from "components/Stack/Stack";
import { ProvisionerGroup } from "modules/provisioners/ProvisionerGroup";
@ -13,16 +16,62 @@ import type { FC } from "react";
import { docs } from "utils/docs";
interface OrganizationProvisionersPageViewProps {
/** Determines if the paywall will be shown or not */
showPaywall?: boolean;
/** An error to display instead of the page content */
error?: unknown;
/** Info about the version of coderd */
buildInfo?: BuildInfoResponse;
/** Groups of provisioners, along with their key information */
provisioners: readonly ProvisionerKeyDaemons[];
provisioners?: readonly ProvisionerKeyDaemons[];
}
export const OrganizationProvisionersPageView: FC<
OrganizationProvisionersPageViewProps
> = ({ buildInfo, provisioners }) => {
> = ({ showPaywall, error, buildInfo, provisioners }) => {
return (
<div>
<Stack
alignItems="baseline"
direction="row"
justifyContent="space-between"
>
<SettingsHeader title="Provisioners" />
{!showPaywall && (
<Button
endIcon={<OpenInNewIcon />}
target="_blank"
href={docs("/admin/provisioners")}
>
Create a provisioner
</Button>
)}
</Stack>
{showPaywall ? (
<Paywall
message="Provisioners"
description="Provisioners run your Terraform to create templates and workspaces. You need a Premium license to use this feature for multiple organizations."
documentationLink={docs("/")}
/>
) : error ? (
<ErrorAlert error={error} />
) : !buildInfo || !provisioners ? (
<Loader />
) : (
<ViewContent buildInfo={buildInfo} provisioners={provisioners} />
)}
</div>
);
};
type ViewContentProps = Required<
Pick<OrganizationProvisionersPageViewProps, "buildInfo" | "provisioners">
>;
const ViewContent: FC<ViewContentProps> = ({ buildInfo, provisioners }) => {
const isEmpty = provisioners.every((group) => group.daemons.length === 0);
const provisionerGroupsCount = provisioners.length;
@ -32,21 +81,7 @@ export const OrganizationProvisionersPageView: FC<
);
return (
<div>
<Stack
alignItems="baseline"
direction="row"
justifyContent="space-between"
>
<SettingsHeader title="Provisioners" />
<Button
endIcon={<OpenInNewIcon />}
target="_blank"
href={docs("/admin/provisioners")}
>
Create a provisioner
</Button>
</Stack>
<>
{isEmpty ? (
<EmptyState
message="No provisioners"
@ -98,7 +133,7 @@ export const OrganizationProvisionersPageView: FC<
);
})}
</Stack>
</div>
</>
);
};