querys for permissions

This commit is contained in:
Nate Kelley 2025-01-10 10:05:38 -07:00
parent ddaf4e9347
commit d96cb9ba68
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
21 changed files with 321 additions and 434 deletions

View File

@ -1,3 +1,4 @@
export * from './responseInterfaces';
export * from './requests';
export * from './queryRequests';
export * from './permissions';

View File

@ -0,0 +1 @@
export const GET_PERMISSIONS_OVERVIEW = (datasetId: string) => `/datasets/${datasetId}/overview`;

View File

@ -0,0 +1,3 @@
export * from './requests';
export * from './queryRequests';
export * from './responseInterfaces';

View File

@ -0,0 +1,103 @@
import { useCreateReactQuery, useCreateReactMutation } from '@/api/createReactQuery';
import {
getDatasetPermissionsOverview,
listDatasetGroups,
listPermissionGroups,
listPermissionUsers,
updateDatasetGroups,
updatePermissionGroups,
updatePermissionUsers
} from './requests';
import { useMemoizedFn } from 'ahooks';
import { QueryClient } from '@tanstack/react-query';
import { getDatasetPermissionsOverview_server } from './serverRequests';
export const useGetDatasetPermissionsOverview = (dataset_id: string) => {
const queryFn = useMemoizedFn(() => getDatasetPermissionsOverview({ dataset_id }));
return useCreateReactQuery({
queryKey: ['dataset_permissions_overview', dataset_id],
queryFn
});
};
export const prefetchGetDatasetPermissionsOverview = async (
datasetId: string,
queryClientProp?: QueryClient
) => {
const queryClient = queryClientProp || new QueryClient();
await queryClient.prefetchQuery({
queryKey: ['dataset_permissions_overview', datasetId],
queryFn: () => getDatasetPermissionsOverview_server(datasetId)
});
return queryClient;
};
export const useListPermissionGroups = (dataset_id: string) => {
const queryFn = useMemoizedFn(() => listPermissionGroups({ dataset_id }));
return useCreateReactQuery({
queryKey: ['list_permission_groups', dataset_id],
queryFn
});
};
export const useListDatasetGroups = (dataset_id: string) => {
const queryFn = useMemoizedFn(() => listDatasetGroups({ dataset_id }));
return useCreateReactQuery({
queryKey: ['list_dataset_groups', dataset_id],
queryFn
});
};
export const useListPermissionUsers = (dataset_id: string) => {
const queryFn = useMemoizedFn(() => listPermissionUsers({ dataset_id }));
return useCreateReactQuery({
queryKey: ['list_permission_users', dataset_id],
queryFn
});
};
export const useUpdatePermissionGroups = (dataset_id: string) => {
const mutationFn = useMemoizedFn((groups: { id: string; assigned: boolean }[]) =>
updatePermissionGroups({ dataset_id, groups })
);
const onSuccess = useMemoizedFn(() => {
// queryClient.invalidateQueries({ queryKey: ['dataset_permissions_overview', dataset_id] });
});
return useCreateReactMutation({
mutationFn,
onSuccess
});
};
export const useUpdateDatasetGroups = (dataset_id: string) => {
const mutationFn = useMemoizedFn((groups: { id: string; assigned: boolean }[]) =>
updateDatasetGroups({ dataset_id, groups })
);
const onSuccess = useMemoizedFn(() => {
// queryClient.invalidateQueries({ queryKey: ['dataset_permissions_overview', dataset_id] });
});
return useCreateReactMutation({
mutationFn,
onSuccess
});
};
export const useUpdatePermissionUsers = (dataset_id: string) => {
const mutationFn = useMemoizedFn((users: { id: string; assigned: boolean }[]) =>
updatePermissionUsers({ dataset_id, users })
);
const onSuccess = useMemoizedFn(() => {
// queryClient.invalidateQueries({ queryKey: ['dataset_permissions_overview', dataset_id] });
});
return useCreateReactMutation({
mutationFn,
onSuccess
});
};

View File

