Skip to content

chore: add paywall to provisioners page #14803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions site/src/modules/provisioners/ProvisionerGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -292,7 +292,7 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
};

interface ProvisionerVersionPopoverProps {
buildInfo?: BuildInfoResponse;
buildInfo: BuildInfoResponse;
provisioner: ProvisionerDaemon;
}

Expand All @@ -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
Expand All @@ -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 doesn’t match your Coder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -20,49 +22,26 @@ const OrganizationProvisionersPage: FC = () => {
organization: string;
};
const { organizations } = useOrganizationSettings();
const { entitlements } = useDashboard();

const { metadata } = useEmbeddedMetadata();
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));

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}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
MockProvisionerPskKey,
MockProvisionerWithTags,
MockUserProvisioner,
mockApiError,
} from "testHelpers/entities";
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView";

Expand Down Expand Up @@ -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,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,33 @@ 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";
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 }) => {
const isEmpty = provisioners.every((group) => group.daemons.length === 0);

const provisionerGroupsCount = provisioners.length;
const provisionersCount = provisioners.reduce(
(a, group) => a + group.daemons.length,
0,
);

> = ({ showPaywall, error, buildInfo, provisioners }) => {
return (
<div>
<Stack
Expand All @@ -39,14 +40,48 @@ export const OrganizationProvisionersPageView: FC<
justifyContent="space-between"
>
<SettingsHeader title="Provisioners" />
<Button
endIcon={<OpenInNewIcon />}
target="_blank"
href={docs("/admin/provisioners")}
>
Create a provisioner
</Button>
{!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;
const provisionersCount = provisioners.reduce(
(a, group) => a + group.daemons.length,
0,
);

return (
<>
{isEmpty ? (
<EmptyState
message="No provisioners"
Expand Down Expand Up @@ -98,7 +133,7 @@ export const OrganizationProvisionersPageView: FC<
);
})}
</Stack>
</div>
</>
);
};

Expand Down
Loading