feat: added helper text for operators and improved rendering of selective operators

This commit is contained in:
=
2024-10-08 01:51:10 +05:30
parent 6db8c100ba
commit fc5487396b
6 changed files with 135 additions and 38 deletions

View File

@@ -11,8 +11,8 @@ export enum PermissionConditionOperators {
}
export const PermissionConditionSchema = {
[PermissionConditionOperators.$IN]: z.string().min(1).array(),
[PermissionConditionOperators.$ALL]: z.string().min(1).array(),
[PermissionConditionOperators.$IN]: z.string().trim().min(1).array(),
[PermissionConditionOperators.$ALL]: z.string().trim().min(1).array(),
[PermissionConditionOperators.$REGEX]: z
.string()
.min(1)

View File

@@ -0,0 +1,33 @@
import { useState } from "react";
import picomatch from "picomatch";
import { FormControl } from "../v2/FormControl";
import { Input } from "../v2/Input";
export const GlobPermissionInfo = () => {
const [pattern, setPattern] = useState("");
const [text, setText] = useState("");
return (
<div>
<div className="mt-2">A glob pattern uses wildcards to match file names or paths.</div>
<div>
<FormControl label="Glob pattern" helperText="Examples: /{a,b}, DB_**">
<Input value={pattern} onChange={(e) => setPattern(e.target.value)} />
</FormControl>
</div>
<div>
<FormControl
label="Test string"
helperText="Type value to test"
isError={
pattern && text ? !picomatch.isMatch(text, pattern, { strictSlashes: false }) : false
}
errorText="Invalid"
>
<Input value={text} onChange={(e) => setText(e.target.value)} />
</FormControl>
</div>
</div>
);
};

View File

@@ -1,3 +1,4 @@
export { GlobPermissionInfo } from "./GlobPermissionInfo";
export { OrgPermissionCan } from "./OrgPermissionCan";
export { PermissionDeniedBanner } from "./PermissionDeniedBanner";
export { ProjectPermissionCan } from "./ProjectPermissionCan";

View File

@@ -1,14 +1,26 @@
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
import { faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons";
import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, FormControl, IconButton, Input, Select, SelectItem } from "@app/components/v2";
import {
Button,
FormControl,
IconButton,
Input,
Select,
SelectItem,
Tooltip
} from "@app/components/v2";
import {
PermissionConditionOperators,
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import { TFormSchema } from "../ProjectRoleModifySection.utils";
import {
getConditionOperatorHelperInfo,
renderOperatorSelectItems
} from "./PermissionConditionHelpers";
type Props = {
position?: number;
@@ -19,12 +31,6 @@ type Props = {
| ProjectPermissionSub.SecretImports;
};
const getValueLabel = (type: string) => {
if (type === "environment") return "Environment slug";
if (type === "secretPath") return "Folder path";
return "";
};
export const GeneralPermissionConditions = ({ position = 0, isDisabled, type }: Props) => {
const {
control,
@@ -40,7 +46,11 @@ export const GeneralPermissionConditions = ({ position = 0, isDisabled, type }:
<div className="mt-6 border-t border-t-gray-800 bg-mineshaft-800 pt-2">
<div className="mt-2 flex flex-col space-y-2">
{items.fields.map((el, index) => {
const lhs = watch(`permissions.${type}.${position}.conditions.${index}.lhs`);
const condition = watch(`permissions.secrets.${position}.conditions.${index}`) as {
lhs: string;
rhs: string;
operator: string;
};
return (
<div
key={el.id}
@@ -69,10 +79,10 @@ export const GeneralPermissionConditions = ({ position = 0, isDisabled, type }:
)}
/>
</div>
<div className="w-36">
<div className="flex w-36 items-center space-x-2">
<Controller
control={control}
name={`permissions.${type}.${position}.conditions.${index}.operator`}
name={`permissions.secrets.${position}.conditions.${index}.operator`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
@@ -85,16 +95,22 @@ export const GeneralPermissionConditions = ({ position = 0, isDisabled, type }:
onValueChange={(e) => field.onChange(e)}
className="w-full"
>
<SelectItem value={PermissionConditionOperators.$EQ}>Equal</SelectItem>
<SelectItem value={PermissionConditionOperators.$NEQ}>Not Equal</SelectItem>
<SelectItem value={PermissionConditionOperators.$GLOB}>
Glob Match
</SelectItem>
<SelectItem value={PermissionConditionOperators.$IN}>Contains</SelectItem>
{renderOperatorSelectItems(condition.lhs)}
</Select>
</FormControl>
)}
/>
<div>
<Tooltip
asChild
content={getConditionOperatorHelperInfo(
condition.operator as PermissionConditionOperators
)}
className="max-w-xs"
>
<FontAwesomeIcon icon={faInfoCircle} size="xs" className="text-gray-400" />
</Tooltip>
</div>
</div>
<div className="flex-grow">
<Controller
@@ -106,7 +122,7 @@ export const GeneralPermissionConditions = ({ position = 0, isDisabled, type }:
errorText={error?.message}
className="mb-0 flex-grow"
>
<Input {...field} placeholder={getValueLabel(lhs)} />
<Input {...field} />
</FormControl>
)}
/>

View File

@@ -0,0 +1,32 @@
import { GlobPermissionInfo } from "@app/components/permissions";
import { SelectItem } from "@app/components/v2";
import { PermissionConditionOperators } from "@app/context/ProjectPermissionContext/types";
export const getConditionOperatorHelperInfo = (type: PermissionConditionOperators) => {
switch (type) {
case PermissionConditionOperators.$EQ:
return "Value should equal specified value.";
case PermissionConditionOperators.$NEQ:
return "Value should not equal specified value.";
case PermissionConditionOperators.$IN:
return "List of comma-separated values that match a given value.";
case PermissionConditionOperators.$GLOB:
return <GlobPermissionInfo />;
default:
return "";
}
};
export const renderOperatorSelectItems = (type: string) => {
if (type === "secretTags") {
return <SelectItem value={PermissionConditionOperators.$IN}>Contains</SelectItem>;
}
return (
<>
<SelectItem value={PermissionConditionOperators.$EQ}>Equal</SelectItem>
<SelectItem value={PermissionConditionOperators.$NEQ}>Not Equal</SelectItem>
<SelectItem value={PermissionConditionOperators.$GLOB}>Glob Match</SelectItem>
<SelectItem value={PermissionConditionOperators.$IN}>Contains</SelectItem>
</>
);
};

View File

@@ -1,23 +1,29 @@
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
import { faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons";
import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, FormControl, IconButton, Input, Select, SelectItem } from "@app/components/v2";
import {
Button,
FormControl,
IconButton,
Input,
Select,
SelectItem,
Tooltip
} from "@app/components/v2";
import { PermissionConditionOperators } from "@app/context/ProjectPermissionContext/types";
import { TFormSchema } from "../ProjectRoleModifySection.utils";
import {
getConditionOperatorHelperInfo,
renderOperatorSelectItems
} from "./PermissionConditionHelpers";
type Props = {
position?: number;
isDisabled?: boolean;
};
const getValueLabel = (type: string) => {
if (type === "environment") return "Environment slug";
if (type === "secretPath") return "Folder path";
return "";
};
export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props) => {
const {
control,
@@ -33,7 +39,11 @@ export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props)
<div className="mt-6 border-t border-t-gray-800 bg-mineshaft-800 pt-2">
<div className="mt-2 flex flex-col space-y-2">
{items.fields.map((el, index) => {
const lhs = watch(`permissions.secrets.${position}.conditions.${index}.lhs`);
const condition = watch(`permissions.secrets.${position}.conditions.${index}`) as {
lhs: string;
rhs: string;
operator: string;
};
return (
<div
key={el.id}
@@ -64,7 +74,7 @@ export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props)
)}
/>
</div>
<div className="w-36">
<div className="flex w-36 items-center space-x-2">
<Controller
control={control}
name={`permissions.secrets.${position}.conditions.${index}.operator`}
@@ -80,16 +90,22 @@ export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props)
onValueChange={(e) => field.onChange(e)}
className="w-full"
>
<SelectItem value={PermissionConditionOperators.$EQ}>Equal</SelectItem>
<SelectItem value={PermissionConditionOperators.$NEQ}>Not Equal</SelectItem>
<SelectItem value={PermissionConditionOperators.$GLOB}>
Glob Match
</SelectItem>
<SelectItem value={PermissionConditionOperators.$IN}>Contains</SelectItem>
{renderOperatorSelectItems(condition.lhs)}
</Select>
</FormControl>
)}
/>
<div>
<Tooltip
asChild
content={getConditionOperatorHelperInfo(
condition.operator as PermissionConditionOperators
)}
className="max-w-xs"
>
<FontAwesomeIcon icon={faInfoCircle} size="xs" className="text-gray-400" />
</Tooltip>
</div>
</div>
<div className="flex-grow">
<Controller
@@ -101,7 +117,7 @@ export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props)
errorText={error?.message}
className="mb-0 flex-grow"
>
<Input {...field} placeholder={getValueLabel(lhs)} />
<Input {...field} />
</FormControl>
)}
/>
@@ -126,7 +142,6 @@ export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props)
<span>{errors?.permissions?.secrets?.[position]?.conditions?.message}</span>
</div>
)}
<div>{}</div>
<div>
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}