mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: only show valid organizations in CreateTemplateForm (#14174)
This commit is contained in:
@@ -9,8 +9,9 @@ import {
|
||||
useState,
|
||||
} from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { checkAuthorization } from "api/queries/authCheck";
|
||||
import { organizations } from "api/queries/organizations";
|
||||
import type { Organization } from "api/typesGenerated";
|
||||
import type { AuthorizationCheck, Organization } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { useDebouncedFunction } from "hooks/debounce";
|
||||
@@ -22,6 +23,7 @@ export type OrganizationAutocompleteProps = {
|
||||
className?: string;
|
||||
size?: ComponentProps<typeof TextField>["size"];
|
||||
required?: boolean;
|
||||
check?: AuthorizationCheck;
|
||||
};
|
||||
|
||||
export const OrganizationAutocomplete: FC<OrganizationAutocompleteProps> = ({
|
||||
@@ -31,6 +33,7 @@ export const OrganizationAutocomplete: FC<OrganizationAutocompleteProps> = ({
|
||||
className,
|
||||
size = "small",
|
||||
required,
|
||||
check,
|
||||
}) => {
|
||||
const [autoComplete, setAutoComplete] = useState<{
|
||||
value: string;
|
||||
@@ -41,6 +44,22 @@ export const OrganizationAutocomplete: FC<OrganizationAutocompleteProps> = ({
|
||||
});
|
||||
const organizationsQuery = useQuery(organizations());
|
||||
|
||||
const permissionsQuery = useQuery(
|
||||
check && organizationsQuery.data
|
||||
? checkAuthorization({
|
||||
checks: Object.fromEntries(
|
||||
organizationsQuery.data.map((org) => [
|
||||
org.id,
|
||||
{
|
||||
...check,
|
||||
object: { ...check.object, organization_id: org.id },
|
||||
},
|
||||
]),
|
||||
),
|
||||
})
|
||||
: { enabled: false },
|
||||
);
|
||||
|
||||
const { debounced: debouncedInputOnChange } = useDebouncedFunction(
|
||||
(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setAutoComplete((state) => ({
|
||||
@@ -51,11 +70,20 @@ export const OrganizationAutocomplete: FC<OrganizationAutocompleteProps> = ({
|
||||
750,
|
||||
);
|
||||
|
||||
// If an authorization check was provided, filter the organizations based on
|
||||
// the results of that check.
|
||||
let options = organizationsQuery.data ?? [];
|
||||
if (check) {
|
||||
options = permissionsQuery.data
|
||||
? options.filter((org) => permissionsQuery.data[org.id])
|
||||
: [];
|
||||
}
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
noOptionsText="No organizations found"
|
||||
className={className}
|
||||
options={organizationsQuery.data ?? []}
|
||||
options={options}
|
||||
loading={organizationsQuery.isLoading}
|
||||
value={value}
|
||||
data-testid="organization-autocomplete"
|
||||
|
||||
@@ -41,6 +41,7 @@ export const permissionsToCheck = {
|
||||
[checks.createTemplates]: {
|
||||
object: {
|
||||
resource_type: "template",
|
||||
any_org: true,
|
||||
},
|
||||
action: "update",
|
||||
},
|
||||
|
||||
@@ -61,6 +61,18 @@ export const StarterTemplateWithOrgPicker: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
const canCreateTemplate = (organizationId: string) => {
|
||||
return {
|
||||
[organizationId]: {
|
||||
object: {
|
||||
resource_type: "template",
|
||||
organization_id: organizationId,
|
||||
},
|
||||
action: "create",
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const StarterTemplateWithProvisionerWarning: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
@@ -68,6 +80,21 @@ export const StarterTemplateWithProvisionerWarning: Story = {
|
||||
key: organizationsKey,
|
||||
data: [MockDefaultOrganization, MockOrganization2],
|
||||
},
|
||||
{
|
||||
key: [
|
||||
"authorization",
|
||||
{
|
||||
checks: {
|
||||
...canCreateTemplate(MockDefaultOrganization.id),
|
||||
...canCreateTemplate(MockOrganization2.id),
|
||||
},
|
||||
},
|
||||
],
|
||||
data: {
|
||||
[MockDefaultOrganization.id]: true,
|
||||
[MockOrganization2.id]: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: getProvisionerDaemonsKey(MockOrganization2.id),
|
||||
data: [],
|
||||
@@ -86,6 +113,44 @@ export const StarterTemplateWithProvisionerWarning: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const StarterTemplatePermissionsCheck: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: organizationsKey,
|
||||
data: [MockDefaultOrganization, MockOrganization2],
|
||||
},
|
||||
{
|
||||
key: [
|
||||
"authorization",
|
||||
{
|
||||
checks: {
|
||||
...canCreateTemplate(MockDefaultOrganization.id),
|
||||
...canCreateTemplate(MockOrganization2.id),
|
||||
},
|
||||
},
|
||||
],
|
||||
data: {
|
||||
[MockDefaultOrganization.id]: true,
|
||||
[MockOrganization2.id]: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: getProvisionerDaemonsKey(MockOrganization2.id),
|
||||
data: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
args: {
|
||||
...StarterTemplate.args,
|
||||
showOrganizationPicker: true,
|
||||
},
|
||||
play: async () => {
|
||||
const organizationPicker = screen.getByPlaceholderText("Organization name");
|
||||
await userEvent.click(organizationPicker);
|
||||
},
|
||||
};
|
||||
|
||||
export const DuplicateTemplateWithVariables: Story = {
|
||||
args: {
|
||||
copiedTemplate: MockTemplate,
|
||||
|
||||
@@ -267,6 +267,10 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
|
||||
void form.setFieldValue("organization", newValue?.name || "");
|
||||
}}
|
||||
size="medium"
|
||||
check={{
|
||||
object: { resource_type: "template" },
|
||||
action: "create",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user