Feat/front metadata request (#1977)

* wip

* Wip

* Wip

* Finished v1

* Fix from PR

* Removed unused fragment masking feature
This commit is contained in:
Lucas Bordeau
2023-10-13 18:01:57 +02:00
committed by GitHub
parent 41ae30cada
commit cafcfdc95e
28 changed files with 1439 additions and 140 deletions

View File

@ -2,9 +2,12 @@ import { DateTime } from 'luxon';
import { useRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { FilterOperand } from '@/ui/view-bar/types/FilterOperand';
import { turnFilterIntoWhereClause } from '@/ui/view-bar/utils/turnFilterIntoWhereClause';
import { ActivityType, useGetActivitiesQuery } from '~/generated/graphql';
import {
ActivityType,
useGetActivitiesQuery,
ViewFilterOperand,
} from '~/generated/graphql';
import { parseDate } from '~/utils/date-utils';
export const useCurrentUserTaskCount = () => {
@ -20,7 +23,7 @@ export const useCurrentUserTaskCount = () => {
key: 'assigneeId',
type: 'entity',
value: currentUser.id,
operand: FilterOperand.Is,
operand: ViewFilterOperand.Is,
displayValue: currentUser.displayName,
displayAvatarUrl: currentUser.avatarUrl ?? undefined,
})

View File

@ -0,0 +1,36 @@
/* eslint-disable no-console */
import { useMemo } from 'react';
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { useRecoilState } from 'recoil';
import { tokenPairState } from '@/auth/states/tokenPairState';
import { ApolloClientMetadataContext } from '../context/ApolloClientMetadataContext';
export const ApolloClientMetadataProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const [tokenPair] = useRecoilState(tokenPairState);
const apolloClientMetadata = useMemo(() => {
if (tokenPair?.accessToken.token) {
return new ApolloClient({
uri: `${process.env.REACT_APP_SERVER_BASE_URL}/metadata`,
cache: new InMemoryCache(),
headers: {
Authorization: `Bearer ${tokenPair.accessToken.token}`,
},
});
} else {
return null;
}
}, [tokenPair]);
return (
<ApolloClientMetadataContext.Provider value={apolloClientMetadata}>
{children}
</ApolloClientMetadataContext.Provider>
);
};

View File

@ -0,0 +1,37 @@
import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { ObjectsQuery } from '~/generated-metadata/graphql';
import { GET_ALL_OBJECTS } from '../graphql/queries';
import { useApolloClientMetadata } from '../hooks/useApolloClientMetadata';
import { metadataObjectsState } from '../states/metadataObjectsState';
import { MetadataObject } from '../types/MetadataObject';
export const FetchMetadataEffect = () => {
const [metadataObjects, setMetadataObjects] =
useRecoilState(metadataObjectsState);
const apolloClientMetadata = useApolloClientMetadata();
useEffect(() => {
(async () => {
if (apolloClientMetadata && metadataObjects.length === 0) {
const objects = await apolloClientMetadata.query<ObjectsQuery>({
query: GET_ALL_OBJECTS,
});
if (objects.data.objects.edges.length > 0) {
const formattedObjects: MetadataObject[] =
objects.data.objects.edges.map((object) => ({
...object.node,
fields: object.node.fields.edges.map((field) => field.node),
}));
setMetadataObjects(formattedObjects);
}
}
})();
}, [metadataObjects, setMetadataObjects, apolloClientMetadata]);
return <></>;
};

View File

@ -0,0 +1,5 @@
import { createContext } from 'react';
import { ApolloClient, NormalizedCacheObject } from '@apollo/client';
export const ApolloClientMetadataContext =
createContext<ApolloClient<NormalizedCacheObject> | null>(null);

View File

@ -0,0 +1,58 @@
import { gql } from '@apollo/client';
export const GET_ALL_OBJECTS = gql`
query Objects {
objects(paging: { first: 100 }) {
edges {
node {
id
dataSourceId
nameSingular
namePlural
labelSingular
labelPlural
description
icon
isCustom
isActive
createdAt
updatedAt
fields(paging: { first: 100 }) {
edges {
node {
id
type
nameSingular
namePlural
labelSingular
labelPlural
description
icon
placeholder
isCustom
isActive
isNullable
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
`;

View File

@ -0,0 +1,7 @@
import { useContext } from 'react';
import { ApolloClientMetadataContext } from '../context/ApolloClientMetadataContext';
export const useApolloClientMetadata = () => {
return useContext(ApolloClientMetadataContext);
};

View File

@ -0,0 +1,8 @@
import { atom } from 'recoil';
import { MetadataObject } from '../types/MetadataObject';
export const metadataObjectsState = atom<MetadataObject[]>({
key: 'metadataObjectsState',
default: [],
});

View File

@ -0,0 +1,5 @@
import { Field, Object as GeneratedObject } from '~/generated-metadata/graphql';
export type MetadataObject = Omit<GeneratedObject, 'fields'> & {
fields: Field[];
};

View File

@ -10,10 +10,10 @@ import { useUpsertFilter } from '@/ui/view-bar/hooks/useUpsertFilter';
import { filterDefinitionUsedInDropdownScopedState } from '@/ui/view-bar/states/filterDefinitionUsedInDropdownScopedState';
import { filterDropdownSelectedEntityIdScopedState } from '@/ui/view-bar/states/filterDropdownSelectedEntityIdScopedState';
import { selectedOperandInDropdownScopedState } from '@/ui/view-bar/states/selectedOperandInDropdownScopedState';
import { ViewFilterOperand } from '~/generated/graphql';
import { useViewBarContext } from '../hooks/useViewBarContext';
import { filterDropdownSearchInputScopedState } from '../states/filterDropdownSearchInputScopedState';
import { FilterOperand } from '../types/FilterOperand';
export const FilterDropdownEntitySearchSelect = ({
entitiesForSelect,
@ -113,7 +113,7 @@ export const FilterDropdownEntitySearchSelect = ({
upsertFilter({
displayValue: filterDefinitionUsedInDropdown.selectAllLabel,
key: filterDefinitionUsedInDropdown.key,
operand: FilterOperand.IsNotNull,
operand: ViewFilterOperand.IsNotNull,
type: filterDefinitionUsedInDropdown.type,
value: '',
});
@ -126,7 +126,7 @@ export const FilterDropdownEntitySearchSelect = ({
} else {
setFilterDropdownSelectedEntityId(filterCurrentlyEdited.value);
setIsAllEntitySelected(
filterCurrentlyEdited.operand === FilterOperand.IsNotNull,
filterCurrentlyEdited.operand === ViewFilterOperand.IsNotNull,
);
}
}, [

View File

@ -1,6 +1,7 @@
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { ViewFilterOperand } from '~/generated/graphql';
import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited';
import { useUpsertFilter } from '../hooks/useUpsertFilter';
@ -8,7 +9,6 @@ import { useViewBarContext } from '../hooks/useViewBarContext';
import { filterDefinitionUsedInDropdownScopedState } from '../states/filterDefinitionUsedInDropdownScopedState';
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
import { FilterOperand } from '../types/FilterOperand';
import { getOperandLabel } from '../utils/getOperandLabel';
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
@ -41,7 +41,7 @@ export const FilterDropdownOperandSelect = () => {
const upsertFilter = useUpsertFilter();
const handleOperangeChange = (newOperand: FilterOperand) => {
const handleOperangeChange = (newOperand: ViewFilterOperand) => {
setSelectedOperandInDropdown(newOperand);
setIsFilterDropdownOperandSelectUnfolded(false);

View File

@ -10,11 +10,11 @@ import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { filterDefinitionUsedInDropdownScopedState } from '@/ui/view-bar/states/filterDefinitionUsedInDropdownScopedState';
import { selectedOperandInDropdownScopedState } from '@/ui/view-bar/states/selectedOperandInDropdownScopedState';
import { ViewFilterOperand } from '~/generated/graphql';
import { useViewBarContext } from '../hooks/useViewBarContext';
import { availableFiltersScopedState } from '../states/availableFiltersScopedState';
import { filtersScopedState } from '../states/filtersScopedState';
import { FilterOperand } from '../types/FilterOperand';
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
import { FilterDropdownEntitySearchInput } from './FilterDropdownEntitySearchInput';
@ -72,7 +72,7 @@ export const SingleEntityFilterDropdownButton = ({
<GenericEntityFilterChip
filter={filters[0]}
Icon={
filters[0].operand === FilterOperand.IsNotNull
filters[0].operand === ViewFilterOperand.IsNotNull
? availableFilter.SelectAllIcon
: undefined
}

View File

@ -1,9 +1,9 @@
import { atomFamily } from 'recoil';
import { FilterOperand } from '../types/FilterOperand';
import { ViewFilterOperand } from '~/generated/graphql';
export const selectedOperandInDropdownScopedState = atomFamily<
FilterOperand | null,
ViewFilterOperand | null,
string
>({
key: 'selectedOperandInDropdownScopedState',

View File

@ -1,4 +1,5 @@
import { FilterOperand } from './FilterOperand';
import { ViewFilterOperand } from '~/generated/graphql';
import { FilterType } from './FilterType';
export type Filter = {
@ -7,5 +8,5 @@ export type Filter = {
value: string;
displayValue: string;
displayAvatarUrl?: string;
operand: FilterOperand;
operand: ViewFilterOperand;
};

View File

@ -1 +0,0 @@
export { ViewFilterOperand as FilterOperand } from '~/generated/graphql';

View File

@ -1,20 +1,22 @@
import { FilterOperand } from '../types/FilterOperand';
import { ViewFilterOperand } from '~/generated/graphql';
export const getOperandLabel = (operand: FilterOperand | null | undefined) => {
export const getOperandLabel = (
operand: ViewFilterOperand | null | undefined,
) => {
switch (operand) {
case FilterOperand.Contains:
case ViewFilterOperand.Contains:
return 'Contains';
case FilterOperand.DoesNotContain:
case ViewFilterOperand.DoesNotContain:
return "Doesn't contain";
case FilterOperand.GreaterThan:
case ViewFilterOperand.GreaterThan:
return 'Greater than';
case FilterOperand.LessThan:
case ViewFilterOperand.LessThan:
return 'Less than';
case FilterOperand.Is:
case ViewFilterOperand.Is:
return 'Is';
case FilterOperand.IsNot:
case ViewFilterOperand.IsNot:
return 'Is not';
case FilterOperand.IsNotNull:
case ViewFilterOperand.IsNotNull:
return 'Is not null';
default:
return '';
@ -22,20 +24,20 @@ export const getOperandLabel = (operand: FilterOperand | null | undefined) => {
};
export const getOperandLabelShort = (
operand: FilterOperand | null | undefined,
operand: ViewFilterOperand | null | undefined,
) => {
switch (operand) {
case FilterOperand.Is:
case FilterOperand.Contains:
case ViewFilterOperand.Is:
case ViewFilterOperand.Contains:
return ': ';
case FilterOperand.IsNot:
case FilterOperand.DoesNotContain:
case ViewFilterOperand.IsNot:
case ViewFilterOperand.DoesNotContain:
return ': Not';
case FilterOperand.IsNotNull:
case ViewFilterOperand.IsNotNull:
return ': NotNull';
case FilterOperand.GreaterThan:
case ViewFilterOperand.GreaterThan:
return '\u00A0> ';
case FilterOperand.LessThan:
case ViewFilterOperand.LessThan:
return '\u00A0< ';
default:
return ': ';

View File

@ -1,17 +1,18 @@
import { FilterOperand } from '../types/FilterOperand';
import { ViewFilterOperand } from '~/generated/graphql';
import { FilterType } from '../types/FilterType';
export const getOperandsForFilterType = (
filterType: FilterType | null | undefined,
): FilterOperand[] => {
): ViewFilterOperand[] => {
switch (filterType) {
case 'text':
return [FilterOperand.Contains, FilterOperand.DoesNotContain];
return [ViewFilterOperand.Contains, ViewFilterOperand.DoesNotContain];
case 'number':
case 'date':
return [FilterOperand.GreaterThan, FilterOperand.LessThan];
return [ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan];
case 'entity':
return [FilterOperand.Is, FilterOperand.IsNot];
return [ViewFilterOperand.Is, ViewFilterOperand.IsNot];
default:
return [];
}

View File

@ -1,11 +1,10 @@
import { QueryMode } from '~/generated/graphql';
import { QueryMode, ViewFilterOperand } from '~/generated/graphql';
import { Filter } from '../types/Filter';
import { FilterOperand } from '../types/FilterOperand';
export const turnFilterIntoWhereClause = (filter: Filter) => {
switch (filter.operand) {
case FilterOperand.IsNotNull:
case ViewFilterOperand.IsNotNull:
return {
[filter.key]: {
not: null,
@ -15,14 +14,14 @@ export const turnFilterIntoWhereClause = (filter: Filter) => {
switch (filter.type) {
case 'text':
switch (filter.operand) {
case FilterOperand.Contains:
case ViewFilterOperand.Contains:
return {
[filter.key]: {
contains: filter.value,
mode: QueryMode.Insensitive,
},
};
case FilterOperand.DoesNotContain:
case ViewFilterOperand.DoesNotContain:
return {
[filter.key]: {
not: {
@ -38,13 +37,13 @@ export const turnFilterIntoWhereClause = (filter: Filter) => {
}
case 'number':
switch (filter.operand) {
case FilterOperand.GreaterThan:
case ViewFilterOperand.GreaterThan:
return {
[filter.key]: {
gte: parseFloat(filter.value),
},
};
case FilterOperand.LessThan:
case ViewFilterOperand.LessThan:
return {
[filter.key]: {
lte: parseFloat(filter.value),
@ -57,13 +56,13 @@ export const turnFilterIntoWhereClause = (filter: Filter) => {
}
case 'date':
switch (filter.operand) {
case FilterOperand.GreaterThan:
case ViewFilterOperand.GreaterThan:
return {
[filter.key]: {
gte: filter.value,
},
};
case FilterOperand.LessThan:
case ViewFilterOperand.LessThan:
return {
[filter.key]: {
lte: filter.value,
@ -76,13 +75,13 @@ export const turnFilterIntoWhereClause = (filter: Filter) => {
}
case 'entity':
switch (filter.operand) {
case FilterOperand.Is:
case ViewFilterOperand.Is:
return {
[filter.key]: {
equals: filter.value,
},
};
case FilterOperand.IsNot:
case ViewFilterOperand.IsNot:
return {
[filter.key]: {
not: { equals: filter.value },