Skip to content

Enum entry & default label #3216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"rimraf": "^5.0.5",
"rxjs": "^7.8.1",
"sanitize-filename": "^1.6.3",
"title-case": "^4.3.1",
"ts-essentials": "^9.4.1",
"winston": "^3.11.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz",
Expand Down
49 changes: 34 additions & 15 deletions src/common/make-enum.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { registerEnumType } from '@nestjs/graphql';
import { cleanJoin, nonEnumerable, setHas } from '@seedcompany/common';
import { cleanJoin, mapKeys, nonEnumerable } from '@seedcompany/common';
import { lowerCase } from 'lodash';
import { titleCase } from 'title-case';
import { SetRequired } from 'type-fest';
import { inspect, InspectOptionsStylized } from 'util';

export type EnumType<Enum> = Enum extends MadeEnum<infer Values, any, any>
Expand Down Expand Up @@ -82,6 +85,7 @@ export const makeEnum = <
(value: EnumValueDeclarationShape): EnumValueDeclarationObjectShape =>
typeof value === 'string' ? { value } : value,
);
const entryMap = mapKeys.fromList(entries, (e) => e.value).asMap;

const object = Object.fromEntries(entries.map((v) => [v.value, v.value]));

Expand All @@ -92,7 +96,14 @@ export const makeEnum = <
entries,
[Symbol.iterator]: () => values.values(),
// @ts-expect-error Ignoring generics for implementation.
has: (value: string) => setHas(values, value),
has: (value: string) => entryMap.has(value),
entry: (value: string) => {
const entry = entryMap.get(value);
if (!entry) {
throw new Error(`${name ?? 'Enum'} does not have member: "${value}"`);
}
return entry;
},
[inspect.custom]: (
depth: number,
options: InspectOptionsStylized,
Expand Down Expand Up @@ -137,6 +148,11 @@ export const makeEnum = <
registerEnumType(object, { name, description, valuesMap });
}

for (const entry of entries) {
// @ts-expect-error ignoring immutable here.
entry.label ??= titleCase(lowerCase(entry.value)).replace(/ and /g, ' & ');
}

return object as any;
};

Expand Down Expand Up @@ -175,23 +191,26 @@ type ValuesOfDeclarations<ValueDeclaration extends EnumValueDeclarationShape> =
* properties as optional.
*/
type NormalizedValueDeclaration<Declaration extends EnumValueDeclarationShape> =
// For values that are objects, accept them as they are...
| (Extract<Declaration, EnumValueDeclarationObjectShape> &
// plus all the normal object keys
EnumValueDeclarationObjectShape<ValuesOfDeclarations<Declaration>>)
// For values that are strings, convert them to the standard shape...
| (EnumValueDeclarationObjectShape<Extract<Declaration, string>> &
// and include all the extra keys as optional
Partial<
Omit<
Extract<Declaration, EnumValueDeclarationObjectShape>,
keyof EnumValueDeclarationObjectShape
>
>);
SetRequired<Pick<EnumValueDeclarationObjectShape<any>, 'label'>, 'label'> &
// For values that are objects, accept them as they are...
(| (Extract<Declaration, EnumValueDeclarationObjectShape> &
// plus all the normal object keys
EnumValueDeclarationObjectShape<ValuesOfDeclarations<Declaration>>)
// For values that are strings, convert them to the standard shape...
| (EnumValueDeclarationObjectShape<Extract<Declaration, string>> &
// and include all the extra keys as optional
Partial<
Omit<
Extract<Declaration, EnumValueDeclarationObjectShape>,
keyof EnumValueDeclarationObjectShape
>
>)
);

interface EnumHelpers<Values extends string, ValueDeclaration> {
readonly values: ReadonlySet<Values>;
readonly entries: ReadonlyArray<Readonly<ValueDeclaration>>;
readonly entry: (value: Values) => Readonly<ValueDeclaration>;
readonly has: <In extends string>(
value: In & {},
) => value is In & Values & {};
Expand Down
8 changes: 4 additions & 4 deletions src/components/engagement/engagement.rules.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-case-declarations */
import { Injectable } from '@nestjs/common';
import { node, relation } from 'cypher-query-builder';
import { first, intersection, startCase } from 'lodash';
import { first, intersection } from 'lodash';
import {
ID,
Role,
Expand Down Expand Up @@ -384,9 +384,9 @@ export class EngagementRules {
);
if (!validNextStatus) {
throw new UnauthorizedException(
`One or more engagements cannot be changed to ${startCase(
nextStatus,
)}. Please check engagement statuses.`,
`One or more engagements cannot be changed to ${
EngagementStatus.entry(nextStatus).label
}. Please check engagement statuses.`,
'engagement.status',
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Injectable } from '@nestjs/common';
import { startCase } from 'lodash';
import open from 'open';
import pako from 'pako';
import { ProgressReportStatus as Status } from '../dto';
Expand Down Expand Up @@ -37,8 +36,8 @@ export class ProgressReportWorkflowFlowchart {
return str ? `classDef ${type} ${str}` : [];
}),
'',
...Object.keys(Status).map(
(status) => `${status}(${startCase(status)}):::State`,
...Status.entries.map(
(status) => `${status.value}(${status.label}):::State`,
),
'',
...Object.values(Transitions).flatMap((t) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
Section,
Text,
} from '@seedcompany/nestjs-email/templates';
import { startCase } from 'lodash';
import { fiscalQuarter, fiscalYear } from '~/common';
import { Language } from '../../../components/language/dto';
import { PeriodicReport } from '../../../components/periodic-report/dto';
Expand Down Expand Up @@ -49,8 +48,12 @@ export function ProgressReportStatusChanged({
report.start,
)} FY${fiscalYear(report.start)}`;

const oldStatus = startCase(previousStatusVal) || undefined;
const newStatus = startCase(newStatusVal) || undefined;
const oldStatus = previousStatusVal
? ProgressReportStatus.entry(previousStatusVal).label
: undefined;
const newStatus = newStatusVal
? ProgressReportStatus.entry(newStatusVal).label
: undefined;

return (
<EmailTemplate
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5442,6 +5442,7 @@ __metadata:
rimraf: "npm:^5.0.5"
rxjs: "npm:^7.8.1"
sanitize-filename: "npm:^1.6.3"
title-case: "npm:^4.3.1"
ts-essentials: "npm:^9.4.1"
ts-jest: "npm:^29.1.1"
ts-morph: "npm:^19.0.0"
Expand Down Expand Up @@ -12385,6 +12386,13 @@ __metadata:
languageName: node
linkType: hard

"title-case@npm:^4.3.1":
version: 4.3.1
resolution: "title-case@npm:4.3.1"
checksum: 10c0/10bb67f3ae5cf4043b4050f5c4b3ae7fdaeef6c55bcb646d08a3567d4233f02d10f3ea55add7d8dbf55e1a320b49b79cb0b5d4ce39701a500de62cc8860d9bc8
languageName: node
linkType: hard

"tmp@npm:^0.0.33":
version: 0.0.33
resolution: "tmp@npm:0.0.33"
Expand Down
Loading