Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3b53f5a

Browse files
authored
fix: only show valid organizations in CreateTemplateForm (#14174)
1 parent ff78558 commit 3b53f5a

File tree

4 files changed

+100
-2
lines changed

4 files changed

+100
-2
lines changed

site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx

+30-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import {
99
useState,
1010
} from "react";
1111
import { useQuery } from "react-query";
12+
import { checkAuthorization } from "api/queries/authCheck";
1213
import { organizations } from "api/queries/organizations";
13-
import type { Organization } from "api/typesGenerated";
14+
import type { AuthorizationCheck, Organization } from "api/typesGenerated";
1415
import { Avatar } from "components/Avatar/Avatar";
1516
import { AvatarData } from "components/AvatarData/AvatarData";
1617
import { useDebouncedFunction } from "hooks/debounce";
@@ -22,6 +23,7 @@ export type OrganizationAutocompleteProps = {
2223
className?: string;
2324
size?: ComponentProps<typeof TextField>["size"];
2425
required?: boolean;
26+
check?: AuthorizationCheck;
2527
};
2628

2729
export const OrganizationAutocomplete: FC<OrganizationAutocompleteProps> = ({
@@ -31,6 +33,7 @@ export const OrganizationAutocomplete: FC<OrganizationAutocompleteProps> = ({
3133
className,
3234
size = "small",
3335
required,
36+
check,
3437
}) => {
3538
const [autoComplete, setAutoComplete] = useState<{
3639
value: string;
@@ -41,6 +44,22 @@ export const OrganizationAutocomplete: FC<OrganizationAutocompleteProps> = ({
4144
});
4245
const organizationsQuery = useQuery(organizations());
4346

47+
const permissionsQuery = useQuery(
48+
check && organizationsQuery.data
49+
? checkAuthorization({
50+
checks: Object.fromEntries(
51+
organizationsQuery.data.map((org) => [
52+
org.id,
53+
{
54+
...check,
55+
object: { ...check.object, organization_id: org.id },
56+
},
57+
]),
58+
),
59+
})
60+
: { enabled: false },
61+
);
62+
4463
const { debounced: debouncedInputOnChange } = useDebouncedFunction(
4564
(event: ChangeEvent<HTMLInputElement>) => {
4665
setAutoComplete((state) => ({
@@ -51,11 +70,20 @@ export const OrganizationAutocomplete: FC<OrganizationAutocompleteProps> = ({
5170
750,
5271
);
5372

73+
// If an authorization check was provided, filter the organizations based on
74+
// the results of that check.
75+
let options = organizationsQuery.data ?? [];
76+
if (check) {
77+
options = permissionsQuery.data
78+
? options.filter((org) => permissionsQuery.data[org.id])
79+
: [];
80+
}
81+
5482
return (
5583
<Autocomplete
5684
noOptionsText="No organizations found"
5785
className={className}
58-
options={organizationsQuery.data ?? []}
86+
options={options}
5987
loading={organizationsQuery.isLoading}
6088
value={value}
6189
data-testid="organization-autocomplete"

site/src/contexts/auth/permissions.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const permissionsToCheck = {
4141
[checks.createTemplates]: {
4242
object: {
4343
resource_type: "template",
44+
any_org: true,
4445
},
4546
action: "update",
4647
},

site/src/pages/CreateTemplatePage/CreateTemplateForm.stories.tsx

+65
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,40 @@ export const StarterTemplateWithOrgPicker: Story = {
6161
},
6262
};
6363

64+
const canCreateTemplate = (organizationId: string) => {
65+
return {
66+
[organizationId]: {
67+
object: {
68+
resource_type: "template",
69+
organization_id: organizationId,
70+
},
71+
action: "create",
72+
},
73+
};
74+
};
75+
6476
export const StarterTemplateWithProvisionerWarning: Story = {
6577
parameters: {
6678
queries: [
6779
{
6880
key: organizationsKey,
6981
data: [MockDefaultOrganization, MockOrganization2],
7082
},
83+
{
84+
key: [
85+
"authorization",
86+
{
87+
checks: {
88+
...canCreateTemplate(MockDefaultOrganization.id),
89+
...canCreateTemplate(MockOrganization2.id),
90+
},
91+
},
92+
],
93+
data: {
94+
[MockDefaultOrganization.id]: true,
95+
[MockOrganization2.id]: true,
96+
},
97+
},
7198
{
7299
key: getProvisionerDaemonsKey(MockOrganization2.id),
73100
data: [],
@@ -86,6 +113,44 @@ export const StarterTemplateWithProvisionerWarning: Story = {
86113
},
87114
};
88115

116+
export const StarterTemplatePermissionsCheck: Story = {
117+
parameters: {
118+
queries: [
119+
{
120+
key: organizationsKey,
121+
data: [MockDefaultOrganization, MockOrganization2],
122+
},
123+
{
124+
key: [
125+
"authorization",
126+
{
127+
checks: {
128+
...canCreateTemplate(MockDefaultOrganization.id),
129+
...canCreateTemplate(MockOrganization2.id),
130+
},
131+
},
132+
],
133+
data: {
134+
[MockDefaultOrganization.id]: true,
135+
[MockOrganization2.id]: false,
136+
},
137+
},
138+
{
139+
key: getProvisionerDaemonsKey(MockOrganization2.id),
140+
data: [],
141+
},
142+
],
143+
},
144+
args: {
145+
...StarterTemplate.args,
146+
showOrganizationPicker: true,
147+
},
148+
play: async () => {
149+
const organizationPicker = screen.getByPlaceholderText("Organization name");
150+
await userEvent.click(organizationPicker);
151+
},
152+
};
153+
89154
export const DuplicateTemplateWithVariables: Story = {
90155
args: {
91156
copiedTemplate: MockTemplate,

site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,10 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
267267
void form.setFieldValue("organization", newValue?.name || "");
268268
}}
269269
size="medium"
270+
check={{
271+
object: { resource_type: "template" },
272+
action: "create",
273+
}}
270274
/>
271275
</>
272276
)}

0 commit comments

Comments
 (0)