Attach permission variables to all queries (#1000)

* [SALEOR-2190] Attach permission variables to all queries

* Fix TS linter issues

* Update package-lock
This commit is contained in:
Jakub Majorek 2021-03-09 09:44:09 +01:00 committed by GitHub
parent 2cd4ea9529
commit a7736e2bf9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 28496 additions and 968 deletions

View file

@ -21,8 +21,15 @@
"default": "array-simple" "default": "array-simple"
} }
], ],
"@typescript-eslint/ban-types": "error", "@typescript-eslint/ban-types": [
"@typescript-eslint/class-name-casing": "error", "error",
{
"extendDefaults": true,
"types": {
"{}": false
}
}
],
"@typescript-eslint/consistent-type-assertions": "error", "@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/consistent-type-definitions": "error", "@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/explicit-member-accessibility": "off", "@typescript-eslint/explicit-member-accessibility": "off",
@ -128,7 +135,7 @@
"no-multiple-empty-lines": "off", "no-multiple-empty-lines": "off",
"no-new-func": "error", "no-new-func": "error",
"no-new-wrappers": "error", "no-new-wrappers": "error",
"no-redeclare": "error", "no-redeclare": "off",
"no-return-await": "error", "no-return-await": "error",
"no-sequences": "error", "no-sequences": "error",
"no-shadow": [ "no-shadow": [
@ -155,7 +162,7 @@
"radix": "error", "radix": "error",
"simple-import-sort/sort": ["error"], "simple-import-sort/sort": ["error"],
"sort-imports": "off", // imports are handled by simple-import-sort/sort "sort-imports": "off", // imports are handled by simple-import-sort/sort
"sort-keys": "warn", "sort-keys": "off",
"space-before-function-paren": "off", "space-before-function-paren": "off",
"spaced-comment": "error", "spaced-comment": "error",
"use-isnan": "error", "use-isnan": "error",

View file

@ -1879,10 +1879,6 @@
"src_dot_components_dot_ErrorPage_dot_3182212440": { "src_dot_components_dot_ErrorPage_dot_3182212440": {
"string": "We've encountered a problem..." "string": "We've encountered a problem..."
}, },
"src_dot_components_dot_FileUpload_dot_3050254265": {
"context": "upload file, button",
"string": "Upload"
},
"src_dot_components_dot_FilterBar_dot_2173195312": { "src_dot_components_dot_FilterBar_dot_2173195312": {
"context": "button", "context": "button",
"string": "Delete Search" "string": "Delete Search"

29222
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -26,8 +26,8 @@
"@material-ui/icons": "^4.5.1", "@material-ui/icons": "^4.5.1",
"@material-ui/styles": "^4.5.2", "@material-ui/styles": "^4.5.2",
"@saleor/macaw-ui": "^0.1.1-9", "@saleor/macaw-ui": "^0.1.1-9",
"@types/faker": "^5.1.6",
"@sentry/react": "^6.0.0", "@sentry/react": "^6.0.0",
"@types/faker": "^5.1.6",
"apollo": "^2.21.2", "apollo": "^2.21.2",
"apollo-cache-inmemory": "^1.6.5", "apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.8", "apollo-client": "^2.6.8",
@ -45,7 +45,6 @@
"editorjs-undo": "^0.1.4", "editorjs-undo": "^0.1.4",
"faker": "^5.1.0", "faker": "^5.1.0",
"fast-array-diff": "^0.2.0", "fast-array-diff": "^0.2.0",
"fsevents": "^1.2.9",
"fuzzaldrin": "^2.1.0", "fuzzaldrin": "^2.1.0",
"graphql": "^14.4.2", "graphql": "^14.4.2",
"graphql-tag": "^2.11.0", "graphql-tag": "^2.11.0",
@ -76,7 +75,7 @@
"react-sortable-tree": "^2.6.2", "react-sortable-tree": "^2.6.2",
"semver-compare": "^1.0.0", "semver-compare": "^1.0.0",
"slugify": "^1.4.6", "slugify": "^1.4.6",
"typescript": "^3.9.7", "typescript": "^4.2.3",
"url-join": "^4.0.1", "url-join": "^4.0.1",
"use-react-router": "^1.0.7" "use-react-router": "^1.0.7"
}, },
@ -91,7 +90,7 @@
"@babel/plugin-proposal-optional-chaining": "^7.8.3", "@babel/plugin-proposal-optional-chaining": "^7.8.3",
"@babel/preset-env": "^7.5.4", "@babel/preset-env": "^7.5.4",
"@babel/preset-react": "^7.7.4", "@babel/preset-react": "^7.7.4",
"@babel/preset-typescript": "^7.7.4", "@babel/preset-typescript": "^7.13.0",
"@babel/runtime": "^7.7.6", "@babel/runtime": "^7.7.6",
"@pollyjs/adapter-node-http": "^5.0.0", "@pollyjs/adapter-node-http": "^5.0.0",
"@pollyjs/core": "^5.0.0", "@pollyjs/core": "^5.0.0",
@ -123,8 +122,8 @@
"@types/storybook__react": "^4.0.2", "@types/storybook__react": "^4.0.2",
"@types/url-join": "^4.0.0", "@types/url-join": "^4.0.0",
"@types/webappsec-credential-management": "^0.5.1", "@types/webappsec-credential-management": "^0.5.1",
"@typescript-eslint/eslint-plugin": "^2.12.0", "@typescript-eslint/eslint-plugin": "^4.16.1",
"@typescript-eslint/parser": "^2.9.0", "@typescript-eslint/parser": "^4.16.1",
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-jest": "^23.6.0", "babel-jest": "^23.6.0",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",

2
react-intl.d.ts vendored
View file

@ -13,7 +13,7 @@ declare module "react-intl" {
MessageDescriptor MessageDescriptor
>; >;
type PrimitiveType = string | number | boolean | null | undefined | Date; type PrimitiveType = string | number | boolean | null | undefined | Date;
type FormatXMLElementFn = (...args: any[]) => string | object; type FormatXMLElementFn = (...args: any[]) => string | {};
export interface IntlFormatters export interface IntlFormatters
extends Omit<ReactIntl.IntlFormatters, "formatMessage"> { extends Omit<ReactIntl.IntlFormatters, "formatMessage"> {
formatMessage( formatMessage(

View file

@ -1,61 +0,0 @@
import Button from "@material-ui/core/Button";
import { makeStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import React from "react";
import { FormattedMessage } from "react-intl";
const useStyles = makeStyles(
{
fileUploadField: {
display: "none"
},
root: {
display: "flex"
},
textField: {
flex: 1
}
},
{ name: "FileUpload" }
);
interface FileUploadProps {
disabled?: boolean;
name?: string;
value?: any;
onChange?(event: React.ChangeEvent<any>);
}
const FileUpload: React.FC<FileUploadProps> = props => {
const { disabled, name, value, onChange } = props;
const classes = useStyles(props);
return (
<div className={classes.root}>
<input
disabled={disabled}
name={name}
onChange={onChange}
ref={ref => (this.upload = ref)}
className={classes.fileUploadField}
type="file"
value={value}
/>
<TextField
className={classes.textField}
disabled={disabled}
onChange={undefined}
value={value}
/>
<Button disabled={disabled} onClick={() => this.upload.click()}>
<FormattedMessage
defaultMessage="Upload"
description="upload file, button"
/>
</Button>
</div>
);
};
FileUpload.displayName = "FileUpload";
export default FileUpload;

View file

@ -1,2 +0,0 @@
export { default } from "./FileUpload";
export * from "./FileUpload";

View file

@ -303,7 +303,7 @@ export const searchPageProps: SearchPageProps = {
onSearchChange: () => undefined onSearchChange: () => undefined
}; };
export const filterPageProps: FilterPageProps<string, object> = { export const filterPageProps: FilterPageProps<string, {}> = {
...searchPageProps, ...searchPageProps,
...tabPageProps, ...tabPageProps,
filterOpts: {}, filterOpts: {},

View file

@ -4,20 +4,28 @@ import gql from "graphql-tag";
import { Home, HomeVariables } from "./types/Home"; import { Home, HomeVariables } from "./types/Home";
const home = gql` const home = gql`
query Home($channel: String!) { query Home(
salesToday: ordersTotal(period: TODAY, channel: $channel) { $channel: String!
$PERMISSION_MANAGE_PRODUCTS: Boolean!
$PERMISSION_MANAGE_ORDERS: Boolean!
) {
salesToday: ordersTotal(period: TODAY, channel: $channel)
@include(if: $PERMISSION_MANAGE_ORDERS) {
gross { gross {
amount amount
currency currency
} }
} }
ordersToday: orders(created: TODAY, channel: $channel) { ordersToday: orders(created: TODAY, channel: $channel)
@include(if: $PERMISSION_MANAGE_ORDERS) {
totalCount totalCount
} }
ordersToFulfill: orders(status: READY_TO_FULFILL, channel: $channel) { ordersToFulfill: orders(status: READY_TO_FULFILL, channel: $channel)
@include(if: $PERMISSION_MANAGE_ORDERS) {
totalCount totalCount
} }
ordersToCapture: orders(status: READY_TO_CAPTURE, channel: $channel) { ordersToCapture: orders(status: READY_TO_CAPTURE, channel: $channel)
@include(if: $PERMISSION_MANAGE_ORDERS) {
totalCount totalCount
} }
productsOutOfStock: products( productsOutOfStock: products(
@ -30,7 +38,7 @@ const home = gql`
period: TODAY period: TODAY
first: 5 first: 5
channel: $channel channel: $channel
) { ) @include(if: $PERMISSION_MANAGE_PRODUCTS) {
edges { edges {
node { node {
id id
@ -57,7 +65,8 @@ const home = gql`
} }
} }
} }
activities: homepageEvents(last: 10) { activities: homepageEvents(last: 10)
@include(if: $PERMISSION_MANAGE_ORDERS) {
edges { edges {
node { node {
amount amount

View file

@ -136,4 +136,6 @@ export interface Home {
export interface HomeVariables { export interface HomeVariables {
channel: string; channel: string;
PERMISSION_MANAGE_PRODUCTS: boolean;
PERMISSION_MANAGE_ORDERS: boolean;
} }

View file

@ -26,10 +26,10 @@ const HomeSection = () => {
return ( return (
<HomePage <HomePage
activities={data?.activities.edges.map(edge => edge.node).reverse()} activities={data?.activities?.edges.map(edge => edge.node).reverse()}
orders={data?.ordersToday.totalCount} orders={data?.ordersToday?.totalCount}
sales={data?.salesToday.gross} sales={data?.salesToday?.gross}
topProducts={data?.productTopToday.edges.map(edge => edge.node)} topProducts={data?.productTopToday?.edges.map(edge => edge.node)}
onProductClick={(productId, variantId) => onProductClick={(productId, variantId) =>
navigate(productVariantEditUrl(productId, variantId)) navigate(productVariantEditUrl(productId, variantId))
} }
@ -57,8 +57,8 @@ const HomeSection = () => {
}) })
) )
} }
ordersToCapture={data?.ordersToCapture.totalCount} ordersToCapture={data?.ordersToCapture?.totalCount}
ordersToFulfill={data?.ordersToFulfill.totalCount} ordersToFulfill={data?.ordersToFulfill?.totalCount}
productsOutOfStock={data?.productsOutOfStock.totalCount} productsOutOfStock={data?.productsOutOfStock.totalCount}
userName={getUserName(user, true)} userName={getUserName(user, true)}
userPermissions={user?.userPermissions} userPermissions={user?.userPermissions}

View file

@ -6,10 +6,33 @@ import { useEffect } from "react";
import { QueryResult, useQuery as useBaseQuery } from "react-apollo"; import { QueryResult, useQuery as useBaseQuery } from "react-apollo";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { User_userPermissions } from "../fragments/types/User";
import { PrefixedPermissions } from "../types/extendedTypes";
import { PermissionEnum } from "../types/globalTypes";
import useAppState from "./useAppState"; import useAppState from "./useAppState";
import useNotifier from "./useNotifier"; import useNotifier from "./useNotifier";
import useUser from "./useUser"; import useUser from "./useUser";
const getPermissionKey = (permission: string) =>
`PERMISSION_${permission}` as PrefixedPermissions;
const allPermissions = Object.keys(PermissionEnum).reduce(
(prev, code) => ({
...prev,
[getPermissionKey(code)]: false
}),
{} as Record<PrefixedPermissions, boolean>
);
const getUserPermissions = (userPermissions: User_userPermissions[]) =>
userPermissions.reduce(
(prev, permission) => ({
...prev,
[getPermissionKey(permission.code)]: true
}),
{} as Record<PrefixedPermissions, boolean>
);
export interface LoadMore<TData, TVariables> { export interface LoadMore<TData, TVariables> {
loadMore: ( loadMore: (
mergeFunc: (prev: TData, next: TData) => TData, mergeFunc: (prev: TData, next: TData) => TData,
@ -25,7 +48,7 @@ type UseQueryOpts<TVariables> = Partial<{
variables: TVariables; variables: TVariables;
}>; }>;
type UseQueryHook<TData, TVariables> = ( type UseQueryHook<TData, TVariables> = (
opts: UseQueryOpts<TVariables> opts: UseQueryOpts<Omit<TVariables, PrefixedPermissions>>
) => UseQueryResult<TData, TVariables>; ) => UseQueryResult<TData, TVariables>;
function makeQuery<TData, TVariables>( function makeQuery<TData, TVariables>(
@ -40,6 +63,15 @@ function makeQuery<TData, TVariables>(
const intl = useIntl(); const intl = useIntl();
const [, dispatchAppState] = useAppState(); const [, dispatchAppState] = useAppState();
const user = useUser(); const user = useUser();
const userPermissions = getUserPermissions(
user.user?.userPermissions || []
);
const variablesWithPermissions = {
...variables,
...allPermissions,
...userPermissions
};
const queryData = useBaseQuery(query, { const queryData = useBaseQuery(query, {
context: { context: {
@ -56,7 +88,7 @@ function makeQuery<TData, TVariables>(
intl intl
), ),
skip, skip,
variables variables: variablesWithPermissions
}); });
useEffect(() => { useEffect(() => {
@ -82,7 +114,7 @@ function makeQuery<TData, TVariables>(
} }
return mergeFunc(previousResults, fetchMoreResult); return mergeFunc(previousResults, fetchMoreResult);
}, },
variables: { ...variables, ...extraVariables } variables: { ...variablesWithPermissions, ...extraVariables }
}); });
return { return {

View file

@ -3,16 +3,16 @@ import { removeAtIndex } from "@saleor/utils/lists";
import useStateFromProps from "./useStateFromProps"; import useStateFromProps from "./useStateFromProps";
export type FormsetChange<TValue = any> = (id: string, value: TValue) => void; export type FormsetChange<TValue = any> = (id: string, value: TValue) => void;
export interface FormsetAtomicData<TData = object, TValue = any> { export interface FormsetAtomicData<TData = {}, TValue = any> {
data: TData; data: TData;
id: string; id: string;
label: string; label: string;
value: TValue; value: TValue;
} }
export type FormsetData<TData = object, TValue = any> = Array< export type FormsetData<TData = {}, TValue = any> = Array<
FormsetAtomicData<TData, TValue> FormsetAtomicData<TData, TValue>
>; >;
export interface UseFormsetOutput<TData = object, TValue = any> { export interface UseFormsetOutput<TData = {}, TValue = any> {
add: (data: FormsetAtomicData<TData, TValue>) => void; add: (data: FormsetAtomicData<TData, TValue>) => void;
change: FormsetChange<TValue>; change: FormsetChange<TValue>;
data: FormsetData<TData, TValue>; data: FormsetData<TData, TValue>;
@ -21,7 +21,7 @@ export interface UseFormsetOutput<TData = object, TValue = any> {
set: (data: FormsetData<TData, TValue>) => void; set: (data: FormsetData<TData, TValue>) => void;
remove: (id: string) => void; remove: (id: string) => void;
} }
function useFormset<TData = object, TValue = any>( function useFormset<TData = {}, TValue = any>(
initial: FormsetData<TData, TValue> initial: FormsetData<TData, TValue>
): UseFormsetOutput<TData, TValue> { ): UseFormsetOutput<TData, TValue> {
const [data, setData] = useStateFromProps<FormsetData<TData, TValue>>( const [data, setData] = useStateFromProps<FormsetData<TData, TValue>>(

View file

@ -245,7 +245,7 @@ export function only<T>(obj: T, key: keyof T): boolean {
); );
} }
export function empty(obj: object): boolean { export function empty(obj: {}): boolean {
return Object.keys(obj).every(key => obj[key] === undefined); return Object.keys(obj).every(key => obj[key] === undefined);
} }
@ -376,10 +376,7 @@ export function generateCode(charNum: number) {
return result; return result;
} }
export function findInEnum<TEnum extends object>( export function findInEnum<TEnum extends {}>(needle: string, haystack: TEnum) {
needle: string,
haystack: TEnum
) {
const match = Object.keys(haystack).find(key => key === needle); const match = Object.keys(haystack).find(key => key === needle);
if (!!match) { if (!!match) {
return haystack[needle as keyof TEnum]; return haystack[needle as keyof TEnum];
@ -388,7 +385,7 @@ export function findInEnum<TEnum extends object>(
throw new Error(`Key ${needle} not found in enum`); throw new Error(`Key ${needle} not found in enum`);
} }
export function findValueInEnum<TEnum extends object>( export function findValueInEnum<TEnum extends {}>(
needle: string, needle: string,
haystack: TEnum haystack: TEnum
): TEnum[keyof TEnum] { ): TEnum[keyof TEnum] {

View file

@ -9,7 +9,6 @@ import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import { CSSProperties } from "@material-ui/styles";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Money from "@saleor/components/Money"; import Money from "@saleor/components/Money";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
@ -25,7 +24,7 @@ import { OrderRefundFormData } from "../OrderRefundPage/form";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => { theme => {
const inputPadding: CSSProperties = { const inputPadding = {
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
paddingTop: theme.spacing(2) paddingTop: theme.spacing(2)
}; };
@ -84,7 +83,7 @@ const OrderRefundFulfilledProducts: React.FC<OrderRefundFulfilledProductsProps>
onRefundedProductQuantityChange, onRefundedProductQuantityChange,
onSetMaximalQuantities onSetMaximalQuantities
} = props; } = props;
const classes = useStyles(props); const classes = useStyles({});
const intl = useIntl(); const intl = useIntl();
return ( return (

View file

@ -9,7 +9,6 @@ import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import { CSSProperties } from "@material-ui/styles";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Money from "@saleor/components/Money"; import Money from "@saleor/components/Money";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
@ -24,7 +23,7 @@ import { OrderRefundFormData } from "../OrderRefundPage/form";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => { theme => {
const inputPadding: CSSProperties = { const inputPadding = {
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
paddingTop: theme.spacing(2) paddingTop: theme.spacing(2)
}; };
@ -77,7 +76,7 @@ const OrderRefundUnfulfilledProducts: React.FC<OrderRefundUnfulfilledProductsPro
onRefundedProductQuantityChange, onRefundedProductQuantityChange,
onSetMaximalQuantities onSetMaximalQuantities
} = props; } = props;
const classes = useStyles(props); const classes = useStyles({});
const intl = useIntl(); const intl = useIntl();
return ( return (

View file

@ -1,10 +0,0 @@
import FileUpload from "@saleor/components/FileUpload";
import { storiesOf } from "@storybook/react";
import React from "react";
import Decorator from "../../Decorator";
storiesOf("Components / FileUpload", module)
.addDecorator(Decorator)
.add("default", () => <FileUpload />)
.add("other", () => <FileUpload />);

View file

@ -96,7 +96,7 @@ export interface SearchProps {
export interface SearchPageProps extends SearchProps { export interface SearchPageProps extends SearchProps {
initialSearch: string; initialSearch: string;
} }
export interface FilterPageProps<TKeys extends string, TOpts extends object> export interface FilterPageProps<TKeys extends string, TOpts extends {}>
extends FilterProps<TKeys>, extends FilterProps<TKeys>,
SearchPageProps, SearchPageProps,
TabPageProps { TabPageProps {

View file

@ -0,0 +1,3 @@
import { PermissionEnum } from "./globalTypes";
export type PrefixedPermissions = `PERMISSION_${PermissionEnum}`;

View file

@ -3,9 +3,9 @@ import { findValueInEnum } from "@saleor/misc";
import isArray from "lodash-es/isArray"; import isArray from "lodash-es/isArray";
function createFilterUtils< function createFilterUtils<
TQueryParams extends object, TQueryParams extends {},
TFilters extends object TFilters extends {}
>(filters: object) { >(filters: {}) {
function getActiveFilters(params: TQueryParams): TFilters { function getActiveFilters(params: TQueryParams): TFilters {
return Object.keys(params) return Object.keys(params)
.filter(key => Object.keys(filters).includes(key)) .filter(key => Object.keys(filters).includes(key))
@ -35,11 +35,11 @@ export function dedupeFilter<T>(array: T[]): T[] {
export type GetFilterQueryParam< export type GetFilterQueryParam<
TFilterKeys extends string, TFilterKeys extends string,
TFilters extends object TFilters extends {}
> = (filter: IFilterElement<TFilterKeys>, params?: object) => TFilters; > = (filter: IFilterElement<TFilterKeys>, params?: {}) => TFilters;
export function getFilterQueryParams< export function getFilterQueryParams<
TFilterKeys extends string, TFilterKeys extends string,
TUrlFilters extends object TUrlFilters extends {}
>( >(
filter: IFilter<TFilterKeys>, filter: IFilter<TFilterKeys>,
getFilterQueryParam: GetFilterQueryParam<TFilterKeys, TUrlFilters> getFilterQueryParam: GetFilterQueryParam<TFilterKeys, TUrlFilters>
@ -86,7 +86,7 @@ export function getSingleValueQueryParam<
export function getSingleEnumValueQueryParam< export function getSingleEnumValueQueryParam<
TKey extends string, TKey extends string,
TUrlKey extends string, TUrlKey extends string,
TEnum extends object TEnum extends {}
>(param: IFilterElement<TKey>, key: TUrlKey, haystack: TEnum) { >(param: IFilterElement<TKey>, key: TUrlKey, haystack: TEnum) {
const { active, value } = param; const { active, value } = param;
@ -104,7 +104,7 @@ export function getSingleEnumValueQueryParam<
export function getMultipleEnumValueQueryParam< export function getMultipleEnumValueQueryParam<
TKey extends string, TKey extends string,
TUrlKey extends string, TUrlKey extends string,
TEnum extends object TEnum extends {}
>(param: IFilterElement<TKey>, key: TUrlKey, haystack: TEnum) { >(param: IFilterElement<TKey>, key: TUrlKey, haystack: TEnum) {
const { active, value } = param; const { active, value } = param;

View file

@ -14,7 +14,7 @@ type CreateFilterHandlers<TFilterKeys extends string> = [
function createFilterHandlers< function createFilterHandlers<
TFilterKeys extends string, TFilterKeys extends string,
TFilters extends object TFilters extends {}
>(opts: { >(opts: {
getFilterQueryParam: GetFilterQueryParam<TFilterKeys, TFilters>; getFilterQueryParam: GetFilterQueryParam<TFilterKeys, TFilters>;
navigate: UseNavigatorResult; navigate: UseNavigatorResult;

View file

@ -16,7 +16,7 @@ describe("Multiple file upload handler", () => {
onStart: jest.fn() onStart: jest.fn()
}; };
const handle = createMultiFileUploadHandler(() => { const handle = createMultiFileUploadHandler(() => {
const promise = new Promise(resolve => { const promise = new Promise<void>(resolve => {
expect(cbs.onBeforeUpload).toBeCalledTimes( expect(cbs.onBeforeUpload).toBeCalledTimes(
cbs.onAfterUpload.mock.calls.length + 1 cbs.onAfterUpload.mock.calls.length + 1
); );
@ -46,7 +46,7 @@ describe("Multiple file upload handler", () => {
onStart: jest.fn() onStart: jest.fn()
}; };
const handle = createMultiFileUploadHandler((_, fileIndex) => { const handle = createMultiFileUploadHandler((_, fileIndex) => {
const promise = new Promise((resolve, reject) => { const promise = new Promise<void>((resolve, reject) => {
if (fileIndex === 2) { if (fileIndex === 2) {
reject(); reject();
} else { } else {

View file

@ -1,7 +1,7 @@
import isArray from "lodash-es/isArray"; import isArray from "lodash-es/isArray";
import { stringify } from "qs"; import { stringify } from "qs";
export function stringifyQs(params: object): string { export function stringifyQs(params: {}): string {
return stringify(params, { return stringify(params, {
indices: false indices: false
}); });

View file

@ -1,7 +1,7 @@
import { IFilter } from "@saleor/components/Filter"; import { IFilter } from "@saleor/components/Filter";
import clone from "lodash-es/clone"; import clone from "lodash-es/clone";
export function getExistingKeys(o: object): string[] { export function getExistingKeys(o: {}): string[] {
return Object.keys(o).filter(key => o[key] !== undefined && o[key] !== null); return Object.keys(o).filter(key => o[key] !== undefined && o[key] !== null);
} }