@ -0,0 +1,73 @@
import { mainApi } from '../../../buster';
import {
DatasetPermissionsOverviewResponse,
ListDatasetGroupsResponse,
ListPermissionGroupsResponse,
ListPermissionUsersResponse
} from './responseInterfaces';
import * as config from './config';
export const listPermissionGroups = async ({
dataset_id
}: {
dataset_id: string;
}): Promise<ListPermissionGroupsResponse[]> => {
return await mainApi.get(`/datasets/${dataset_id}/permission_groups`).then((res) => res.data);
};
export const listDatasetGroups = async ({
dataset_id
}: {
dataset_id: string;
}): Promise<ListDatasetGroupsResponse[]> => {
return await mainApi.get(`/datasets/${dataset_id}/dataset_groups`).then((res) => res.data);
};
export const listPermissionUsers = async ({
dataset_id
}: {
dataset_id: string;
}): Promise<ListPermissionUsersResponse[]> => {
return await mainApi.get(`/datasets/${dataset_id}/users`).then((res) => res.data);
};
export const updatePermissionUsers = async ({
dataset_id,
users
}: {
dataset_id: string;
users: {
id: string;
assigned: boolean;
}[];
}): Promise<void> => {
return await mainApi.put(`/datasets/${dataset_id}/users`, users);
};
export const updatePermissionGroups = async ({
dataset_id,
groups
}: {
dataset_id: string;
groups: { id: string; assigned: boolean }[];
}): Promise<void> => {
return await mainApi.put(`/datasets/${dataset_id}/permission_groups`, groups);
};
export const updateDatasetGroups = async ({
dataset_id,
groups
}: {
dataset_id: string;
groups: { id: string; assigned: boolean }[];
}): Promise<void> => {
return await mainApi.put(`/datasets/${dataset_id}/dataset_groups`, groups);
};
export const getDatasetPermissionsOverview = async ({
dataset_id
}: {
dataset_id: string;
}): Promise<DatasetPermissionsOverviewResponse> => {
return await mainApi.get(config.GET_PERMISSIONS_OVERVIEW(dataset_id)).then((res) => res.data);
};

View File

@ -0,0 +1,29 @@
export interface ListPermissionGroupsResponse {
id: string;
name: string;
assigned: boolean;
}
export interface ListDatasetGroupsResponse {
id: string;
name: string;
assigned: boolean;
}
export interface ListPermissionUsersResponse {
id: string;
name: string;
assigned: boolean;
}
export interface DatasetPermissionOverviewUser {
id: string;
name: string;
can_query: boolean;
lineage: any[];
}
export interface DatasetPermissionsOverviewResponse {
dataset_id: string;
users: DatasetPermissionOverviewUser[];
}

View File

@ -0,0 +1,12 @@
'use server';
import * as config from './config';
import { serverFetch } from '../../../createServerInstance';
import { DatasetPermissionsOverviewResponse } from './responseInterfaces';
export const getDatasetPermissionsOverview_server = async (datasetId: string) => {
const response = await serverFetch<DatasetPermissionsOverviewResponse>(
config.GET_PERMISSIONS_OVERVIEW(datasetId)
);
return response;
};

View File

@ -1,5 +1,11 @@
import { useCreateReactMutation, useCreateReactQuery } from '@/api/createReactQuery';
import { createDataset, getDatasetData, getDatasetMetadata, getDatasets } from './requests';
import {
createDataset,
deployDataset,
getDatasetDataSample,
getDatasetMetadata,
getDatasets
} from './requests';
import { BusterDataset, BusterDatasetData, BusterDatasetListItem } from './responseInterfaces';
import { useMemoizedFn } from 'ahooks';
import { QueryClient, useQuery, useQueryClient } from '@tanstack/react-query';
@ -36,24 +42,16 @@ export const prefetchGetDatasets = async (
};
export const useGetDatasetData = (datasetId: string) => {
const queryFn = useMemoizedFn(() => getDatasetData(datasetId));
const queryFn = useMemoizedFn(() => getDatasetDataSample(datasetId));
return useCreateReactQuery<BusterDatasetData>({
queryKey: ['datasetData', datasetId],
queryFn,
enabled: !!datasetId,
refetchOnMount: false
refetchOnMount: false,
staleTime: 1000 * 60 * 10 // 10 minutes
});
};
export const prefetchGetDatasetData = async (datasetId: string, queryClientProp?: QueryClient) => {
const queryClient = queryClientProp || new QueryClient();
await queryClient.prefetchQuery({
queryKey: ['datasetData', datasetId],
queryFn: () => getDatasetData(datasetId)
});
return queryClient;
};
export const useGetDatasetMetadata = (datasetId: string) => {
const queryFn = useMemoizedFn(() => getDatasetMetadata(datasetId));
return useCreateReactQuery<BusterDataset>({
@ -100,3 +98,16 @@ export const useCreateDataset = () => {
onError
});
};
export const useDeployDataset = () => {
const mutationFn = useMemoizedFn((params: { dataset_id: string; sql: string; yml: string }) =>
deployDataset(params)
);
const onSuccess = useMemoizedFn((res: any) => {
console.log(res);
});
return useCreateReactMutation({
mutationFn,
onSuccess
});
};

View File

@ -24,7 +24,7 @@ export const getDatasetMetadata = async (datasetId: string): Promise<BusterDatas
.then((res) => res.data);
};
export const getDatasetData = async (datasetId: string): Promise<BusterDatasetData> => {
export const getDatasetDataSample = async (datasetId: string): Promise<BusterDatasetData> => {
return await mainApi
.get<BusterDatasetData>(`/datasets/${datasetId}/data/sample`)
.then((res) => res.data);
@ -37,3 +37,16 @@ export const createDataset = async (dataset: BusterDataset): Promise<BusterDatas
export const deleteDataset = async (datasetId: string): Promise<void> => {
return await mainApi.delete(`/datasets/${datasetId}`).then((res) => res.data);
};
export const deployDataset = async ({
dataset_id,
...params
}: {
dataset_id: string;
sql: string;
yml: string;
}): Promise<void> => {
return await mainApi
.post(`/datasets/deploy`, { id: dataset_id, ...params })
.then((res) => res.data);
};

View File

@ -1,7 +1,8 @@
'use server';
import { useSupabaseServerContext } from '@/context/Supabase/useSupabaseContext';
import { BASE_URL } from './buster/instances';
import type { RequestInit } from 'next/dist/server/web/spec-extension/request';
import { createClient } from '../context/Supabase/server';
export interface FetchConfig extends RequestInit {
baseURL?: string;
@ -9,7 +10,9 @@ export interface FetchConfig extends RequestInit {
}
export const serverFetch = async <T>(url: string, config: FetchConfig = {}): Promise<T> => {
const { accessToken } = await useSupabaseServerContext();
const supabase = await createClient();
const sessionData = await supabase.auth.getSession();
const accessToken = sessionData.data?.session?.access_token;
const { baseURL = BASE_URL, params, headers = {}, method = 'GET', ...restConfig } = config;

View File

@ -1,5 +1,6 @@
'use client';
import React from 'react';
import { useGetDatasets } from '@/api/busterv2/datasets';
import { useUserConfigContextSelector } from '@/context/Users';
import { useMemo, useState } from 'react';

View File

@ -2,7 +2,7 @@
import { useIndividualDataset } from '@/context/Datasets';
import { useSelectedLayoutSegment } from 'next/navigation';
import React, { PropsWithChildren, useLayoutEffect, useState } from 'react';
import React, { PropsWithChildren, useEffect, useLayoutEffect, useState } from 'react';
import { DatasetApps } from './_config';
import {
createContext,
@ -12,23 +12,24 @@ import {
export const useDatasetPageContext = ({ datasetId }: { datasetId: string }) => {
const segments = useSelectedLayoutSegment() as DatasetApps;
const datasetResult = useIndividualDataset({ datasetId });
const datasetSQL = datasetResult.dataset.data?.sql;
const datasetYmlFile = datasetResult.dataset.data?.yml_file;
const [sql, setSQL] = useState<string>(datasetSQL || '');
const { dataset, datasetData } = useIndividualDataset({ datasetId });
const originalDatasetSQL = dataset.data?.sql;
const datasetYmlFile = dataset.data?.yml_file;
const [sql, setSQL] = useState<string>(originalDatasetSQL || '');
const [ymlFile, setYmlFile] = useState<string>(datasetYmlFile || '');
const selectedApp = segments;
useLayoutEffect(() => {
setSQL(datasetSQL || '');
}, [datasetSQL]);
useEffect(() => {
setSQL(originalDatasetSQL || '');
}, [originalDatasetSQL]);
useLayoutEffect(() => {
useEffect(() => {
setYmlFile(datasetYmlFile || '');
}, [datasetYmlFile]);
return { sql, ymlFile, selectedApp, setSQL, setYmlFile, ...datasetResult };
return { sql, ymlFile, selectedApp, setSQL, setYmlFile, datasetData, dataset };
};
const DatasetPageContext = createContext<ReturnType<typeof useDatasetPageContext>>(

View File

@ -16,9 +16,13 @@ export const DatasetsHeaderOptions: React.FC<{
datasetId: string | undefined;
}> = React.memo(({ datasetId, isAdmin, selectedApp }) => {
const { push } = useRouter();
const optionsItems = isAdmin
? [DatasetApps.OVERVIEW, DatasetApps.PERMISSIONS, DatasetApps.EDITOR]
: [DatasetApps.OVERVIEW, DatasetApps.PERMISSIONS];
const optionsItems = useMemo(
() =>
isAdmin
? [DatasetApps.OVERVIEW, DatasetApps.PERMISSIONS, DatasetApps.EDITOR]
: [DatasetApps.OVERVIEW, DatasetApps.PERMISSIONS],
[isAdmin]
);
const options: SegmentedProps['options'] = useMemo(
() =>

View File

@ -22,7 +22,7 @@ export const DatasetIndividualThreeDotMenu: React.FC<{
}, [datasetId, onDeleteDataset]);
return (
<Dropdown menu={menu}>
<Dropdown menu={menu} trigger={['click']}>
<Button type="text" icon={<AppMaterialIcons icon="more_horiz" />} />
</Dropdown>
);

View File

@ -3,7 +3,7 @@ import React from 'react';
export enum DatasetApps {
OVERVIEW = 'overview',
PERMISSIONS = 'PERMISSIONS',
PERMISSIONS = 'permissions',
EDITOR = 'editor'
}

View File

@ -19,7 +19,6 @@ export const EditorContent: React.FC<{
const ref = useRef<HTMLDivElement>(null);
const splitterRef = useRef<AppSplitterRef>(null);
const [selectedApp, setSelectedApp] = useState<EditorApps>(EditorApps.PREVIEW);
const dataset = useDatasetPageContextSelector((state) => state.dataset);
const datasetData = useDatasetPageContextSelector((state) => state.datasetData);
const sql = useDatasetPageContextSelector((state) => state.sql);
const setSQL = useDatasetPageContextSelector((state) => state.setSQL);
@ -28,6 +27,7 @@ export const EditorContent: React.FC<{
const [tempData, setTempData] = useState<BusterDatasetData>(datasetData.data || []);
const [fetchingTempData, setFetchingTempData] = useState(false);
const { runAsync: runQuery } = useRequest(
async () => {
await timeout(1000);

View File

@ -7,11 +7,8 @@ import { OverviewData } from './OverviewData';
import { Divider } from 'antd';
export default function Page() {
const selectedApp = useDatasetPageContextSelector((state) => state.selectedApp);
const datasetRes = useDatasetPageContextSelector((state) => state.dataset);
const datasetDataRes = useDatasetPageContextSelector((state) => state.datasetData);
const sql = useDatasetPageContextSelector((state) => state.sql);
const setSQL = useDatasetPageContextSelector((state) => state.setSQL);
const datasetData = datasetDataRes?.data;
const dataset = datasetRes?.data;
@ -37,7 +34,7 @@ export default function Page() {
<OverviewData
datasetId={dataset.id}
data={datasetData}
data={datasetData || []}
isFetchedDatasetData={isFetchedDatasetData}
/>
</div>

View File

@ -0,0 +1,21 @@
'use client';
import { useGetDatasetPermissionsOverview } from '@/api/busterv2/datasets';
import React from 'react';
export const PermissionTitleCard: React.FC<{ datasetId: string }> = ({ datasetId }) => {
const { data, isFetched } = useGetDatasetPermissionsOverview(datasetId);
const users = data?.users;
return (
<div>
{isFetched ? (
<div>
<h1>Permission Title Card {users?.length}</h1>
</div>
) : (
<div>Loading...</div>
)}
</div>
);
};

View File

@ -1,377 +0,0 @@
import { useDatasetContextSelector } from '@/context/Datasets';
import React, { useRef } from 'react';
import { Input, InputRef } from 'antd';
import { BusterDataset, BusterDatasetColumn } from '@/api/busterv2/datasets';
import { useMemoizedFn } from 'ahooks';
import { useAntToken } from '@/styles/useAntToken';
import { AppPopoverOption, AppPopoverOptions } from '@/components/tooltip/AppPopoverOptions';
import { BsDatabaseDown, BsDatabaseSlash } from 'react-icons/bs';
import { Text, Title } from '@/components';
import { useUserConfigContextSelector } from '@/context/Users';
import { useBusterNotifications } from '@/context/BusterNotifications';
export const DatasetDescriptions: React.FC<{
selectedApp: string;
selectedDataset: BusterDataset | undefined;
sql: string | undefined;
setSQL: (value: string) => void;
isAdmin: boolean;
}> = ({ selectedDataset }) => {
return (
<div className="flex flex-col space-y-5">
{selectedDataset ? (
<>
<DatasetHeader
title="Dataset descriptions"
editableDescription={false}
description={
'Dataset descriptions are used to identify which dataset should be used to answer a users question. These descriptions should very clearly describe what insights the dataset can provide.'
}
/>
<DatasetWhenToUseContainer dataset={selectedDataset} />
<ColumnDescriptions dataset={selectedDataset} />
</>
) : (
<div>{/* <Skeleton /> */}</div>
)}
</div>
);
};
const DatasetHeader: React.FC<{
description: string;
title: string;
editableDescription?: boolean;
onChange?: (value: string) => void;
}> = ({ onChange, editableDescription = true, title, description }) => {
const [isEditingTitle, onSetIsEditTitle] = React.useState(false);
const [definition, onChangeDefinition] = React.useState(description);
const token = useAntToken();
const handleClickAwayDescription = useMemoizedFn(() => {
onSetIsEditTitle(false);
const isChanged = definition !== description;
if (isChanged) {
onChange?.(definition);
}
});
return (
<div className="flex w-full justify-between space-x-2">
<div className="flex w-full space-x-4">
<div className="flex w-full flex-col space-y-2">
<Title level={4}>{title}</Title>
{editableDescription ? (
<Input.TextArea
variant="borderless"
className={'w-full !pl-0'}
autoSize={{ maxRows: 15, minRows: 2 }}
style={{
color: isEditingTitle ? token.colorText : token.colorTextDescription
}}
defaultValue={definition}
onChange={(e) => {
onChangeDefinition(e.target.value);
}}
placeholder="Add dataset description..."
onFocus={() => {
onSetIsEditTitle(true);
}}
onBlur={() => {
handleClickAwayDescription();
}}
/>
) : (
<>
<Text type="secondary">{definition}</Text>
</>
)}
</div>
</div>
</div>
);
};
const DatasetWhenToUseContainer: React.FC<{ dataset: BusterDataset }> = ({ dataset }) => {
const onUpdateDataset = useDatasetContextSelector((state) => state.onUpdateDataset);
const isAdmin = useUserConfigContextSelector((state) => state.isAdmin);
const onUpdateWhenToUse = useMemoizedFn(
(whenToUse: string, key: 'when_to_use' | 'when_not_to_use') => {
const isChanged = whenToUse !== dataset[key];
if (isChanged) {
onUpdateDataset({
id: dataset.id,
[key]: whenToUse
});
}
}
);
return (
<div className="flex flex-col space-y-4">
<WhenToUseItem
title="When to use this dataset..."
placeholder='For example, "This dataset should be used for customer data"'
whenToUse={dataset.when_to_use}
isAdmin={isAdmin}
onChange={(v) => {
onUpdateWhenToUse(v, 'when_to_use');
}}
/>
<WhenToUseItem
title="When not to use this dataset..."
placeholder='For example, "This dataset should not be used for customer data"'
whenToUse={dataset.when_not_to_use}
isAdmin={isAdmin}
onChange={(v) => {
onUpdateWhenToUse(v, 'when_not_to_use');
}}
/>
</div>
);
};
const WhenToUseItem: React.FC<{
whenToUse: string;
title: string;
placeholder: string;
onChange: (value: string) => void;
isAdmin: boolean;
}> = ({ title, isAdmin, whenToUse, placeholder, onChange }) => {
const token = useAntToken();
const inputRef = useRef<InputRef>(null);
const [isEditingTitle, onSetIsEditTitle] = React.useState(false);
const handleClickAwayDescription = useMemoizedFn(() => {
const value = inputRef.current?.input?.value;
if (value && value !== whenToUse) {
onChange(value);
}
onSetIsEditTitle(false);
});
return (
<div
className="overflow-hidden"
style={{
borderRadius: `${token.borderRadius}px`,
border: `0.5px solid ${token.colorBorder}`
}}>
<div
className="px-4 py-2.5"
style={{
backgroundColor: token.controlItemBgActive,
borderBottom: `0.5px solid ${token.colorSplit}`
}}>
<Text>{title}</Text>
</div>
<div className="px-4 py-5">
<Input.TextArea
ref={inputRef}
variant="borderless"
defaultValue={whenToUse}
value={!isAdmin ? whenToUse : undefined}
placeholder={placeholder}
className={`!pl-0 ${!isAdmin ? '!cursor-text' : ''}`}
disabled={!isAdmin}
autoSize={{ maxRows: 8, minRows: 1 }}
style={{
color: isEditingTitle ? token.colorText : token.colorTextDescription
}}
onFocus={() => {
if (isAdmin) onSetIsEditTitle(true);
}}
onBlur={() => {
if (isAdmin) handleClickAwayDescription();
}}
/>
</div>
</div>
);
};
const ColumnDescriptions: React.FC<{ dataset: BusterDataset }> = ({ dataset }) => {
const columns = dataset.columns;
const onUpdateDatasetColumn = useDatasetContextSelector((state) => state.onUpdateDatasetColumn);
const isAdmin = useUserConfigContextSelector((state) => state.isAdmin);
const { openSuccessMessage } = useBusterNotifications();
return (
<div
className="flex flex-col space-y-6"
style={{
marginTop: 64
}}>
<DatasetHeader
editableDescription={false}
title="Column descriptions"
description={`Weve generated descriptions for each of the columns in your dataset. You can edit these descriptions to explain business acronyms, when a column should be used, or how the data should be queried. You can also edit the column title to make them more descriptive & human-friendly.`}
/>
<div>
{columns.map((column) => (
<ColumnDescription
isAdmin={isAdmin}
key={column.id}
column={column}
first={column.id === columns[0].id}
last={column.id === columns[columns.length - 1].id}
onEditDescription={(value) => {
const isChanged = value !== column.name;
if (isChanged) {
onUpdateDatasetColumn({
columnId: column.id,
description: value
});
}
}}
onToggleStoredValues={async (value) => {
const isChanged = value !== column.stored_values;
if (isChanged) {
await onUpdateDatasetColumn({
columnId: column.id,
stored_values: value
});
openSuccessMessage('Column updated');
}
}}
/>
))}
</div>
</div>
);
};
const ColumnDescription: React.FC<{
first: boolean;
last: boolean;
column: BusterDataset['columns'][0];
onEditDescription: (value: string) => void;
onToggleStoredValues: (value: boolean) => void;
isAdmin: boolean;
}> = ({ column, isAdmin, first, onToggleStoredValues, last, onEditDescription }) => {
const token = useAntToken();
const [isEditing, onSetIsEditing] = React.useState(false);
return (
<div
className="flex w-full justify-between space-x-3 px-4 py-3"
style={{
borderRadius:
first && last
? `${token.borderRadius}px`
: first
? `${token.borderRadius}px ${token.borderRadius}px 0 0`
: last
? `0 0 ${token.borderRadius}px ${token.borderRadius}px`
: 0,
border: `0.5px solid ${token.colorBorder}`,
borderBottom: !last ? `0.5px solid transparent` : `0.5px solid ${token.colorBorder}`
}}>
<div className="flex w-full flex-col space-y-3">
<div className="flex items-center space-x-2">
<Title level={4}>{column.name}</Title>
<div
className="flex items-center"
style={{
height: 26,
padding: '0px 6px ',
color: token.colorTextDescription,
borderRadius: `${token.borderRadius}px`,
border: `0.5px solid ${token.colorSplit}`
}}>
{column.type}
</div>
</div>
<Input.TextArea
className={`w-full !pl-0 ${!isAdmin ? '!cursor-text' : ''}`}
disabled={!isAdmin}
defaultValue={column.description || ''}
value={!isAdmin ? column.description || '' : undefined}
style={{
color: isEditing ? token.colorText : token.colorTextDescription
}}
variant="borderless"
onFocus={() => {
if (isAdmin) onSetIsEditing(true);
}}
autoSize={{ maxRows: 12, minRows: 1 }}
placeholder={isAdmin ? 'Add column description...' : 'No description'}
onBlur={(e) => {
if (isAdmin) {
const value = e.target.value;
onSetIsEditing(false);
onEditDescription(value);
}
}}
/>
</div>
{isAdmin && (
<StoredValuesDropdown
enabled={column.stored_values}
onToggleStoredValues={onToggleStoredValues}
/>
)}
</div>
);
};
const StoredValuesDropdown: React.FC<{
enabled: BusterDatasetColumn['stored_values'];
onToggleStoredValues: (value: boolean) => void;
}> = ({ enabled, onToggleStoredValues }) => {
const positiveIcon = <BsDatabaseDown size={18} />;
const negativeIcon = <BsDatabaseSlash size={18} />;
const storedValuesOptions: AppPopoverOption[] = [
{
key: 'not_selected',
label: 'Dont index this columns data',
description: 'This column will not be indexed and its data should never be stored.',
icon: negativeIcon,
onClick: () => {
onToggleStoredValues(false);
}
},
{
key: 'selected',
label: 'Index this columns data',
description:
'This column will be indexed. This column contains distinct string or enum values. This column doesnt include any PII.',
icon: positiveIcon,
onClick: () => {
onToggleStoredValues(true);
}
}
];
const selectedOption = enabled ? storedValuesOptions[1]! : storedValuesOptions[0]!;
return (
<AppPopoverOptions
options={storedValuesOptions}
value={selectedOption}
showCheckIcon={false}
trigger={'click'}
placement="bottomRight"
footer={
<div className="ml-10">
Not sure?{' '}
<a className="" target="_blank">
Read the docs
</a>
</div>
}>
<div className={`relative h-fit cursor-pointer opacity-80 transition hover:opacity-100`}>
{enabled ? positiveIcon : negativeIcon}
</div>
</AppPopoverOptions>
);
};

View File

@ -1,26 +1,17 @@
'use client';
import React from 'react';
import { DatasetDescriptions } from './_DatasetDescriptions';
import { useUserConfigContextSelector } from '@/context/Users';
import { useDatasetPageContextSelector } from '../_DatasetPageContext';
import { PermissionTitleCard } from './PermissionTitleCard';
import { prefetchGetDatasetPermissionsOverview } from '@/api/busterv2/datasets/permissions/queryRequests';
import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
export default function Page() {
const sql = useDatasetPageContextSelector((state) => state.sql);
const selectedApp = useDatasetPageContextSelector((state) => state.selectedApp);
const dataset = useDatasetPageContextSelector((state) => state.dataset);
const setSQL = useDatasetPageContextSelector((state) => state.setSQL);
const isAdmin = useUserConfigContextSelector((state) => state.isAdmin);
export default async function Page({ params }: { params: { datasetId: string } }) {
const datasetId = params.datasetId;
const queryClient = await prefetchGetDatasetPermissionsOverview(datasetId);
return (
<div className="m-auto max-w-[1400px] overflow-y-auto px-14 pb-12 pt-12">
<DatasetDescriptions
setSQL={setSQL}
sql={sql}
isAdmin={isAdmin}
selectedApp={selectedApp}
selectedDataset={dataset}
/>
</div>
<HydrationBoundary state={dehydrate(queryClient)}>
<div className="m-auto max-w-[1400px] overflow-y-auto px-14 pb-12 pt-12">
<PermissionTitleCard datasetId={datasetId} />
</div>
</HydrationBoundary>
);
}

View File

@ -11,9 +11,9 @@ export const pathNameToRoute = (pathName: string, params: any): BusterRoutes =>
[BusterRoutes.APP_DASHBOARD_ID]: BusterRoutes.APP_DASHBOARDS,
[BusterRoutes.APP_COLLECTIONS_ID]: BusterRoutes.APP_COLLECTIONS,
[BusterRoutes.APP_DATASETS_ID]: BusterRoutes.APP_DATASETS,
[BusterRoutes.APP_DATASETS_ID_DESCRIPTIONS]: BusterRoutes.APP_DATASETS,
[BusterRoutes.APP_DATASETS_ID_PERMISSIONS]: BusterRoutes.APP_DATASETS,
[BusterRoutes.APP_DATASETS_ID_OVERVIEW]: BusterRoutes.APP_DATASETS,
[BusterRoutes.APP_DATASETS_ID_SQL]: BusterRoutes.APP_DATASETS,
[BusterRoutes.APP_DATASETS_ID_EDITOR]: BusterRoutes.APP_DATASETS,
[BusterRoutes.APP_TERMS_ID]: BusterRoutes.APP_TERMS
};
if (route && paramRoutesToParent[route as string]) {