Skip to content

Commit 4dcf5ef

Browse files
authored
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 * :|
1 parent aef400c commit 4dcf5ef

File tree

4 files changed

+84
-56
lines changed

4 files changed

+84
-56
lines changed

site/src/modules/provisioners/ProvisionerGroup.tsx

+7-9
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { ProvisionerTag } from "./ProvisionerTag";
3030
type ProvisionerGroupType = "builtin" | "psk" | "key";
3131

3232
interface ProvisionerGroupProps {
33-
readonly buildInfo?: BuildInfoResponse;
33+
readonly buildInfo: BuildInfoResponse;
3434
readonly keyName: string;
3535
readonly keyTags: Record<string, string>;
3636
readonly type: ProvisionerGroupType;
@@ -80,7 +80,7 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
8080
let warnings = 0;
8181
let provisionersWithWarnings = 0;
8282
const provisionersWithWarningInfo = provisioners.map((it) => {
83-
const outOfDate = Boolean(buildInfo) && it.version !== buildInfo?.version;
83+
const outOfDate = it.version !== buildInfo.version;
8484
const warningCount = outOfDate ? 1 : 0;
8585
warnings += warningCount;
8686
if (warnings > 0) {
@@ -292,7 +292,7 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
292292
};
293293

294294
interface ProvisionerVersionPopoverProps {
295-
buildInfo?: BuildInfoResponse;
295+
buildInfo: BuildInfoResponse;
296296
provisioner: ProvisionerDaemon;
297297
}
298298

@@ -304,11 +304,9 @@ const ProvisionerVersionPopover: FC<ProvisionerVersionPopoverProps> = ({
304304
<Popover mode="hover">
305305
<PopoverTrigger>
306306
<span>
307-
{buildInfo
308-
? provisioner.version === buildInfo.version
309-
? "Up to date"
310-
: "Out of date"
311-
: provisioner.version}
307+
{provisioner.version === buildInfo.version
308+
? "Up to date"
309+
: "Out of date"}
312310
</span>
313311
</PopoverTrigger>
314312
<PopoverContent
@@ -324,7 +322,7 @@ const ProvisionerVersionPopover: FC<ProvisionerVersionPopoverProps> = ({
324322
<p css={styles.text}>{provisioner.version}</p>
325323
<h4 css={styles.versionPopoverTitle}>Protocol version</h4>
326324
<p css={styles.text}>{provisioner.api_version}</p>
327-
{provisioner.api_version !== buildInfo?.provisioner_api_version && (
325+
{provisioner.api_version !== buildInfo.provisioner_api_version && (
328326
<p css={[styles.text, { fontSize: 13 }]}>
329327
This provisioner is out of date. You may experience issues when
330328
using a provisioner version that doesn’t match your Coder

site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx

+8-29
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ import {
33
organizationsPermissions,
44
provisionerDaemonGroups,
55
} from "api/queries/organizations";
6-
import type { Organization, ProvisionerDaemon } from "api/typesGenerated";
6+
import type { Organization } from "api/typesGenerated";
77
import { ErrorAlert } from "components/Alert/ErrorAlert";
88
import { EmptyState } from "components/EmptyState/EmptyState";
99
import { Loader } from "components/Loader/Loader";
10+
import { Paywall } from "components/Paywall/Paywall";
1011
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
11-
import NotFoundPage from "pages/404Page/404Page";
12+
import { useDashboard } from "modules/dashboard/useDashboard";
1213
import type { FC } from "react";
1314
import { useQuery } from "react-query";
1415
import { useParams } from "react-router-dom";
16+
import { docs } from "utils/docs";
1517
import { useOrganizationSettings } from "./ManagementSettingsLayout";
1618
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView";
1719

@@ -20,49 +22,26 @@ const OrganizationProvisionersPage: FC = () => {
2022
organization: string;
2123
};
2224
const { organizations } = useOrganizationSettings();
25+
const { entitlements } = useDashboard();
2326

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

2730
const organization = organizations
2831
? getOrganizationByName(organizations, organizationName)
2932
: undefined;
30-
const permissionsQuery = useQuery(
31-
organizationsPermissions(organizations?.map((o) => o.id)),
32-
);
3333
const provisionersQuery = useQuery(provisionerDaemonGroups(organizationName));
3434

3535
if (!organization) {
3636
return <EmptyState message="Organization not found" />;
3737
}
3838

39-
if (permissionsQuery.isLoading || provisionersQuery.isLoading) {
40-
return <Loader />;
41-
}
42-
43-
const permissions = permissionsQuery.data;
44-
const provisioners = provisionersQuery.data;
45-
const error = permissionsQuery.error || provisionersQuery.error;
46-
if (error || !permissions || !provisioners) {
47-
return <ErrorAlert error={error} />;
48-
}
49-
50-
// The user may not be able to edit this org but they can still see it because
51-
// they can edit members, etc. In this case they will be shown a read-only
52-
// summary page instead of the settings form.
53-
// Similarly, if the feature is not entitled then the user will not be able to
54-
// edit the organization.
55-
if (!permissions[organization.id]?.viewProvisioners) {
56-
// This probably doesn't work with the layout................fix this pls
57-
// Kayla, hey, yes you, you gotta fix this.
58-
// Don't scroll past this. It's important. Fix it!!!
59-
return <NotFoundPage />;
60-
}
61-
6239
return (
6340
<OrganizationProvisionersPageView
41+
showPaywall={!entitlements.features.multiple_organizations.enabled}
42+
error={provisionersQuery.error}
6443
buildInfo={buildInfoQuery.data}
65-
provisioners={provisioners}
44+
provisioners={provisionersQuery.data}
6645
/>
6746
);
6847
};

site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.stories.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
MockProvisionerPskKey,
1010
MockProvisionerWithTags,
1111
MockUserProvisioner,
12+
mockApiError,
1213
} from "testHelpers/entities";
1314
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView";
1415

@@ -112,3 +113,18 @@ export const Empty: Story = {
112113
provisioners: [],
113114
},
114115
};
116+
117+
export const WithError: Story = {
118+
args: {
119+
error: mockApiError({
120+
message: "Fern is mad",
121+
detail: "Frieren slept in and didn't get groceries",
122+
}),
123+
},
124+
};
125+
126+
export const Paywall: Story = {
127+
args: {
128+
showPaywall: true,
129+
},
130+
};

site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx

+53-18
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,33 @@ import type {
55
ProvisionerKey,
66
ProvisionerKeyDaemons,
77
} from "api/typesGenerated";
8+
import { ErrorAlert } from "components/Alert/ErrorAlert";
89
import { EmptyState } from "components/EmptyState/EmptyState";
10+
import { Loader } from "components/Loader/Loader";
11+
import { Paywall } from "components/Paywall/Paywall";
912
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
1013
import { Stack } from "components/Stack/Stack";
1114
import { ProvisionerGroup } from "modules/provisioners/ProvisionerGroup";
1215
import type { FC } from "react";
1316
import { docs } from "utils/docs";
1417

1518
interface OrganizationProvisionersPageViewProps {
19+
/** Determines if the paywall will be shown or not */
20+
showPaywall?: boolean;
21+
22+
/** An error to display instead of the page content */
23+
error?: unknown;
24+
1625
/** Info about the version of coderd */
1726
buildInfo?: BuildInfoResponse;
1827

1928
/** Groups of provisioners, along with their key information */
20-
provisioners: readonly ProvisionerKeyDaemons[];
29+
provisioners?: readonly ProvisionerKeyDaemons[];
2130
}
2231

2332
export const OrganizationProvisionersPageView: FC<
2433
OrganizationProvisionersPageViewProps
25-
> = ({ buildInfo, provisioners }) => {
26-
const isEmpty = provisioners.every((group) => group.daemons.length === 0);
27-
28-
const provisionerGroupsCount = provisioners.length;
29-
const provisionersCount = provisioners.reduce(
30-
(a, group) => a + group.daemons.length,
31-
0,
32-
);
33-
34+
> = ({ showPaywall, error, buildInfo, provisioners }) => {
3435
return (
3536
<div>
3637
<Stack
@@ -39,14 +40,48 @@ export const OrganizationProvisionersPageView: FC<
3940
justifyContent="space-between"
4041
>
4142
<SettingsHeader title="Provisioners" />
42-
<Button
43-
endIcon={<OpenInNewIcon />}
44-
target="_blank"
45-
href={docs("/admin/provisioners")}
46-
>
47-
Create a provisioner
48-
</Button>
43+
{!showPaywall && (
44+
<Button
45+
endIcon={<OpenInNewIcon />}
46+
target="_blank"
47+
href={docs("/admin/provisioners")}
48+
>
49+
Create a provisioner
50+
</Button>
51+
)}
4952
</Stack>
53+
{showPaywall ? (
54+
<Paywall
55+
message="Provisioners"
56+
description="Provisioners run your Terraform to create templates and workspaces. You need a Premium license to use this feature for multiple organizations."
57+
documentationLink={docs("/")}
58+
/>
59+
) : error ? (
60+
<ErrorAlert error={error} />
61+
) : !buildInfo || !provisioners ? (
62+
<Loader />
63+
) : (
64+
<ViewContent buildInfo={buildInfo} provisioners={provisioners} />
65+
)}
66+
</div>
67+
);
68+
};
69+
70+
type ViewContentProps = Required<
71+
Pick<OrganizationProvisionersPageViewProps, "buildInfo" | "provisioners">
72+
>;
73+
74+
const ViewContent: FC<ViewContentProps> = ({ buildInfo, provisioners }) => {
75+
const isEmpty = provisioners.every((group) => group.daemons.length === 0);
76+
77+
const provisionerGroupsCount = provisioners.length;
78+
const provisionersCount = provisioners.reduce(
79+
(a, group) => a + group.daemons.length,
80+
0,
81+
);
82+
83+
return (
84+
<>
5085
{isEmpty ? (
5186
<EmptyState
5287
message="No provisioners"
@@ -98,7 +133,7 @@ export const OrganizationProvisionersPageView: FC<
98133
);
99134
})}
100135
</Stack>
101-
</div>
136+
</>
102137
);
103138
};
104139

0 commit comments

Comments
 (0)