-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(filters): rework config for better type safety for dynamic filters
- Loading branch information
Showing
10 changed files
with
329 additions
and
14 deletions.
There are no files selected for viewing
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './viewConfigResolver' | ||
export * from './sectionListViewFilterKeys' | ||
// export * from './sectionListViewsConfig' |
25 changes: 25 additions & 0 deletions
25
src/lib/constants/sectionListView/sectionListViewFilterKeys.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// special key for handling search for identifiable objects | ||
// eg. searches for name, code, id and shortname | ||
// this would translate to "token" in the old API, but does not exist in GIST-API | ||
export const IDENTIFIABLE_KEY = 'identifiable' | ||
|
||
/* Allowed "keys" to filter by | ||
Used to specify the allowed filters in the query-Params as well | ||
as mapping to the correct filter component */ | ||
export const validFilterKeys = [ | ||
IDENTIFIABLE_KEY, | ||
'domainType', | ||
'valueType', | ||
'dataSet', | ||
'categoryCombo', | ||
] as const | ||
|
||
const filterKeysSet = new Set(validFilterKeys) | ||
|
||
export type FilterKeys = typeof validFilterKeys | ||
//export const filterKeys = Object.keys(filterKeys) | ||
|
||
export type FilterKey = FilterKeys[number] | ||
|
||
// Identifiable is not configurable, and is always shown in the list | ||
export type ConfigurableFilterKey = Exclude<FilterKey, typeof IDENTIFIABLE_KEY> |
91 changes: 91 additions & 0 deletions
91
src/lib/constants/sectionListView/sectionListViewsConfig.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import i18n from '@dhis2/d2-i18n' | ||
import type { ConfigurableFilterKey } from './sectionListViewFilterKeys' | ||
|
||
export interface ModelPropertyDescriptor { | ||
label: string | ||
path: string | ||
} | ||
|
||
export interface FilterDescriptor { | ||
label: string | ||
filterKey: ConfigurableFilterKey | ||
} | ||
|
||
/* Configs can either define the label and filterKey, or a string | ||
If config is a string, getTranslatedProperty will be used to get the label. */ | ||
|
||
export type FilterConfig = ConfigurableFilterKey | FilterDescriptor | ||
|
||
export type ModelPropertyConfig = string | ModelPropertyDescriptor | ||
|
||
export interface ViewConfigPart<TEntry> { | ||
available?: ReadonlyArray<TEntry> | ||
overrideDefaultAvailable?: boolean | ||
default?: ReadonlyArray<TEntry> | ||
} | ||
|
||
export interface ViewConfig { | ||
columns: ViewConfigPart<ModelPropertyConfig> | ||
filters: ViewConfigPart<FilterConfig> | ||
} | ||
|
||
// generic here is just used for "satisfies" below, for code-completion of future customizations | ||
// cant use "[key in SectionName]" here, because section.name might be a "string" | ||
export type SectionListViewConfig<Key extends string = string> = { | ||
[key in Key]?: ViewConfig | ||
} | ||
|
||
const DESCRIPTORS = { | ||
publicAccess: { path: 'sharing.public', label: i18n.t('Public access') }, | ||
} satisfies Record<string, ModelPropertyDescriptor> | ||
|
||
// This is the default views, and can be overriden per section in modelListViewsConfig below | ||
export const defaultModelViewConfig = { | ||
columns: { | ||
available: [ | ||
'name', | ||
'shortName', | ||
'code', | ||
'created', | ||
'createdBy', | ||
'href', | ||
'id', | ||
'lastUpdatedBy', | ||
DESCRIPTORS.publicAccess, | ||
], | ||
default: ['name', DESCRIPTORS.publicAccess, 'lastUpdated'], | ||
}, | ||
filters: { | ||
available: [], | ||
default: [ | ||
// NOTE: Identifiable is special, and is always included in the default filters | ||
// It should not be handled the same way as "configurable" filters | ||
], | ||
}, | ||
} satisfies ViewConfig | ||
|
||
/* this is the default views (eg. which columns and filters) to show in the List-page for each section | ||
Note: by default, the available columns are merged with columnsDefault.available above. | ||
If it's needed to override this for a section, set overrideDefaultAvailable to true | ||
and list all available columns in the available array below. | ||
Default-list will NOT be merged with columnsDefault.default - list all explicitly. | ||
elements in the default array implies they are also available, no need to list them in both. */ | ||
|
||
export const modelListViewsConfig = { | ||
dataElement: { | ||
columns: { | ||
available: ['zeroIsSignificant', 'categoryCombo'], | ||
default: [ | ||
'name', | ||
{ label: i18n.t('Domain'), path: 'domainType' }, | ||
{ label: i18n.t('Value type'), path: 'valueType' }, | ||
'lastUpdated', | ||
'sharing.public', | ||
], | ||
}, | ||
filters: { | ||
default: ['domainType', 'valueType'], | ||
available: ['categoryCombo'], | ||
}, | ||
}, | ||
} satisfies SectionListViewConfig |
142 changes: 142 additions & 0 deletions
142
src/lib/constants/sectionListView/viewConfigResolver.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import { uniqueBy } from '../../utils' | ||
import { getTranslatedProperty } from '../translatedModelProperties' | ||
import { | ||
defaultModelViewConfig, | ||
modelListViewsConfig, | ||
ViewConfigPart, | ||
ModelPropertyConfig, | ||
ModelPropertyDescriptor, | ||
FilterDescriptor, | ||
FilterConfig, | ||
} from './sectionListViewsConfig' | ||
|
||
interface ResolvedViewConfigPart<TEntry> { | ||
available: ReadonlyArray<TEntry> | ||
default: ReadonlyArray<TEntry> | ||
} | ||
interface ResolvedViewConfig { | ||
columns: ResolvedViewConfigPart<ModelPropertyDescriptor> | ||
filters: ResolvedViewConfigPart<FilterDescriptor> | ||
} | ||
|
||
interface ResolvedSectionListView { | ||
[key: string]: ResolvedViewConfig | ||
} | ||
|
||
const toModelPropertyDescriptor = ( | ||
propertyConfig: ModelPropertyConfig | ||
): ModelPropertyDescriptor => { | ||
if (typeof propertyConfig === 'string') { | ||
return { | ||
label: getTranslatedProperty(propertyConfig), | ||
path: propertyConfig, | ||
} | ||
} | ||
return propertyConfig | ||
} | ||
|
||
const toFilterDescriptor = (propertyConfig: FilterConfig): FilterDescriptor => { | ||
if (typeof propertyConfig === 'string') { | ||
return { | ||
label: getTranslatedProperty(propertyConfig), | ||
filterKey: propertyConfig, | ||
} | ||
} | ||
return propertyConfig | ||
} | ||
|
||
const resolveFilterConfig = ( | ||
part: ViewConfigPart<FilterConfig> | ||
): ResolvedViewConfigPart<FilterDescriptor> => { | ||
const { default: defaultFilters, available: defaultAvailableFilers } = | ||
defaultModelViewConfig.filters | ||
|
||
const mergedAvailableDescriptors = uniqueBy( | ||
[ | ||
part.available || [], | ||
part.overrideDefaultAvailable ? [] : defaultAvailableFilers || [], | ||
part.default || [], | ||
] | ||
.flat() | ||
.map((propConfig) => toFilterDescriptor(propConfig)), | ||
(prop) => prop.filterKey | ||
) | ||
const defaultPropConfig = part.default || defaultFilters || [] | ||
const defaultDescriptors = defaultPropConfig.map((propConfig) => | ||
toFilterDescriptor(propConfig) | ||
) | ||
return { | ||
available: mergedAvailableDescriptors, | ||
default: defaultDescriptors, | ||
} | ||
} | ||
|
||
const resolveColumnConfig = ( | ||
part: ViewConfigPart<ModelPropertyConfig> | ||
): ResolvedViewConfigPart<ModelPropertyDescriptor> => { | ||
const { default: defaultFilters, available: defaultAvailableFilers } = | ||
defaultModelViewConfig.columns | ||
|
||
const mergedAvailableDescriptors = uniqueBy( | ||
[ | ||
part.available || [], | ||
part.overrideDefaultAvailable ? [] : defaultAvailableFilers || [], | ||
part.default || [], | ||
] | ||
.flat() | ||
.map((propConfig) => toModelPropertyDescriptor(propConfig)), | ||
(prop) => prop.path | ||
) | ||
const defaultPropConfig = part.default || defaultFilters || [] | ||
const defaultDescriptors = defaultPropConfig.map((propConfig) => | ||
toModelPropertyDescriptor(propConfig) | ||
) | ||
return { | ||
available: mergedAvailableDescriptors, | ||
default: defaultDescriptors, | ||
} | ||
} | ||
|
||
// merge the default modelViewConfig with the modelViewsConfig for each section | ||
const resolveListViewsConfig = () => { | ||
const merged: ResolvedSectionListView = {} | ||
|
||
Object.entries(modelListViewsConfig).forEach((viewConfig) => { | ||
const [sectionName, sectionViewConfig] = viewConfig | ||
merged[sectionName] = { | ||
columns: resolveColumnConfig(sectionViewConfig.columns), | ||
filters: resolveFilterConfig(sectionViewConfig.filters), | ||
} | ||
}) | ||
return merged | ||
} | ||
|
||
const mergedModelViewsConfig = resolveListViewsConfig() | ||
const resolvedDefaultConfig = { | ||
columns: resolveColumnConfig(defaultModelViewConfig.columns), | ||
filters: resolveFilterConfig(defaultModelViewConfig.filters), | ||
} | ||
|
||
export const getViewConfigForSection = ( | ||
sectionName: string | ||
): ResolvedViewConfig => { | ||
const resolvedConfig = mergedModelViewsConfig[sectionName] | ||
if (resolvedConfig) { | ||
return resolvedConfig | ||
} | ||
return resolvedDefaultConfig | ||
} | ||
|
||
export const getColumnsForSection = ( | ||
sectionName: string | ||
): ResolvedViewConfig['columns'] => { | ||
const view = getViewConfigForSection(sectionName) | ||
return view.columns | ||
} | ||
|
||
export const getFiltersForSection = ( | ||
sectionName: string | ||
): ResolvedViewConfig['filters'] => { | ||
const view = getViewConfigForSection(sectionName) | ||
return view.filters | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.