chore: type improvements (#598)

* chore: type improvements

* ci: typechecking in pipeline
This commit is contained in:
Carlos Valente
2023-11-18 08:35:54 +01:00
committed by GitHub
parent 884ab0b67b
commit 2316bbebac
17 changed files with 84 additions and 34 deletions
+6 -6
View File
@@ -28,19 +28,19 @@ jobs:
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
# Run code quality per package # Run code quality per package
- name: React - Run linter - name: React - Run linter + TypeScript checks
if: always() if: always()
run: pnpm lint run: pnpm lint && tsc --noEmit
working-directory: ./apps/client working-directory: ./apps/client
- name: Server - Run linter - name: Server - Run linter + TypeScript checks
if: always() if: always()
run: pnpm lint run: pnpm lint && tsc --noEmit
working-directory: ./apps/server working-directory: ./apps/server
- name: Utils - Run linter - name: Utils - Run linter + TypeScript checks
if: always() if: always()
run: pnpm lint run: pnpm lint && tsc --noEmit
working-directory: ./packages/utils working-directory: ./packages/utils
- name: Types - Run linter - name: Types - Run linter
@@ -19,9 +19,11 @@ interface TextInputProps extends BaseProps {
isTextArea?: false; isTextArea?: false;
} }
type ResizeOptions = 'horizontal' | 'vertical' | 'none';
interface TextAreaProps extends BaseProps { interface TextAreaProps extends BaseProps {
isTextArea: true; isTextArea: true;
resize?: 'horizontal' | 'vertical' | 'none'; resize?: ResizeOptions;
} }
type InputProps = TextInputProps | TextAreaProps; type InputProps = TextInputProps | TextAreaProps;
@@ -35,7 +37,7 @@ export default function TextInput(props: InputProps) {
const textInputProps = useReactiveTextInput(initialText, submitCallback, { submitOnEnter: true }); const textInputProps = useReactiveTextInput(initialText, submitCallback, { submitOnEnter: true });
const textAreaProps = useReactiveTextInput(initialText, submitCallback); const textAreaProps = useReactiveTextInput(initialText, submitCallback);
let resize = 'none'; let resize: ResizeOptions = 'none';
if (isTextArea) { if (isTextArea) {
resize = (props as TextAreaProps)?.resize ?? 'none'; resize = (props as TextAreaProps)?.resize ?? 'none';
} }
@@ -1,3 +1,5 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-nocheck -- working on it
import { useMutation, useQuery } from '@tanstack/react-query'; import { useMutation, useQuery } from '@tanstack/react-query';
import { OSCSettings } from 'ontime-types'; import { OSCSettings } from 'ontime-types';
@@ -9,21 +9,21 @@ const cachedRundownPlaceholder = { rundown: [], revision: -1 };
// TODO: can we leverage structural sharing to see if data has changed? // TODO: can we leverage structural sharing to see if data has changed?
export default function useRundown() { export default function useRundown() {
return useQuery<GetRundownCached>({ const { data, status, isError, refetch, isFetching } = useQuery<GetRundownCached>({
queryKey: RUNDOWN, queryKey: RUNDOWN,
queryFn: fetchCachedRundown, queryFn: fetchCachedRundown,
placeholderData: cachedRundownPlaceholder, placeholderData: cachedRundownPlaceholder,
retry: 5, retry: 5,
select: (data) => data.rundown,
retryDelay: (attempt) => attempt * 2500, retryDelay: (attempt) => attempt * 2500,
refetchInterval: queryRefetchInterval, refetchInterval: queryRefetchInterval,
networkMode: 'always', networkMode: 'always',
// structuralSharing: (oldData: GetRundownCached | undefined, newData: GetRundownCached) => { // structuralSharing: (oldData: GetRundownCached | undefined, newData: GetRundownCached) => {
// if (oldData === undefined) { // if (oldData === undefined) {
// cachedRundownPlaceholder; // return cachedRundownPlaceholder;
// } // }
// const hasDataChanged = oldData?.revision === newData.revision; // const hasDataChanged = oldData?.revision === newData.revision;
// return hasDataChanged ? oldData : newData; // return hasDataChanged ? oldData : newData;
// }, // },
}); });
return { data: data?.rundown ?? [], status, isError, refetch, isFetching };
} }
@@ -1,3 +1,5 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-nocheck -- working on it
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
interface WebkitDocument extends Document { interface WebkitDocument extends Document {
@@ -1,4 +1,4 @@
import { memo } from 'react'; import { memo, ReactNode } from 'react';
import { Button, Checkbox, Switch } from '@chakra-ui/react'; import { Button, Checkbox, Switch } from '@chakra-ui/react';
import { Column } from '@tanstack/react-table'; import { Column } from '@tanstack/react-table';
import { OntimeRundownEntry } from 'ontime-types'; import { OntimeRundownEntry } from 'ontime-types';
@@ -44,7 +44,7 @@ function CuesheetTableSettings(props: CuesheetTableSettingsProps) {
defaultChecked={visible} defaultChecked={visible}
onChange={column.getToggleVisibilityHandler()} onChange={column.getToggleVisibilityHandler()}
/> />
{columnHeader} {columnHeader as ReactNode}
</label> </label>
); );
})} })}
@@ -9,7 +9,7 @@ import { millisToString } from 'ontime-utils';
* @return {string} * @return {string}
*/ */
export const parseField = (field: keyof OntimeRundown, data: unknown): string => { export const parseField = <T extends OntimeEntryCommonKeys>(field: T, data: unknown): string => {
let val; let val;
switch (field) { switch (field) {
case 'timeStart': case 'timeStart':
@@ -96,6 +96,7 @@ export const makeTable = (headerData: ProjectData, rundown: OntimeRundown, userF
rundown.forEach((entry) => { rundown.forEach((entry) => {
const row: string[] = []; const row: string[] = [];
// @ts-expect-error -- not sure how to type this
fieldOrder.forEach((field) => row.push(parseField(field, entry[field]))); fieldOrder.forEach((field) => row.push(parseField(field, entry[field])));
data.push(row); data.push(row);
}); });
@@ -1,3 +1,5 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-nocheck -- working on it
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { FormControl, Input, Switch } from '@chakra-ui/react'; import { FormControl, Input, Switch } from '@chakra-ui/react';
@@ -48,7 +48,7 @@ export default function AliasesForm() {
useEffect(() => { useEffect(() => {
if (data) { if (data) {
reset(data); reset({ aliases: data });
} }
}, [data, reset]); }, [data, reset]);
@@ -50,7 +50,7 @@ export default function AppSettingsModal() {
reset(data); reset(data);
}; };
const disableInputs = status === 'loading'; const disableInputs = status === 'pending';
if (isFetching) { if (isFetching) {
return <ModalLoader />; return <ModalLoader />;
@@ -87,7 +87,7 @@ export default function AppSettingsModal() {
description='Protect the editor with a pin code' description='Protect the editor with a pin code'
error={errors.editorKey?.message} error={errors.editorKey?.message}
> >
<ModalPinInput register={register} formName='editorKey' isDisabled={disableInputs} /> <ModalPinInput register={register as any} formName='editorKey' isDisabled={disableInputs} />
</ModalSplitInput> </ModalSplitInput>
<ModalSplitInput <ModalSplitInput
field='operatorKey' field='operatorKey'
@@ -95,7 +95,7 @@ export default function AppSettingsModal() {
description='Protect the cuesheet with a pin code' description='Protect the cuesheet with a pin code'
error={errors.operatorKey?.message} error={errors.operatorKey?.message}
> >
<ModalPinInput register={register} formName='operatorKey' isDisabled={disableInputs} /> <ModalPinInput register={register as any} formName='operatorKey' isDisabled={disableInputs} />
</ModalSplitInput> </ModalSplitInput>
<div style={{ height: '16px' }} /> <div style={{ height: '16px' }} />
<ModalSplitInput <ModalSplitInput
@@ -48,7 +48,7 @@ export default function ProjectDataForm() {
reset(data); reset(data);
}; };
const disableInputs = status === 'loading'; const disableInputs = status === 'pending';
if (isFetching) { if (isFetching) {
return <ModalLoader />; return <ModalLoader />;
@@ -94,7 +94,7 @@ export default function ViewSettingsForm() {
<AlertTitle>CSS Override</AlertTitle> <AlertTitle>CSS Override</AlertTitle>
<AlertDescription> <AlertDescription>
Ontime will use the CSS file at its install location. <br /> Ontime will use the CSS file at its install location. <br />
<span className={style.url}>{info.cssOverride}</span> <span className={style.url}>{info?.cssOverride}</span>
<ModalLink href={cssOverrideDocsUrl}>For more information, see the docs</ModalLink> <ModalLink href={cssOverrideDocsUrl}>For more information, see the docs</ModalLink>
</AlertDescription> </AlertDescription>
</div> </div>
@@ -13,7 +13,7 @@ import { EventItemActions } from '../../RundownEntry';
interface BlockActionMenuProps { interface BlockActionMenuProps {
enableDelete?: boolean; enableDelete?: boolean;
showClone?: boolean; showClone?: boolean;
actionHandler: (action: EventItemActions, payload?: unknown) => void; actionHandler: (action: EventItemActions, payload?: any) => void;
className?: string; className?: string;
} }
@@ -1,6 +1,6 @@
/* eslint-disable react/display-name */
import { ComponentType, useMemo } from 'react'; import { ComponentType, useMemo } from 'react';
import { SupportedEvent } from 'ontime-types'; import { TimeManagerType } from 'common/models/TimeManager.type';
import { Message, OntimeEvent, ProjectData, SupportedEvent, TimerMessage, ViewSettings } from 'ontime-types';
import { useStore } from 'zustand'; import { useStore } from 'zustand';
import useProjectData from '../../common/hooks-query/useProjectData'; import useProjectData from '../../common/hooks-query/useProjectData';
@@ -9,8 +9,32 @@ import useViewSettings from '../../common/hooks-query/useViewSettings';
import { runtime } from '../../common/stores/runtime'; import { runtime } from '../../common/stores/runtime';
import { useViewOptionsStore } from '../../common/stores/viewOptions'; import { useViewOptionsStore } from '../../common/stores/viewOptions';
const withData = <P extends object>(Component: ComponentType<P>) => { type WithDataProps = {
return (props: Partial<P>) => { isMirrored: boolean;
pres: TimerMessage;
publ: Message;
lower: Message;
eventNow: OntimeEvent | null;
publicEventNow: OntimeEvent | null;
eventNext: OntimeEvent | null;
publicEventNext: OntimeEvent | null;
time: TimeManagerType;
events: OntimeEvent[];
backstageEvents: OntimeEvent[];
selectedId: string | null;
publicSelectedId: string | null;
nextId: string | null;
general: ProjectData;
viewSettings: ViewSettings;
onAir: boolean;
};
function getDisplayName(Component: React.ComponentType<any>): string {
return Component.displayName || Component.name || 'Component';
}
const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
const WithDataComponent = (props: P) => {
// persisted app state // persisted app state
const isMirrored = useViewOptionsStore((state) => state.mirror); const isMirrored = useViewOptionsStore((state) => state.mirror);
@@ -84,6 +108,9 @@ const withData = <P extends object>(Component: ComponentType<P>) => {
/> />
); );
}; };
WithDataComponent.displayName = `WithData(${getDisplayName(Component)})`;
return WithDataComponent;
}; };
export default withData; export default withData;
@@ -121,7 +121,9 @@ export default function Countdown(props: CountdownProps) {
<div className='time'>{clock}</div> <div className='time'>{clock}</div>
</div> </div>
<div className='status'>{getLocalizedString(`countdown.${runningMessage}`)}</div> {runningMessage !== TimerMessage.unhandled && (
<div className='status'>{getLocalizedString(`countdown.${runningMessage}`)}</div>
)}
<span className={`timer ${standby ? 'timer--paused' : ''} ${isRunningFinished ? 'timer--finished' : ''}`}> <span className={`timer ${standby ? 'timer--paused' : ''} ${isRunningFinished ? 'timer--finished' : ''}`}>
{formattedTimer} {formattedTimer}
@@ -1,3 +1,4 @@
import { Alias, DatabaseModel, OntimeRundown, Settings } from 'ontime-types';
import { safeMerge } from '../DataProvider.utils.js'; import { safeMerge } from '../DataProvider.utils.js';
describe('safeMerge', () => { describe('safeMerge', () => {
@@ -5,8 +6,10 @@ describe('safeMerge', () => {
rundown: [], rundown: [],
project: { project: {
title: 'existing title', title: 'existing title',
description: 'existing description',
publicUrl: 'existing public URL', publicUrl: 'existing public URL',
backstageUrl: 'existing backstageUrl', backstageUrl: 'existing backstageUrl',
publicInfo: 'existing backstageInfo',
backstageInfo: 'existing backstageInfo', backstageInfo: 'existing backstageInfo',
}, },
settings: { settings: {
@@ -42,7 +45,7 @@ describe('safeMerge', () => {
onFinish: [], onFinish: [],
}, },
}, },
}; } as DatabaseModel;
it('returns existing data if new data is not provided', () => { it('returns existing data if new data is not provided', () => {
const mergedData = safeMerge(existing, undefined); const mergedData = safeMerge(existing, undefined);
@@ -51,7 +54,7 @@ describe('safeMerge', () => {
it('merges the rundown key', () => { it('merges the rundown key', () => {
const newData = { const newData = {
rundown: [{ name: 'item 1' }, { name: 'item 2' }], rundown: [{ title: 'item 1' }, { title: 'item 2' }] as OntimeRundown,
}; };
const mergedData = safeMerge(existing, newData); const mergedData = safeMerge(existing, newData);
expect(mergedData.rundown).toEqual(newData.rundown); expect(mergedData.rundown).toEqual(newData.rundown);
@@ -64,9 +67,11 @@ describe('safeMerge', () => {
publicInfo: 'new public info', publicInfo: 'new public info',
}, },
}; };
// @ts-expect-error -- just testing
const mergedData = safeMerge(existing, newData); const mergedData = safeMerge(existing, newData);
expect(mergedData.project).toEqual({ expect(mergedData.project).toEqual({
title: 'new title', title: 'new title',
description: 'existing description',
publicUrl: 'existing public URL', publicUrl: 'existing public URL',
publicInfo: 'new public info', publicInfo: 'new public info',
backstageUrl: 'existing backstageUrl', backstageUrl: 'existing backstageUrl',
@@ -79,7 +84,7 @@ describe('safeMerge', () => {
settings: { settings: {
serverPort: 3000, serverPort: 3000,
language: 'pt', language: 'pt',
}, } as Settings,
}; };
const mergedData = safeMerge(existing, newData); const mergedData = safeMerge(existing, newData);
expect(mergedData.settings).toEqual({ expect(mergedData.settings).toEqual({
@@ -108,6 +113,7 @@ describe('safeMerge', () => {
}, },
}, },
}; };
//@ts-expect-error -- testing partial merge
const mergedData = safeMerge(existing, newData); const mergedData = safeMerge(existing, newData);
expect(mergedData.osc).toEqual({ expect(mergedData.osc).toEqual({
portIn: 7777, portIn: 7777,
@@ -135,7 +141,7 @@ describe('safeMerge', () => {
it('should merge the aliases key when present', () => { it('should merge the aliases key when present', () => {
const existingData = { const existingData = {
rundown: [], rundown: [],
event: { project: {
title: '', title: '',
publicUrl: '', publicUrl: '',
publicInfo: '', publicInfo: '',
@@ -183,10 +189,13 @@ describe('safeMerge', () => {
onFinish: [], onFinish: [],
}, },
}, },
}; } as DatabaseModel;
const newData = { const newData = {
aliases: ['alias1', 'alias2'], aliases: [
{ enabled: true, alias: 'alias1', pathAndParams: '' },
{ enabled: true, alias: 'alias2', pathAndParams: '' },
] as Alias[],
}; };
const mergedData = safeMerge(existingData, newData); const mergedData = safeMerge(existingData, newData);
@@ -217,6 +226,7 @@ describe('safeMerge', () => {
user3: 'David', user3: 'David',
}; };
//@ts-expect-error -- testing partial merge
const result = safeMerge(existing, newData); const result = safeMerge(existing, newData);
expect(result.userFields).toEqual(expected); expect(result.userFields).toEqual(expected);
}); });
@@ -34,13 +34,13 @@ describe('mergeObject()', () => {
third: 'yes', third: 'yes',
}; };
const b = { const b = {
first: 0, first: 'no',
second: null, second: null,
third: '', third: '',
}; };
const merged = mergeObject(a, b); const merged = mergeObject(a, b);
expect(merged).toStrictEqual({ expect(merged).toStrictEqual({
first: 0, first: 'no',
second: null, second: null,
third: '', third: '',
}); });
@@ -57,6 +57,7 @@ describe('mergeObject()', () => {
third: '', third: '',
forth: 'not-this', forth: 'not-this',
}; };
// @ts-expect-error -- testing changing type
const merged = mergeObject(a, b); const merged = mergeObject(a, b);
expect(merged).toStrictEqual({ expect(merged).toStrictEqual({
first: 0, first: 0,
@@ -83,6 +84,7 @@ describe('mergeObject()', () => {
}, },
}; };
// @ts-expect-error -- testing missing property
const merged = mergeObject(a, b); const merged = mergeObject(a, b);
expect(merged.name).toBe('Doe'); expect(merged.name).toBe('Doe');