Skip to content
Snippets Groups Projects
Unverified Commit 5d28bc0e authored by Matiss Janis Aboltins's avatar Matiss Janis Aboltins Committed by GitHub
Browse files

:label: making some files comply with strict TS (#2247)

parent a4e97e00
No related branches found
No related tags found
No related merge requests found
Showing
with 112 additions and 142 deletions
// @ts-strict-ignore
import React from 'react';
import { useSelector } from 'react-redux';
......@@ -11,14 +10,6 @@ import { LinkButton } from './common/LinkButton';
import { Text } from './common/Text';
import { View } from './common/View';
function closeNotification(setAppState) {
// Set a flag to never show an update notification again for this session
setAppState({
updateInfo: null,
showUpdateNotification: false,
});
}
export function UpdateNotification() {
const updateInfo = useSelector(state => state.app.updateInfo);
const showUpdateNotification = useSelector(
......@@ -68,7 +59,7 @@ export function UpdateNotification() {
textDecoration: 'underline',
}}
onClick={() =>
window.Actual.openURLInBrowser(
window.Actual?.openURLInBrowser(
'https://actualbudget.org/docs/releases',
)
}
......@@ -80,7 +71,13 @@ export function UpdateNotification() {
type="bare"
aria-label="Close"
style={{ display: 'inline', padding: '1px 7px 2px 7px' }}
onClick={() => closeNotification(setAppState)}
onClick={() => {
// Set a flag to never show an update notification again for this session
setAppState({
updateInfo: null,
showUpdateNotification: false,
});
}}
>
<SvgClose
width={9}
......
......@@ -691,7 +691,7 @@ function MultiAutocomplete<T extends Item>({
type AutocompleteFooterProps = {
show?: boolean;
embedded: boolean;
embedded?: boolean;
children: ReactNode;
};
export function AutocompleteFooter({
......@@ -699,18 +699,20 @@ export function AutocompleteFooter({
embedded,
children,
}: AutocompleteFooterProps) {
if (!show) {
return null;
}
return (
show && (
<View
style={{
flexShrink: 0,
...(embedded ? { paddingTop: 5 } : { padding: 5 }),
}}
onMouseDown={e => e.preventDefault()}
>
{children}
</View>
)
<View
style={{
flexShrink: 0,
...(embedded ? { paddingTop: 5 } : { padding: 5 }),
}}
onMouseDown={e => e.preventDefault()}
>
{children}
</View>
);
}
......
// @ts-strict-ignore
import React, {
type ComponentProps,
Fragment,
......@@ -25,9 +24,11 @@ import { Autocomplete, defaultFilterSuggestion } from './Autocomplete';
export type CategoryListProps = {
items: Array<CategoryEntity & { group?: CategoryGroupEntity }>;
getItemProps?: (arg: { item }) => Partial<ComponentProps<typeof View>>;
getItemProps?: (arg: {
item: CategoryEntity;
}) => Partial<ComponentProps<typeof View>>;
highlightedIndex: number;
embedded: boolean;
embedded?: boolean;
footer?: ReactNode;
renderSplitTransactionButton?: (
props: SplitTransactionButtonProps,
......@@ -47,7 +48,7 @@ function CategoryList({
renderCategoryItemGroupHeader = defaultRenderCategoryItemGroupHeader,
renderCategoryItem = defaultRenderCategoryItem,
}: CategoryListProps) {
let lastGroup = null;
let lastGroup: string | undefined | null = null;
return (
<View>
......@@ -72,10 +73,10 @@ function CategoryList({
lastGroup = item.cat_group;
return (
<Fragment key={item.id}>
{showGroup && (
<Fragment key={item.group?.name}>
{showGroup && item.group?.name && (
<Fragment key={item.group.name}>
{renderCategoryItemGroupHeader({
title: item.group?.name,
title: item.group.name,
})}
</Fragment>
)}
......@@ -125,7 +126,7 @@ export function CategoryAutocomplete({
categoryGroups.reduce(
(list, group) =>
list.concat(
group.categories
(group.categories || [])
.filter(category => category.cat_group === group.id)
.map(category => ({
...category,
......@@ -214,8 +215,7 @@ type SplitTransactionButtonProps = {
style?: CSSProperties;
};
// eslint-disable-next-line import/no-unused-modules
export function SplitTransactionButton({
function SplitTransactionButton({
Icon,
highlighted,
embedded,
......
// @ts-strict-ignore
import { APIError } from '../../../server/errors';
import { runHandler, isMutating } from '../../../server/mutators';
import { captureException } from '../../exceptions';
......@@ -70,7 +71,7 @@ export const init: T.Init = function (_socketName, handlers) {
type: 'reply',
id,
result: null,
error: { type: 'APIError', message: 'Unknown method: ' + name },
error: APIError('Unknown method: ' + name),
});
}
});
......
// @ts-strict-ignore
import { APIError } from '../../../server/errors';
import { runHandler, isMutating } from '../../../server/mutators';
import { captureException } from '../../exceptions';
......@@ -90,7 +91,7 @@ export const init: T.Init = function (serverChn, handlers) {
type: 'reply',
id,
result: null,
error: { type: 'APIError', message: 'Unknown method: ' + name },
error: APIError('Unknown method: ' + name),
});
}
},
......
......@@ -28,6 +28,7 @@ import {
import { runQuery as aqlQuery } from './aql';
import * as cloudStorage from './cloud-storage';
import * as db from './db';
import { APIError } from './errors';
import { runMutator } from './mutators';
import * as prefs from './prefs';
import * as sheet from './sheet';
......@@ -35,11 +36,6 @@ import { setSyncingMode, batchMessages } from './sync';
let IMPORT_MODE = false;
// This is duplicate from main.js...
function APIError(msg, meta?) {
return { type: 'APIError', message: msg, meta };
}
// The API is different in two ways: we never want undo enabled, and
// we also need to notify the UI manually if stuff has changed (if
// they are connecting to an already running instance, the UI should
......
// @ts-strict-ignore
export const SORT_INCREMENT = 16384;
function midpoint(items, to) {
function midpoint<T extends { sort_order: number }>(items: T[], to: number) {
const below = items[to - 1];
const above = items[to];
......@@ -14,11 +13,14 @@ function midpoint(items, to) {
}
}
export function shoveSortOrders(items, targetId?: string) {
export function shoveSortOrders<T extends { id: string; sort_order: number }>(
items: T[],
targetId?: string,
) {
const to = items.findIndex(item => item.id === targetId);
const target = items[to];
const before = items[to - 1];
const updates = [];
const updates: Array<{ id: string; sort_order: number }> = [];
// If no target is specified, append at the end
if (!targetId || to === -1) {
......
// @ts-strict-ignore
// TODO: normalize error types
export class PostError extends Error {
meta;
reason;
type;
meta?: { meta: string };
reason: string;
type: 'PostError';
constructor(reason, meta?) {
constructor(reason: string, meta?: { meta: string }) {
super('PostError: ' + reason);
this.type = 'PostError';
this.reason = reason;
......@@ -14,10 +13,10 @@ export class PostError extends Error {
}
export class HTTPError extends Error {
statusCode;
responseBody;
statusCode: number;
responseBody: string;
constructor(code, body) {
constructor(code: number, body: string) {
super(`HTTPError: unsuccessful status code (${code}): ${body}`);
this.statusCode = code;
this.responseBody = body;
......@@ -25,10 +24,27 @@ export class HTTPError extends Error {
}
export class SyncError extends Error {
meta;
reason;
meta?:
| {
isMissingKey: boolean;
}
| {
error: { message: string; stack: string };
query: { sql: string; params: Array<string | number> };
};
reason: string;
constructor(reason, meta?) {
constructor(
reason: string,
meta?:
| {
isMissingKey: boolean;
}
| {
error: { message: string; stack: string };
query: { sql: string; params: Array<string | number> };
},
) {
super('SyncError: ' + reason);
this.reason = reason;
this.meta = meta;
......@@ -46,14 +62,20 @@ export class RuleError extends Error {
}
}
export function APIError(msg, meta?) {
return { type: 'APIError', message: msg, meta };
export function APIError(msg: string) {
return { type: 'APIError', message: msg };
}
export function FileDownloadError(reason, meta?) {
export function FileDownloadError(
reason: string,
meta?: { fileId?: string; isMissingKey?: boolean },
) {
return { type: 'FileDownloadError', reason, meta };
}
export function FileUploadError(reason, meta?) {
export function FileUploadError(
reason: string,
meta?: { isMissingKey: boolean },
) {
return { type: 'FileUploadError', reason, meta };
}
// @ts-strict-ignore
export function requiredFields(name, row, fields, update) {
import {
AccountEntity,
CategoryEntity,
CategoryGroupEntity,
PayeeEntity,
} from '../types/models';
export function requiredFields<T extends object, K extends keyof T>(
name: string,
row: T,
fields: K[],
update?: boolean,
) {
fields.forEach(field => {
if (update) {
if (row.hasOwnProperty(field) && row[field] == null) {
throw new Error(`${name} is missing field ${field}`);
throw new Error(`${name} is missing field ${String(field)}`);
}
} else {
if (!row.hasOwnProperty(field) || row[field] == null) {
throw new Error(`${name} is missing field ${field}`);
throw new Error(`${name} is missing field ${String(field)}`);
}
}
});
}
export function toDateRepr(str) {
export function toDateRepr(str: string) {
if (typeof str !== 'string') {
throw new Error('toDateRepr not passed a string: ' + str);
}
......@@ -21,7 +32,7 @@ export function toDateRepr(str) {
return parseInt(str.replace(/-/g, ''));
}
export function fromDateRepr(number) {
export function fromDateRepr(number: number) {
if (typeof number !== 'number') {
throw new Error('fromDateRepr not passed a number: ' + number);
}
......@@ -37,7 +48,7 @@ export function fromDateRepr(number) {
}
export const accountModel = {
validate(account, { update }: { update?: boolean } = {}) {
validate(account: AccountEntity, { update }: { update?: boolean } = {}) {
requiredFields(
'account',
account,
......@@ -50,7 +61,7 @@ export const accountModel = {
};
export const categoryModel = {
validate(category, { update }: { update?: boolean } = {}) {
validate(category: CategoryEntity, { update }: { update?: boolean } = {}) {
requiredFields(
'category',
category,
......@@ -64,7 +75,10 @@ export const categoryModel = {
};
export const categoryGroupModel = {
validate(categoryGroup, { update }: { update?: boolean } = {}) {
validate(
categoryGroup: CategoryGroupEntity,
{ update }: { update?: boolean } = {},
) {
requiredFields(
'categoryGroup',
categoryGroup,
......@@ -78,78 +92,8 @@ export const categoryGroupModel = {
};
export const payeeModel = {
validate(payee, { update }: { update?: boolean } = {}) {
validate(payee: PayeeEntity, { update }: { update?: boolean } = {}) {
requiredFields('payee', payee, ['name'], update);
return payee;
},
};
export const transactionModel = {
validate(trans, { update }: { update?: boolean } = {}) {
requiredFields('transaction', trans, ['date', 'acct'], update);
if ('date' in trans) {
// Make sure it's the right format, and also do a sanity check.
// Really old dates can mess up the system and can happen by
// accident
if (
trans.date.match(/^\d{4}-\d{2}-\d{2}$/) == null ||
trans.date < '2000-01-01'
) {
throw new Error('Invalid transaction date: ' + trans.date);
}
}
return trans;
},
toJS(row) {
// Check a non-important field that typically wouldn't be passed in
// manually, and use it as a smoke test to see if this is a
// fully-formed transaction or not.
if (!('location' in row)) {
throw new Error(
'A full transaction is required to be passed to `toJS`. Instead got: ' +
JSON.stringify(row),
);
}
const trans = { ...row };
trans.error = row.error ? JSON.parse(row.error) : null;
trans.isParent = row.isParent === 1 ? true : false;
trans.isChild = row.isChild === 1 ? true : false;
trans.starting_balance_flag =
row.starting_balance_flag === 1 ? true : false;
trans.cleared = row.cleared === 1 ? true : false;
trans.pending = row.pending === 1 ? true : false;
trans.date = trans.date && fromDateRepr(trans.date);
return trans;
},
fromJS(trans) {
const row = { ...trans };
if ('error' in row) {
row.error = trans.error ? JSON.stringify(trans.error) : null;
}
if ('isParent' in row) {
row.isParent = trans.isParent ? 1 : 0;
}
if ('isChild' in row) {
row.isChild = trans.isChild ? 1 : 0;
}
if ('cleared' in row) {
row.cleared = trans.cleared ? 1 : 0;
}
if ('pending' in row) {
row.pending = trans.pending ? 1 : 0;
}
if ('starting_balance_flag' in row) {
row.starting_balance_flag = trans.starting_balance_flag ? 1 : 0;
}
if ('date' in row) {
row.date = toDateRepr(trans.date);
}
return row;
},
};
......@@ -15,7 +15,7 @@ import { ReportsHandlers } from './types/handlers';
const reportModel = {
validate(report: CustomReportEntity, { update }: { update?: boolean } = {}) {
requiredFields('reports', report, ['conditions'], update);
requiredFields('reports', report, ['conditionsOp'], update);
if (!update || 'conditionsOp' in report) {
if (!['and', 'or'].includes(report.conditionsOp)) {
......
......@@ -142,8 +142,7 @@ async function fetchAll(table, ids) {
message: error.message,
stack: error.stack,
},
sql,
params: partIds,
query: { sql, params: partIds },
});
}
}
......
---
category: Maintenance
authors: [MatissJanis]
---
TypeScript: making some files comply with strict TS.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment