mirror of https://github.com/buster-so/buster.git
adjust new select
This commit is contained in:
parent
f39a309a38
commit
d83193f0c4
|
@ -6,7 +6,6 @@ export * from './dataset_groups';
|
||||||
export * from './datasets';
|
export * from './datasets';
|
||||||
export * from './datasources';
|
export * from './datasources';
|
||||||
export * from './metric';
|
export * from './metric';
|
||||||
export * from './organizations';
|
|
||||||
export * from './permission';
|
export * from './permission';
|
||||||
export * from './permission_groups';
|
export * from './permission_groups';
|
||||||
export * from './search';
|
export * from './search';
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './interfaces';
|
|
|
@ -1,9 +0,0 @@
|
||||||
import type { OrganizationRole } from '@buster/server-shared/organization';
|
|
||||||
|
|
||||||
export const BusterOrganizationRoleLabels: Record<OrganizationRole, string> = {
|
|
||||||
workspace_admin: 'Workspace Admin',
|
|
||||||
data_admin: 'Data Admin',
|
|
||||||
querier: 'Querier',
|
|
||||||
restricted_querier: 'Restricted Querier',
|
|
||||||
viewer: 'Viewer',
|
|
||||||
};
|
|
|
@ -32,7 +32,7 @@ export const ListUsersComponent: React.FC<{
|
||||||
dataIndex: 'role',
|
dataIndex: 'role',
|
||||||
width: 165,
|
width: 165,
|
||||||
render: (role: OrganizationUser['role']) => {
|
render: (role: OrganizationUser['role']) => {
|
||||||
return <Text variant="secondary">{OrganizationUserRoleText[role]}</Text>;
|
return <Text variant="secondary">{OrganizationUserRoleText[role].title}</Text>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BusterOrganizationRoleLabels } from '@/api/asset_interfaces';
|
|
||||||
import { useUpdateUser } from '@/api/buster_rest/users';
|
import { useUpdateUser } from '@/api/buster_rest/users';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
@ -14,6 +13,8 @@ import { Text } from '@/components/ui/typography';
|
||||||
import { useMemoizedFn } from '@/hooks';
|
import { useMemoizedFn } from '@/hooks';
|
||||||
import { User } from '@buster/server-shared/user';
|
import { User } from '@buster/server-shared/user';
|
||||||
import type { OrganizationUser } from '@buster/server-shared/organization';
|
import type { OrganizationUser } from '@buster/server-shared/organization';
|
||||||
|
import { OrganizationUserRoleText } from '@/lib/organization/translations';
|
||||||
|
import { AccessRoleSelect } from '@/components/features/security/AccessRoleSelect';
|
||||||
|
|
||||||
export const UserDefaultAccess: React.FC<{
|
export const UserDefaultAccess: React.FC<{
|
||||||
user: OrganizationUser;
|
user: OrganizationUser;
|
||||||
|
@ -41,20 +42,6 @@ export const UserDefaultAccess: React.FC<{
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const accessOptions: SelectItem<OrganizationUser['role']>[] = [
|
|
||||||
{ label: BusterOrganizationRoleLabels.data_admin, value: 'data_admin' },
|
|
||||||
{
|
|
||||||
label: BusterOrganizationRoleLabels.workspace_admin,
|
|
||||||
value: 'workspace_admin'
|
|
||||||
},
|
|
||||||
{ label: BusterOrganizationRoleLabels.querier, value: 'querier' },
|
|
||||||
{
|
|
||||||
label: BusterOrganizationRoleLabels.restricted_querier,
|
|
||||||
value: 'restricted_querier'
|
|
||||||
},
|
|
||||||
{ label: BusterOrganizationRoleLabels.viewer, value: 'viewer' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const DefaultAccessCard = React.memo(
|
const DefaultAccessCard = React.memo(
|
||||||
({
|
({
|
||||||
role,
|
role,
|
||||||
|
@ -91,12 +78,7 @@ const DefaultAccessCard = React.memo(
|
||||||
: 'Only admins can change access'
|
: 'Only admins can change access'
|
||||||
: undefined
|
: undefined
|
||||||
}>
|
}>
|
||||||
<Select
|
<AccessRoleSelect role={role} onChange={onChange} />
|
||||||
items={accessOptions}
|
|
||||||
value={role}
|
|
||||||
onChange={onChange}
|
|
||||||
disabled={isDisabled}
|
|
||||||
/>
|
|
||||||
</AppTooltip>
|
</AppTooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { useGetDatasets } from '@/api/buster_rest/datasets';
|
import { useGetDatasets } from '@/api/buster_rest/datasets';
|
||||||
import { Select, type SelectItem } from '@/components/ui/select/SelectOld';
|
import { Select, type SelectItem } from '@/components/ui/select';
|
||||||
import { SelectMultiple } from '@/components/ui/select/SelectMultiple';
|
import { SelectMultiple } from '@/components/ui/select/SelectMultiple';
|
||||||
import { useMemoizedFn } from '@/hooks';
|
import { useMemoizedFn } from '@/hooks';
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
import { AccessRoleSelect } from './AccessRoleSelect';
|
||||||
|
import type { OrganizationRole } from '@buster/server-shared/organization';
|
||||||
|
|
||||||
|
const meta: Meta<typeof AccessRoleSelect> = {
|
||||||
|
title: 'Features/Security/AccessRoleSelect',
|
||||||
|
component: AccessRoleSelect,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'A select component for choosing organization access roles with titles and descriptions.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
role: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['viewer', 'restricted_querier', 'querier', 'data_admin', 'workspace_admin'],
|
||||||
|
description: 'The currently selected organization role'
|
||||||
|
},
|
||||||
|
onChange: {
|
||||||
|
action: 'role-changed',
|
||||||
|
description: 'Callback function called when role selection changes'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tags: ['autodocs']
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof AccessRoleSelect>;
|
||||||
|
|
||||||
|
// Default story
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
role: 'viewer',
|
||||||
|
onChange: action('role-changed')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interactive story where users can change roles
|
||||||
|
export const Interactive: Story = {
|
||||||
|
args: {
|
||||||
|
role: 'querier',
|
||||||
|
onChange: action('role-changed')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stories for each specific role
|
||||||
|
export const Viewer: Story = {
|
||||||
|
args: {
|
||||||
|
role: 'viewer',
|
||||||
|
onChange: action('role-changed')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RestrictedQuerier: Story = {
|
||||||
|
args: {
|
||||||
|
role: 'restricted_querier',
|
||||||
|
onChange: action('role-changed')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Querier: Story = {
|
||||||
|
args: {
|
||||||
|
role: 'querier',
|
||||||
|
onChange: action('role-changed')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DataAdmin: Story = {
|
||||||
|
args: {
|
||||||
|
role: 'data_admin',
|
||||||
|
onChange: action('role-changed')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkspaceAdmin: Story = {
|
||||||
|
args: {
|
||||||
|
role: 'workspace_admin',
|
||||||
|
onChange: action('role-changed')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Story without initial role (uses default)
|
||||||
|
export const NoInitialRole: Story = {
|
||||||
|
args: {
|
||||||
|
onChange: action('role-changed')
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Select, type SelectItem } from '@/components/ui/select';
|
||||||
|
import { OrganizationRoleEnum, type OrganizationRole } from '@buster/server-shared/organization';
|
||||||
|
import { OrganizationUserRoleText } from '@/lib/organization';
|
||||||
|
|
||||||
|
const items: SelectItem<OrganizationRole>[] = Object.values(OrganizationRoleEnum).map((role) => ({
|
||||||
|
label: OrganizationUserRoleText[role as OrganizationRole].title,
|
||||||
|
secondaryLabel: OrganizationUserRoleText[role as OrganizationRole].description,
|
||||||
|
value: role as OrganizationRole
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface AccessRoleSelectProps {
|
||||||
|
role?: OrganizationRole;
|
||||||
|
onChange: (role: OrganizationRole) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AccessRoleSelect = ({ role = 'viewer', onChange }: AccessRoleSelectProps) => {
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
items={items}
|
||||||
|
className="w-36 max-w-72"
|
||||||
|
value={role}
|
||||||
|
onChange={(v) => onChange(v as OrganizationRole)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -12,11 +12,12 @@ import type {
|
||||||
GetWorkspaceSettingsResponse,
|
GetWorkspaceSettingsResponse,
|
||||||
UpdateWorkspaceSettingsRequest
|
UpdateWorkspaceSettingsRequest
|
||||||
} from '@buster/server-shared/security';
|
} from '@buster/server-shared/security';
|
||||||
import { Select, type SelectItem } from '@/components/ui/select';
|
import { type SelectItem } from '@/components/ui/select';
|
||||||
import { type OrganizationRole, OrganizationRoleEnum } from '@buster/server-shared/organization';
|
import { type OrganizationRole } from '@buster/server-shared/organization';
|
||||||
import { OrganizationUserRoleText } from '@/lib/organization/translations';
|
|
||||||
import { useGetDatasets } from '@/api/buster_rest/datasets';
|
import { useGetDatasets } from '@/api/buster_rest/datasets';
|
||||||
import { SelectMultiple } from '@/components/ui/select/SelectMultiple';
|
import { SelectMultiple } from '@/components/ui/select/SelectMultiple';
|
||||||
|
import { AccessRoleSelect } from './AccessRoleSelect';
|
||||||
|
import { useMemoizedFn } from '@/hooks';
|
||||||
|
|
||||||
export const WorkspaceRestrictions = React.memo(() => {
|
export const WorkspaceRestrictions = React.memo(() => {
|
||||||
const { data: workspaceSettings } = useGetWorkspaceSettings();
|
const { data: workspaceSettings } = useGetWorkspaceSettings();
|
||||||
|
@ -31,7 +32,7 @@ export const WorkspaceRestrictions = React.memo(() => {
|
||||||
/>,
|
/>,
|
||||||
<DefaultRole
|
<DefaultRole
|
||||||
key="default-role"
|
key="default-role"
|
||||||
default_role={workspaceSettings?.default_role ?? 'viewer' as OrganizationRole}
|
default_role={workspaceSettings?.default_role ?? ('viewer' as OrganizationRole)}
|
||||||
updateWorkspaceSettings={updateWorkspaceSettings}
|
updateWorkspaceSettings={updateWorkspaceSettings}
|
||||||
/>,
|
/>,
|
||||||
<DefaultDatasets
|
<DefaultDatasets
|
||||||
|
@ -84,13 +85,6 @@ const DefaultRole = ({
|
||||||
}: Pick<GetWorkspaceSettingsResponse, 'default_role'> & {
|
}: Pick<GetWorkspaceSettingsResponse, 'default_role'> & {
|
||||||
updateWorkspaceSettings: (request: UpdateWorkspaceSettingsRequest) => Promise<unknown>;
|
updateWorkspaceSettings: (request: UpdateWorkspaceSettingsRequest) => Promise<unknown>;
|
||||||
}) => {
|
}) => {
|
||||||
const items: SelectItem<OrganizationRole>[] = useMemo(() => {
|
|
||||||
return Object.values(OrganizationRoleEnum).map((role) => ({
|
|
||||||
label: OrganizationUserRoleText[role as OrganizationRole],
|
|
||||||
value: role as OrganizationRole
|
|
||||||
}));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex min-w-0 flex-1 flex-col space-y-0.5">
|
<div className="flex min-w-0 flex-1 flex-col space-y-0.5">
|
||||||
|
@ -99,13 +93,11 @@ const DefaultRole = ({
|
||||||
{`Select which default role is assigned to new users`}
|
{`Select which default role is assigned to new users`}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<Select
|
<AccessRoleSelect
|
||||||
items={items}
|
role={default_role}
|
||||||
className="w-36 max-w-72"
|
onChange={useMemoizedFn((v) => {
|
||||||
value={default_role}
|
|
||||||
onChange={(v) => {
|
|
||||||
updateWorkspaceSettings({ default_role: v });
|
updateWorkspaceSettings({ default_role: v });
|
||||||
}}
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -27,10 +27,10 @@ export interface SelectItem<T = string> {
|
||||||
|
|
||||||
type SearchFunction<T> = (item: SelectItem<T>, searchTerm: string) => boolean;
|
type SearchFunction<T> = (item: SelectItem<T>, searchTerm: string) => boolean;
|
||||||
|
|
||||||
export interface SelectProps<T> {
|
// Base interface with common properties
|
||||||
|
interface BaseSelectProps<T> {
|
||||||
items: SelectItem<T>[] | SelectItemGroup<T>[];
|
items: SelectItem<T>[] | SelectItemGroup<T>[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onChange: (value: T | null) => void;
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value?: string | undefined;
|
value?: string | undefined;
|
||||||
onOpenChange?: (open: boolean) => void;
|
onOpenChange?: (open: boolean) => void;
|
||||||
|
@ -41,10 +41,24 @@ export interface SelectProps<T> {
|
||||||
dataTestId?: string;
|
dataTestId?: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
search?: boolean | SearchFunction<T>;
|
search?: boolean | SearchFunction<T>;
|
||||||
clearable?: boolean;
|
|
||||||
emptyMessage?: string;
|
emptyMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clearable version - onChange can return null
|
||||||
|
interface ClearableSelectProps<T> extends BaseSelectProps<T> {
|
||||||
|
clearable: true;
|
||||||
|
onChange: (value: T | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-clearable version - onChange cannot return null
|
||||||
|
interface NonClearableSelectProps<T> extends BaseSelectProps<T> {
|
||||||
|
clearable?: false;
|
||||||
|
onChange: (value: T) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union type for type-safe props
|
||||||
|
export type SelectProps<T> = ClearableSelectProps<T> | NonClearableSelectProps<T>;
|
||||||
|
|
||||||
function isGroupedItems<T>(
|
function isGroupedItems<T>(
|
||||||
items: SelectItem<T>[] | SelectItemGroup<T>[]
|
items: SelectItem<T>[] | SelectItemGroup<T>[]
|
||||||
): items is SelectItemGroup<T>[] {
|
): items is SelectItemGroup<T>[] {
|
||||||
|
@ -83,17 +97,19 @@ const SelectItemComponent = React.memo(
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
disabled={item.disabled}
|
disabled={item.disabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-7 items-center gap-2 px-2',
|
'flex min-h-7 items-center gap-2 px-2',
|
||||||
item.disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',
|
item.disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',
|
||||||
isSelected && 'bg-item-select'
|
isSelected && 'bg-item-select'
|
||||||
)}>
|
)}>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
<span className="flex-1">
|
<span className="flex-1">
|
||||||
{showIndex && `${index + 1}. `}
|
{showIndex && `${index + 1}. `}
|
||||||
{item.label}
|
<div className="flex flex-col space-y-0">
|
||||||
{item.secondaryLabel && (
|
<span className="text-foreground">{item.label}</span>
|
||||||
<span className="text-text-secondary ml-2 text-sm">{item.secondaryLabel}</span>
|
{item.secondaryLabel && (
|
||||||
)}
|
<span className="text-text-secondary text-sm">{item.secondaryLabel}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<div className="text-icon-color flex h-4 w-4 items-center">
|
<div className="text-icon-color flex h-4 w-4 items-center">
|
||||||
|
@ -191,11 +207,14 @@ function SelectComponent<T = string>({
|
||||||
(e: React.MouseEvent) => {
|
(e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onChange(null);
|
// Type assertion is safe here because handleClear is only called when clearable is true
|
||||||
setSearchValue('');
|
if (clearable) {
|
||||||
handleOpenChange(false);
|
(onChange as (value: T | null) => void)(null);
|
||||||
|
setSearchValue('');
|
||||||
|
handleOpenChange(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[onChange, handleOpenChange]
|
[onChange, handleOpenChange, clearable]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleInputChange = React.useCallback(
|
const handleInputChange = React.useCallback(
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { faker } from '@faker-js/faker';
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { fn } from '@storybook/test';
|
import { fn } from '@storybook/test';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import type { SelectItem } from './SelectOld';
|
import type { SelectItem } from './Select';
|
||||||
import { SelectMultiple } from './SelectMultiple';
|
import { SelectMultiple } from './SelectMultiple';
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { useMemoizedFn } from '@/hooks';
|
||||||
import { cn } from '@/lib/classMerge';
|
import { cn } from '@/lib/classMerge';
|
||||||
import { Dropdown, type DropdownItem, type DropdownProps } from '../dropdown/Dropdown';
|
import { Dropdown, type DropdownItem, type DropdownProps } from '../dropdown/Dropdown';
|
||||||
import { InputTag } from '../inputs/InputTag';
|
import { InputTag } from '../inputs/InputTag';
|
||||||
import type { SelectItem } from './SelectOld';
|
import type { SelectItem } from './Select';
|
||||||
import { selectVariants } from './SelectBase';
|
import { selectVariants } from './SelectBase';
|
||||||
import { CircleSpinnerLoader } from '../loaders';
|
import { CircleSpinnerLoader } from '../loaders';
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ export interface SelectProps<T> {
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
showIndex?: boolean;
|
showIndex?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
contentClassName?: string;
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
dataTestId?: string;
|
dataTestId?: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
@ -50,6 +51,7 @@ export const Select = <T extends string>({
|
||||||
value,
|
value,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
open,
|
open,
|
||||||
|
contentClassName,
|
||||||
loading = false,
|
loading = false,
|
||||||
className = '',
|
className = '',
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
@ -69,7 +71,7 @@ export const Select = <T extends string>({
|
||||||
<SelectTrigger className={className} data-testid={dataTestId} loading={loading}>
|
<SelectTrigger className={className} data-testid={dataTestId} loading={loading}>
|
||||||
<SelectValue placeholder={placeholder} defaultValue={value || defaultValue} />
|
<SelectValue placeholder={placeholder} defaultValue={value || defaultValue} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent className={contentClassName}>
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<SelectItemSelector
|
<SelectItemSelector
|
||||||
key={index.toString()}
|
key={index.toString()}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
export * from './SelectOld';
|
export * from './Select';
|
||||||
|
|
|
@ -5,11 +5,11 @@ import { ArrowUpRight, CircleCheck, AlertWarning } from '@/components/ui/icons';
|
||||||
import { Paragraph, Text } from '@/components/ui/typography';
|
import { Paragraph, Text } from '@/components/ui/typography';
|
||||||
import { cn } from '@/lib/classMerge';
|
import { cn } from '@/lib/classMerge';
|
||||||
import type { useNewChatWarning } from './useNewChatWarning';
|
import type { useNewChatWarning } from './useNewChatWarning';
|
||||||
import { BusterOrganizationRoleLabels } from '@/api/asset_interfaces/organizations';
|
|
||||||
import type { OrganizationRole } from '@buster/server-shared/organization';
|
import type { OrganizationRole } from '@buster/server-shared/organization';
|
||||||
|
import { OrganizationUserRoleText } from '@/lib/organization/translations';
|
||||||
|
|
||||||
const translateRole = (role: OrganizationRole) => {
|
const translateRole = (role: OrganizationRole): string => {
|
||||||
return BusterOrganizationRoleLabels[role];
|
return OrganizationUserRoleText[role].title;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NewChatWarning = React.memo(
|
export const NewChatWarning = React.memo(
|
||||||
|
|
|
@ -1,9 +1,30 @@
|
||||||
import type { OrganizationRole } from '@buster/server-shared/organization';
|
import type { OrganizationRole } from '@buster/server-shared/organization';
|
||||||
|
|
||||||
export const OrganizationUserRoleText: Record<OrganizationRole, string> = {
|
export const OrganizationUserRoleText: Record<
|
||||||
data_admin: 'Data Admin',
|
OrganizationRole,
|
||||||
workspace_admin: 'Workspace Admin',
|
{
|
||||||
querier: 'Querier',
|
title: string;
|
||||||
restricted_querier: 'Restricted Querier',
|
description: string;
|
||||||
viewer: 'Viewer',
|
}
|
||||||
|
> = {
|
||||||
|
viewer: {
|
||||||
|
title: 'Viewer',
|
||||||
|
description: 'Can only view metrics that have been shared with them.'
|
||||||
|
},
|
||||||
|
restricted_querier: {
|
||||||
|
title: 'Restricted Querier',
|
||||||
|
description: 'Can only query datasets that have been provisioned to them.'
|
||||||
|
},
|
||||||
|
querier: {
|
||||||
|
title: 'Querier',
|
||||||
|
description: 'Can query all datasets associated with the workspace.'
|
||||||
|
},
|
||||||
|
data_admin: {
|
||||||
|
title: 'Data Admin',
|
||||||
|
description: 'Full access, except for billing.'
|
||||||
|
},
|
||||||
|
workspace_admin: {
|
||||||
|
title: 'Workspace Admin',
|
||||||
|
description: 'Full access, including billing.'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue