mirror of https://github.com/buster-so/buster.git
change share roles
This commit is contained in:
parent
4a8b1e3674
commit
486d93e826
|
@ -160,7 +160,7 @@ export const DEFAULT_IBUSTER_METRIC: Required<IBusterMetric> = {
|
|||
updated_at: '',
|
||||
sent_by_id: '',
|
||||
sent_by_name: '',
|
||||
permission: ShareRole.VIEWER,
|
||||
permission: ShareRole.CAN_VIEW,
|
||||
sent_by_avatar_url: null,
|
||||
draft_session_id: null,
|
||||
dashboards: [],
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
export enum ShareRole {
|
||||
OWNER = 'owner',
|
||||
EDITOR = 'editor',
|
||||
VIEWER = 'viewer'
|
||||
OWNER = 'owner', //owner of the asset
|
||||
FULL_ACCESS = 'fullAccess', //same as owner, can share with others
|
||||
CAN_EDIT = 'canEdit', //can edit, cannot share
|
||||
CAN_FILTER = 'canFilter', //can filter dashboard
|
||||
CAN_VIEW = 'canView' //can view asset
|
||||
}
|
||||
|
||||
export enum ShareAssetType {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Paragraph, Text } from '@/components/ui/typography';
|
|||
import { useMemoizedFn } from '@/hooks';
|
||||
import { Dropdown } from '@/components/ui/dropdown';
|
||||
import { ChevronDown } from '@/components/ui/icons/NucleoIconFilled';
|
||||
import { canEdit } from '@/lib/share';
|
||||
|
||||
type DropdownValue = ShareRole | 'remove' | 'notShared';
|
||||
|
||||
|
@ -21,7 +22,7 @@ export const AccessDropdown: React.FC<{
|
|||
className = '',
|
||||
onChangeShareLevel
|
||||
}) => {
|
||||
const disabled = useMemo(() => shareLevel === ShareRole.OWNER, [shareLevel]);
|
||||
const disabled = useMemo(() => canEdit(shareLevel), [shareLevel]);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const baseItems: DropdownItem<DropdownValue>[] = [...standardItems];
|
||||
|
@ -47,12 +48,28 @@ export const AccessDropdown: React.FC<{
|
|||
const selectedLabel = useMemo(() => {
|
||||
const selectedItem = items.find((item) => item.selected);
|
||||
if (!selectedItem) return 'No shared';
|
||||
if (selectedItem.value === ShareRole.OWNER) return 'Full access';
|
||||
if (selectedItem.value === ShareRole.EDITOR) return 'Can edit';
|
||||
if (selectedItem.value === ShareRole.VIEWER) return 'Can view';
|
||||
if (selectedItem.value === 'remove') return 'Remove';
|
||||
if (selectedItem.value === 'notShared') return 'Not shared';
|
||||
return selectedItem.label;
|
||||
|
||||
const { value } = selectedItem;
|
||||
|
||||
// Using a type-safe switch to handle all ShareRole values
|
||||
switch (value) {
|
||||
case ShareRole.FULL_ACCESS:
|
||||
return 'Full access';
|
||||
case ShareRole.CAN_EDIT:
|
||||
return 'Can edit';
|
||||
case ShareRole.CAN_FILTER:
|
||||
return 'Can filter';
|
||||
case ShareRole.CAN_VIEW:
|
||||
return 'Can view';
|
||||
case ShareRole.OWNER:
|
||||
return 'Full access';
|
||||
case 'remove':
|
||||
return 'Remove';
|
||||
case 'notShared':
|
||||
return 'Not shared';
|
||||
default:
|
||||
return typeof selectedItem.label === 'string' ? selectedItem.label : 'Selected';
|
||||
}
|
||||
}, [items]);
|
||||
|
||||
const onSelectMenuItem = useMemoizedFn((value: string) => {
|
||||
|
@ -86,19 +103,24 @@ export const AccessDropdown: React.FC<{
|
|||
|
||||
const standardItems: DropdownItem<ShareRole>[] = [
|
||||
{
|
||||
value: ShareRole.OWNER,
|
||||
value: ShareRole.FULL_ACCESS,
|
||||
label: 'Full access',
|
||||
secondaryLabel: 'Can edit and share with others.'
|
||||
},
|
||||
{
|
||||
value: ShareRole.EDITOR,
|
||||
value: ShareRole.CAN_EDIT,
|
||||
label: 'Can edit',
|
||||
secondaryLabel: 'Can edit but not share with others.'
|
||||
},
|
||||
{
|
||||
value: ShareRole.VIEWER,
|
||||
value: ShareRole.CAN_FILTER,
|
||||
label: 'Can filter',
|
||||
secondaryLabel: 'Can filter dashboards but not edit.'
|
||||
},
|
||||
{
|
||||
value: ShareRole.CAN_VIEW,
|
||||
label: 'Can view',
|
||||
secondaryLabel: 'Can view but not edit.'
|
||||
secondaryLabel: 'Can view asset but not edit.'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { AppTooltip } from '@/components/ui/tooltip';
|
|||
import { useMemoizedFn } from '@/hooks';
|
||||
import { BusterShare, ShareAssetType } from '@/api/asset_interfaces';
|
||||
import { ShareMenuContent } from './ShareMenuContent';
|
||||
import { isShareMenuVisible } from './helpers';
|
||||
import { canShare } from '@/lib/share';
|
||||
|
||||
export const ShareMenu: React.FC<
|
||||
PropsWithChildren<{
|
||||
|
@ -21,7 +21,7 @@ export const ShareMenu: React.FC<
|
|||
setIsOpen(v);
|
||||
});
|
||||
|
||||
const showShareMenu = shareAssetConfig && isShareMenuVisible(shareAssetConfig);
|
||||
const showShareMenu = canShare(shareAssetConfig?.permission);
|
||||
|
||||
if (!showShareMenu) {
|
||||
return null;
|
||||
|
|
|
@ -20,13 +20,13 @@ const mockShareConfig: BusterShare = {
|
|||
{
|
||||
id: '1',
|
||||
email: 'test_with_a_long_name_like_super_long_name@test.com',
|
||||
role: ShareRole.VIEWER,
|
||||
role: ShareRole.CAN_VIEW,
|
||||
name: 'Test User'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
email: 'test2@test.com',
|
||||
role: ShareRole.VIEWER,
|
||||
role: ShareRole.FULL_ACCESS,
|
||||
name: 'Test User 2 with a long name like super long name'
|
||||
}
|
||||
],
|
||||
|
@ -81,7 +81,7 @@ export const ViewerPermission: Story = {
|
|||
assetType: ShareAssetType.METRIC,
|
||||
shareAssetConfig: {
|
||||
...mockShareConfig,
|
||||
permission: ShareRole.VIEWER
|
||||
permission: ShareRole.CAN_VIEW
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useMemoizedFn } from '@/hooks';
|
|||
import { BusterRoutes, createBusterRoute } from '@/routes';
|
||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||
import { ShareMenuContentEmbedFooter } from './ShareMenuContentEmbed';
|
||||
import { isEffectiveOwner } from '@/lib/share';
|
||||
|
||||
export const ShareMenuContent: React.FC<{
|
||||
shareAssetConfig: BusterShare;
|
||||
|
@ -20,7 +21,7 @@ export const ShareMenuContent: React.FC<{
|
|||
|
||||
const permission = shareAssetConfig?.permission;
|
||||
const publicly_accessible = shareAssetConfig?.publicly_accessible;
|
||||
const isOwner = permission === ShareRole.OWNER;
|
||||
const isOwner = isEffectiveOwner(permission);
|
||||
|
||||
const onCopyLink = useMemoizedFn(() => {
|
||||
let url = '';
|
||||
|
|
|
@ -87,7 +87,7 @@ const ShareMenuContentShare: React.FC<{
|
|||
const [inputValue, setInputValue] = React.useState<string>('');
|
||||
const [isInviting, setIsInviting] = React.useState<boolean>(false);
|
||||
const [defaultPermissionLevel, setDefaultPermissionLevel] = React.useState<ShareRole>(
|
||||
ShareRole.VIEWER
|
||||
ShareRole.CAN_VIEW
|
||||
);
|
||||
const disableSubmit = !inputHasText(inputValue) || !validate(inputValue);
|
||||
const hasUserTeams = userTeams?.length > 0;
|
||||
|
|
|
@ -2,19 +2,9 @@ import {
|
|||
BusterCollection,
|
||||
BusterDashboardResponse,
|
||||
BusterShare,
|
||||
IBusterChatMessage,
|
||||
IBusterMetric,
|
||||
ShareRole
|
||||
IBusterMetric
|
||||
} from '@/api/asset_interfaces';
|
||||
|
||||
export const isShareMenuVisible = (shareAssetConfig: BusterShare | null) => {
|
||||
return (
|
||||
!!shareAssetConfig &&
|
||||
(shareAssetConfig.permission === ShareRole.OWNER ||
|
||||
shareAssetConfig.permission === ShareRole.EDITOR)
|
||||
);
|
||||
};
|
||||
|
||||
export const getShareAssetConfig = (
|
||||
message: IBusterMetric | BusterDashboardResponse | BusterCollection | null
|
||||
): BusterShare | null => {
|
||||
|
|
|
@ -5,11 +5,12 @@ import type {
|
|||
ChatEvent_GeneratingResponseMessage,
|
||||
ChatEvent_GeneratingReasoningMessage
|
||||
} from '@/api/buster_socket/chats';
|
||||
import type {
|
||||
BusterChatResponseMessage_text,
|
||||
BusterChatMessageReasoning_text,
|
||||
BusterChatMessageReasoning_files,
|
||||
BusterChatMessageReasoning_file
|
||||
import {
|
||||
type BusterChatResponseMessage_text,
|
||||
type BusterChatMessageReasoning_text,
|
||||
type BusterChatMessageReasoning_files,
|
||||
type BusterChatMessageReasoning_file,
|
||||
ShareRole
|
||||
} from '@/api/asset_interfaces';
|
||||
|
||||
const createInitialMessage = (messageId: string): IBusterChatMessage => ({
|
||||
|
@ -26,7 +27,17 @@ const createInitialMessage = (messageId: string): IBusterChatMessage => ({
|
|||
response_messages: {},
|
||||
reasoning_messages: {},
|
||||
created_at: new Date().toISOString(),
|
||||
final_reasoning_message: null
|
||||
final_reasoning_message: null,
|
||||
sharingKey: '',
|
||||
individual_permissions: [],
|
||||
team_permissions: [],
|
||||
organization_permissions: [],
|
||||
permission: ShareRole.CAN_VIEW,
|
||||
public_expiry_date: null,
|
||||
public_enabled_by: null,
|
||||
publicly_accessible: false,
|
||||
public_password: null,
|
||||
password_secret_id: null
|
||||
});
|
||||
|
||||
export const initializeOrUpdateMessage = (
|
||||
|
|
|
@ -12,6 +12,7 @@ import { useMemoizedFn } from '@/hooks';
|
|||
import { type BreadcrumbItem, Breadcrumb } from '@/components/ui/breadcrumb';
|
||||
import { Dots, Pencil, Plus, ShareAllRight, ShareRight, Trash } from '@/components/ui/icons';
|
||||
import { useDeleteCollection, useUpdateCollection } from '@/api/buster_rest/collections';
|
||||
import { canEdit } from '@/lib/share';
|
||||
|
||||
export const CollectionsIndividualHeader: React.FC<{
|
||||
openAddTypeModal: boolean;
|
||||
|
@ -47,7 +48,7 @@ export const CollectionsIndividualHeader: React.FC<{
|
|||
)}
|
||||
</div>
|
||||
|
||||
{collection && canEditCollection(collection) && (
|
||||
{collection && canEdit(collection.permission) && (
|
||||
<ContentRight
|
||||
collection={collection}
|
||||
openAddTypeModal={openAddTypeModal}
|
||||
|
@ -146,7 +147,3 @@ const CollectionBreadcrumb: React.FC<{
|
|||
return <Breadcrumb items={items} />;
|
||||
});
|
||||
CollectionBreadcrumb.displayName = 'CollectionBreadcrumb';
|
||||
|
||||
const canEditCollection = (collection: BusterCollection) => {
|
||||
return collection.permission === 'owner' || collection.permission === 'editor';
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { ChartType } from '@/api/asset_interfaces/metric/charts/enum';
|
|||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { cn } from '@/lib/classMerge';
|
||||
import { ShareRole } from '@/api/asset_interfaces';
|
||||
import { canEdit } from '@/lib/share';
|
||||
|
||||
export const MetricViewChart: React.FC<{
|
||||
metricId: string;
|
||||
|
@ -24,9 +25,7 @@ export const MetricViewChart: React.FC<{
|
|||
const { title, description, time_frame, evaluation_score, evaluation_summary } = metric;
|
||||
const isTable = metric.chart_config.selectedChartType === ChartType.Table;
|
||||
|
||||
const readOnly =
|
||||
readOnlyProp ||
|
||||
!(metric.permission === ShareRole.OWNER || metric.permission === ShareRole.EDITOR);
|
||||
const readOnly = readOnlyProp || !canEdit(metric.permission);
|
||||
|
||||
const loadingData = !isFetchedMetricData;
|
||||
const errorData = !!metricDataError;
|
||||
|
|
|
@ -31,6 +31,7 @@ import { useFavoriteStar } from '@/components/features/list/FavoriteStar';
|
|||
import { timeout } from '@/lib';
|
||||
import { ShareMenuContent } from '@/components/features/ShareMenu/ShareMenuContent';
|
||||
import { DASHBOARD_TITLE_INPUT_ID } from '@/controllers/DashboardController/DashboardViewDashboardController/DashboardEditTitle';
|
||||
import { isEffectiveOwner } from '@/lib/share';
|
||||
|
||||
export const DashboardThreeDotMenu = React.memo(({ dashboardId }: { dashboardId: string }) => {
|
||||
const versionHistoryItems = useVersionHistorySelectMenu({ dashboardId });
|
||||
|
@ -211,7 +212,7 @@ const useRenameDashboardSelectMenu = ({ dashboardId }: { dashboardId: string })
|
|||
|
||||
export const useShareMenuSelectMenu = ({ dashboardId }: { dashboardId: string }) => {
|
||||
const { data: dashboard } = useGetDashboard(dashboardId);
|
||||
const isOwner = dashboard?.permission === ShareRole.OWNER;
|
||||
const isOwner = isEffectiveOwner(dashboard?.permission);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
|
|
|
@ -45,6 +45,7 @@ import { METRIC_CHART_CONTAINER_ID } from '@/controllers/MetricController/Metric
|
|||
import { timeout } from '@/lib';
|
||||
import { METRIC_CHART_TITLE_INPUT_ID } from '@/controllers/MetricController/MetricViewChart/MetricViewChartHeader';
|
||||
import { ShareMenuContent } from '@/components/features/ShareMenu/ShareMenuContent';
|
||||
import { isEffectiveOwner } from '@/lib/share';
|
||||
|
||||
export const ThreeDotMenuButton = React.memo(({ metricId }: { metricId: string }) => {
|
||||
const { openSuccessMessage } = useBusterNotifications();
|
||||
|
@ -433,7 +434,7 @@ const useRenameMetricSelectMenu = ({ metricId }: { metricId: string }) => {
|
|||
|
||||
export const useShareMenuSelectMenu = ({ metricId }: { metricId: string }) => {
|
||||
const { data: metric } = useGetMetric(metricId);
|
||||
const isOwner = metric?.permission === ShareRole.OWNER;
|
||||
const isOwner = isEffectiveOwner(metric?.permission);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { ShareRole } from '@/api/asset_interfaces';
|
||||
|
||||
export const isOwner = (role: ShareRole | null | undefined) => {
|
||||
return role === ShareRole.OWNER;
|
||||
};
|
||||
|
||||
export const isEffectiveOwner = (role: ShareRole | null | undefined) => {
|
||||
return role === ShareRole.FULL_ACCESS || role === ShareRole.OWNER;
|
||||
};
|
||||
|
||||
export const canEdit = (role: ShareRole | null | undefined) => {
|
||||
return role === ShareRole.CAN_EDIT || role === ShareRole.FULL_ACCESS || role === ShareRole.OWNER;
|
||||
};
|
||||
|
||||
export const canShare = (role: ShareRole | null | undefined) => {
|
||||
return role === ShareRole.FULL_ACCESS || role === ShareRole.OWNER;
|
||||
};
|
||||
|
||||
export const canFilter = (role: ShareRole) => {
|
||||
return (
|
||||
role === ShareRole.CAN_FILTER ||
|
||||
role === ShareRole.FULL_ACCESS ||
|
||||
role === ShareRole.OWNER ||
|
||||
role === ShareRole.CAN_EDIT
|
||||
);
|
||||
};
|
|
@ -1,9 +1,10 @@
|
|||
import type {
|
||||
BusterChat,
|
||||
BusterChatMessage,
|
||||
BusterChatMessageReasoning,
|
||||
BusterChatMessageReasoning_file,
|
||||
BusterChatMessageResponse
|
||||
import {
|
||||
ShareRole,
|
||||
type BusterChat,
|
||||
type BusterChatMessage,
|
||||
type BusterChatMessageReasoning,
|
||||
type BusterChatMessageReasoning_file,
|
||||
type BusterChatMessageResponse
|
||||
} from '@/api/asset_interfaces';
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
|
@ -14,7 +15,8 @@ const MOCK_MESSAGE_RESPONSE = (typeProp?: 'text' | 'file'): BusterChatMessageRes
|
|||
return {
|
||||
id: faker.string.uuid(),
|
||||
type,
|
||||
message: faker.lorem.sentence()
|
||||
message: faker.lorem.sentence(),
|
||||
is_final_message: false
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -141,7 +143,17 @@ const MOCK_MESSAGE = (): BusterChatMessage => {
|
|||
},
|
||||
{}
|
||||
),
|
||||
reasoning_message_ids: reasoningMessage.map((m) => m.id)
|
||||
reasoning_message_ids: reasoningMessage.map((m) => m.id),
|
||||
sharingKey: '',
|
||||
individual_permissions: [],
|
||||
team_permissions: [],
|
||||
organization_permissions: [],
|
||||
permission: ShareRole.CAN_VIEW,
|
||||
public_expiry_date: null,
|
||||
public_enabled_by: null,
|
||||
publicly_accessible: false,
|
||||
public_password: null,
|
||||
password_secret_id: null
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -102,20 +102,14 @@ refresh_interval: 300`,
|
|||
config: {
|
||||
rows
|
||||
},
|
||||
sharingKey: 'mock-sharing-key',
|
||||
publicly_accessible: false,
|
||||
public_password: null,
|
||||
public_expiry_date: null,
|
||||
public_enabled_by: null,
|
||||
password_secret_id: null,
|
||||
versions: []
|
||||
};
|
||||
|
||||
const response: BusterDashboardResponse = {
|
||||
access: ShareRole.EDITOR,
|
||||
access: ShareRole.CAN_EDIT,
|
||||
metrics: metrics.reduce((acc, metric) => ({ ...acc, [metric.id]: metric }), {}),
|
||||
dashboard,
|
||||
permission: ShareRole.EDITOR,
|
||||
permission: ShareRole.CAN_EDIT,
|
||||
public_password: null,
|
||||
sharingKey: 'mock-sharing-key',
|
||||
individual_permissions: null,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { IBusterChatMessage } from '@/api/asset_interfaces';
|
||||
import { ShareRole, type IBusterChatMessage } from '@/api/asset_interfaces';
|
||||
|
||||
export const mockBusterChatMessage: IBusterChatMessage = {
|
||||
id: 'message-1',
|
||||
|
@ -121,5 +121,15 @@ hunchback_of_notre_dame:
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
sharingKey: '',
|
||||
individual_permissions: [],
|
||||
team_permissions: [],
|
||||
organization_permissions: [],
|
||||
permission: ShareRole.CAN_VIEW,
|
||||
public_expiry_date: null,
|
||||
public_enabled_by: null,
|
||||
publicly_accessible: false,
|
||||
public_password: null,
|
||||
password_secret_id: null
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue