mirror of
https://github.com/apache/superset.git
synced 2026-05-04 23:44:23 +00:00
Compare commits
2 Commits
docs/testi
...
ts_migrate
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c92a78ab35 | ||
|
|
04ba0a00b3 |
@@ -35,7 +35,6 @@ import {
|
|||||||
SaveDatasetModal,
|
SaveDatasetModal,
|
||||||
ISaveableDatasource,
|
ISaveableDatasource,
|
||||||
} from 'src/SqlLab/components/SaveDatasetModal';
|
} from 'src/SqlLab/components/SaveDatasetModal';
|
||||||
import { getDatasourceAsSaveableDataset } from 'src/utils/datasourceUtils';
|
|
||||||
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
|
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
|
||||||
import { QueryEditor } from 'src/SqlLab/types';
|
import { QueryEditor } from 'src/SqlLab/types';
|
||||||
import useLogAction from 'src/logger/useLogAction';
|
import useLogAction from 'src/logger/useLogAction';
|
||||||
@@ -215,7 +214,15 @@ const SaveQuery = ({
|
|||||||
onHide={() => setShowSaveDatasetModal(false)}
|
onHide={() => setShowSaveDatasetModal(false)}
|
||||||
buttonTextOnSave={t('Save & Explore')}
|
buttonTextOnSave={t('Save & Explore')}
|
||||||
buttonTextOnOverwrite={t('Overwrite & Explore')}
|
buttonTextOnOverwrite={t('Overwrite & Explore')}
|
||||||
datasource={getDatasourceAsSaveableDataset(query)}
|
datasource={{
|
||||||
|
columns,
|
||||||
|
name: query.name || query.description || t('Undefined'),
|
||||||
|
dbId: query.dbId || 0,
|
||||||
|
sql: query.sql || '',
|
||||||
|
catalog: query.catalog,
|
||||||
|
schema: query.schema,
|
||||||
|
templateParams: query.templateParams,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
className="save-query-modal"
|
className="save-query-modal"
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export function MarshmallowErrorMessage({
|
|||||||
children: (
|
children: (
|
||||||
<JSONTree
|
<JSONTree
|
||||||
data={extra.messages}
|
data={extra.messages}
|
||||||
shouldExpandNodeInitially={() => true}
|
shouldExpandNode={() => true}
|
||||||
hideRoot
|
hideRoot
|
||||||
theme={jsonTreeTheme}
|
theme={jsonTreeTheme}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const CopyToClipboardButton = ({
|
|||||||
}) => (
|
}) => (
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
text={
|
text={
|
||||||
data && columns ? prepareCopyToClipboardTabularData(data, columns) : ''
|
data && columns ? prepareCopyToClipboardTabularData([data], columns) : ''
|
||||||
}
|
}
|
||||||
wrapped={false}
|
wrapped={false}
|
||||||
copyNode={
|
copyNode={
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ export const TableControls = ({
|
|||||||
name &&
|
name &&
|
||||||
!originalTimeColumns.includes(name),
|
!originalTimeColumns.includes(name),
|
||||||
)
|
)
|
||||||
.map(([colname]) => colname);
|
.map(([colname]) => colname)
|
||||||
|
.filter((colname): colname is string => colname !== undefined);
|
||||||
const formattedData = useMemo(
|
const formattedData = useMemo(
|
||||||
() => applyFormattingToTabularData(data, formattedTimeColumns),
|
() => applyFormattingToTabularData(data, formattedTimeColumns),
|
||||||
[data, formattedTimeColumns],
|
[data, formattedTimeColumns],
|
||||||
|
|||||||
@@ -18,33 +18,49 @@
|
|||||||
*/
|
*/
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
|
interface DebouncedMessageQueueConfig {
|
||||||
|
callback?: (events: any[]) => void;
|
||||||
|
sizeThreshold?: number;
|
||||||
|
delayThreshold?: number;
|
||||||
|
}
|
||||||
|
|
||||||
class DebouncedMessageQueue {
|
class DebouncedMessageQueue {
|
||||||
|
private queue: any[];
|
||||||
|
|
||||||
|
private sizeThreshold: number;
|
||||||
|
|
||||||
|
private delayThreshold: number;
|
||||||
|
|
||||||
|
private callback: (events: any[]) => void;
|
||||||
|
|
||||||
|
private trigger: () => void;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
callback = () => {},
|
callback = () => {},
|
||||||
sizeThreshold = 1000,
|
sizeThreshold = 1000,
|
||||||
delayThreshold = 1000,
|
delayThreshold = 1000,
|
||||||
}) {
|
}: DebouncedMessageQueueConfig = {}) {
|
||||||
this.queue = [];
|
this.queue = [];
|
||||||
this.sizeThreshold = sizeThreshold;
|
this.sizeThreshold = sizeThreshold;
|
||||||
this.delayThreshold = delayThreshold;
|
this.delayThreshold = delayThreshold;
|
||||||
|
|
||||||
this.trigger = debounce(this.trigger.bind(this), this.delayThreshold);
|
this.trigger = debounce(this.triggerQueue.bind(this), this.delayThreshold);
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
append(eventData) {
|
append(eventData: any): void {
|
||||||
this.queue.push(eventData);
|
this.queue.push(eventData);
|
||||||
this.trigger();
|
this.trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger() {
|
private triggerQueue(): void {
|
||||||
if (this.queue.length > 0) {
|
if (this.queue.length > 0) {
|
||||||
const events = this.queue.splice(0, this.sizeThreshold);
|
const events = this.queue.splice(0, this.sizeThreshold);
|
||||||
this.callback.call(null, events);
|
this.callback.call(null, events);
|
||||||
|
|
||||||
// If there are remaining items, call it again.
|
// If there are remaining items, call it again.
|
||||||
if (this.queue.length > 0) {
|
if (this.queue.length > 0) {
|
||||||
this.trigger();
|
this.triggerQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,8 +50,8 @@ describe('utils/common', () => {
|
|||||||
});
|
});
|
||||||
describe('prepareCopyToClipboardTabularData', () => {
|
describe('prepareCopyToClipboardTabularData', () => {
|
||||||
it('converts empty array', () => {
|
it('converts empty array', () => {
|
||||||
const data = [];
|
const data: any[] = [];
|
||||||
const columns = [];
|
const columns: any[] = [];
|
||||||
expect(prepareCopyToClipboardTabularData(data, columns)).toEqual('');
|
expect(prepareCopyToClipboardTabularData(data, columns)).toEqual('');
|
||||||
});
|
});
|
||||||
it('converts non empty array', () => {
|
it('converts non empty array', () => {
|
||||||
@@ -77,7 +77,7 @@ describe('utils/common', () => {
|
|||||||
});
|
});
|
||||||
describe('applyFormattingToTabularData', () => {
|
describe('applyFormattingToTabularData', () => {
|
||||||
it('does not mutate empty array', () => {
|
it('does not mutate empty array', () => {
|
||||||
const data = [];
|
const data: any[] = [];
|
||||||
expect(applyFormattingToTabularData(data, [])).toEqual(data);
|
expect(applyFormattingToTabularData(data, [])).toEqual(data);
|
||||||
});
|
});
|
||||||
it('does not mutate array without temporal column', () => {
|
it('does not mutate array without temporal column', () => {
|
||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
getTimeFormatter,
|
getTimeFormatter,
|
||||||
TimeFormats,
|
TimeFormats,
|
||||||
ensureIsArray,
|
ensureIsArray,
|
||||||
|
JsonObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
|
|
||||||
// ATTENTION: If you change any constants, make sure to also change constants.py
|
// ATTENTION: If you change any constants, make sure to also change constants.py
|
||||||
@@ -36,7 +37,7 @@ export const SHORT_TIME = 'h:m a';
|
|||||||
|
|
||||||
const DATETIME_FORMATTER = getTimeFormatter(TimeFormats.DATABASE_DATETIME);
|
const DATETIME_FORMATTER = getTimeFormatter(TimeFormats.DATABASE_DATETIME);
|
||||||
|
|
||||||
export function storeQuery(query) {
|
export function storeQuery(query: JsonObject): Promise<string> {
|
||||||
return SupersetClient.post({
|
return SupersetClient.post({
|
||||||
endpoint: '/kv/store/',
|
endpoint: '/kv/store/',
|
||||||
postPayload: { data: query },
|
postPayload: { data: query },
|
||||||
@@ -47,7 +48,7 @@ export function storeQuery(query) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function optionLabel(opt) {
|
export function optionLabel(opt: any): string {
|
||||||
if (opt === null) {
|
if (opt === null) {
|
||||||
return NULL_STRING;
|
return NULL_STRING;
|
||||||
}
|
}
|
||||||
@@ -66,28 +67,31 @@ export function optionLabel(opt) {
|
|||||||
return opt;
|
return opt;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function optionValue(opt) {
|
export function optionValue(opt: any): any {
|
||||||
if (opt === null) {
|
if (opt === null) {
|
||||||
return NULL_STRING;
|
return NULL_STRING;
|
||||||
}
|
}
|
||||||
return opt;
|
return opt;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function optionFromValue(opt) {
|
export function optionFromValue(opt: any): { value: any; label: string } {
|
||||||
// From a list of options, handles special values & labels
|
// From a list of options, handles special values & labels
|
||||||
return { value: optionValue(opt), label: optionLabel(opt) };
|
return { value: optionValue(opt), label: optionLabel(opt) };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getColumnName(column) {
|
function getColumnName(column: any): string {
|
||||||
return column.name || column;
|
return column.name || column;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prepareCopyToClipboardTabularData(data, columns) {
|
export function prepareCopyToClipboardTabularData(
|
||||||
|
data: JsonObject[],
|
||||||
|
columns: any[],
|
||||||
|
): string {
|
||||||
let result = columns.length
|
let result = columns.length
|
||||||
? `${columns.map(getColumnName).join('\t')}\n`
|
? `${columns.map(getColumnName).join('\t')}\n`
|
||||||
: '';
|
: '';
|
||||||
for (let i = 0; i < data.length; i += 1) {
|
for (let i = 0; i < data.length; i += 1) {
|
||||||
const row = {};
|
const row: Record<number, any> = {};
|
||||||
for (let j = 0; j < columns.length; j += 1) {
|
for (let j = 0; j < columns.length; j += 1) {
|
||||||
// JavaScript does not maintain the order of a mixed set of keys (i.e integers and strings)
|
// JavaScript does not maintain the order of a mixed set of keys (i.e integers and strings)
|
||||||
// the below function orders the keys based on the column names.
|
// the below function orders the keys based on the column names.
|
||||||
@@ -103,7 +107,10 @@ export function prepareCopyToClipboardTabularData(data, columns) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyFormattingToTabularData(data, timeFormattedColumns) {
|
export function applyFormattingToTabularData(
|
||||||
|
data: JsonObject[],
|
||||||
|
timeFormattedColumns: string[],
|
||||||
|
): JsonObject[] {
|
||||||
if (
|
if (
|
||||||
!data ||
|
!data ||
|
||||||
data.length === 0 ||
|
data.length === 0 ||
|
||||||
@@ -115,19 +122,22 @@ export function applyFormattingToTabularData(data, timeFormattedColumns) {
|
|||||||
return data.map(row => ({
|
return data.map(row => ({
|
||||||
...row,
|
...row,
|
||||||
/* eslint-disable no-underscore-dangle */
|
/* eslint-disable no-underscore-dangle */
|
||||||
...timeFormattedColumns.reduce((acc, colName) => {
|
...timeFormattedColumns.reduce(
|
||||||
if (row[colName] !== null && row[colName] !== undefined) {
|
(acc, colName) => {
|
||||||
acc[colName] = DATETIME_FORMATTER(row[colName]);
|
if (row[colName] !== null && row[colName] !== undefined) {
|
||||||
}
|
acc[colName] = DATETIME_FORMATTER(row[colName]);
|
||||||
return acc;
|
}
|
||||||
}, {}),
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, any>,
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const noOp = () => undefined;
|
export const noOp = (): undefined => undefined;
|
||||||
|
|
||||||
// Detects the user's OS through the browser
|
// Detects the user's OS through the browser
|
||||||
export const detectOS = () => {
|
export const detectOS = (): string => {
|
||||||
const { appVersion } = navigator;
|
const { appVersion } = navigator;
|
||||||
|
|
||||||
// Leveraging this condition because of stackOverflow
|
// Leveraging this condition because of stackOverflow
|
||||||
@@ -140,8 +150,8 @@ export const detectOS = () => {
|
|||||||
return 'Unknown OS';
|
return 'Unknown OS';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isSafari = () => {
|
export const isSafari = (): boolean => {
|
||||||
const { userAgent } = navigator;
|
const { userAgent } = navigator;
|
||||||
|
|
||||||
return userAgent && /^((?!chrome|android).)*safari/i.test(userAgent);
|
return Boolean(userAgent && /^((?!chrome|android).)*safari/i.test(userAgent));
|
||||||
};
|
};
|
||||||
@@ -16,12 +16,22 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
export const getDatasourceAsSaveableDataset = source => ({
|
import { Dataset } from '@superset-ui/chart-controls';
|
||||||
columns: source.columns,
|
import { ISaveableDatasource } from '../SqlLab/components/SaveDatasetModal';
|
||||||
|
|
||||||
|
export const getDatasourceAsSaveableDataset = (
|
||||||
|
source: Partial<Dataset> | any,
|
||||||
|
): ISaveableDatasource => ({
|
||||||
|
columns: (source.columns || []).map((col: any) => ({
|
||||||
|
column_name: col.column_name || col.name || '',
|
||||||
|
name: col.name || col.column_name || '',
|
||||||
|
type: col.type || null,
|
||||||
|
is_dttm: col.is_dttm || null,
|
||||||
|
})),
|
||||||
name: source?.datasource_name || source?.name || 'Untitled',
|
name: source?.datasource_name || source?.name || 'Untitled',
|
||||||
dbId: source?.database?.id || source?.dbId,
|
dbId: source?.database?.id || (source as any)?.dbId || 0,
|
||||||
sql: source?.sql || '',
|
sql: (source as any)?.sql || '',
|
||||||
catalog: source?.catalog,
|
catalog: source?.catalog,
|
||||||
schema: source?.schema,
|
schema: source?.schema,
|
||||||
templateParams: source?.templateParams,
|
templateParams: (source as any)?.templateParams,
|
||||||
});
|
});
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import memoizeOne from 'memoize-one';
|
|
||||||
import { isControlPanelSectionConfig } from '@superset-ui/chart-controls';
|
|
||||||
import { getChartControlPanelRegistry } from '@superset-ui/core';
|
|
||||||
import { controls } from '../explore/controls';
|
|
||||||
|
|
||||||
const memoizedControls = memoizeOne((vizType, controlPanel) => {
|
|
||||||
const controlsMap = {};
|
|
||||||
(controlPanel?.controlPanelSections || [])
|
|
||||||
.filter(isControlPanelSectionConfig)
|
|
||||||
.forEach(section => {
|
|
||||||
section.controlSetRows.forEach(row => {
|
|
||||||
row.forEach(control => {
|
|
||||||
if (!control) return;
|
|
||||||
if (typeof control === 'string') {
|
|
||||||
// For now, we have to look in controls.jsx to get the config for some controls.
|
|
||||||
// Once everything is migrated out, delete this if statement.
|
|
||||||
controlsMap[control] = controls[control];
|
|
||||||
} else if (control.name && control.config) {
|
|
||||||
// condition needed because there are elements, e.g. <hr /> in some control configs (I'm looking at you, FilterBox!)
|
|
||||||
controlsMap[control.name] = control.config;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return controlsMap;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getControlsForVizType = vizType => {
|
|
||||||
const controlPanel = getChartControlPanelRegistry().get(vizType);
|
|
||||||
return memoizedControls(vizType, controlPanel);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getControlsForVizType;
|
|
||||||
66
superset-frontend/src/utils/getControlsForVizType.ts
Normal file
66
superset-frontend/src/utils/getControlsForVizType.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import memoizeOne from 'memoize-one';
|
||||||
|
import {
|
||||||
|
isControlPanelSectionConfig,
|
||||||
|
ControlStateMapping,
|
||||||
|
ControlPanelConfig,
|
||||||
|
} from '@superset-ui/chart-controls';
|
||||||
|
import { getChartControlPanelRegistry } from '@superset-ui/core';
|
||||||
|
import { controls } from '../explore/controls';
|
||||||
|
|
||||||
|
const memoizedControls = memoizeOne(
|
||||||
|
(vizType: string, controlPanel: ControlPanelConfig): ControlStateMapping => {
|
||||||
|
const controlsMap: ControlStateMapping = {};
|
||||||
|
(controlPanel?.controlPanelSections || [])
|
||||||
|
.filter(isControlPanelSectionConfig)
|
||||||
|
.forEach(section => {
|
||||||
|
section.controlSetRows.forEach(row => {
|
||||||
|
row.forEach(control => {
|
||||||
|
if (!control) return;
|
||||||
|
if (typeof control === 'string') {
|
||||||
|
// For now, we have to look in controls.jsx to get the config for some controls.
|
||||||
|
// Once everything is migrated out, delete this if statement.
|
||||||
|
controlsMap[control] = (controls as any)[control];
|
||||||
|
} else if (
|
||||||
|
control &&
|
||||||
|
typeof control === 'object' &&
|
||||||
|
'name' in control &&
|
||||||
|
'config' in control
|
||||||
|
) {
|
||||||
|
// condition needed because there are elements, e.g. <hr /> in some control configs (I'm looking at you, FilterBox!)
|
||||||
|
controlsMap[control.name] = control.config;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return controlsMap;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const getControlsForVizType = (vizType: string): ControlStateMapping => {
|
||||||
|
const controlPanel = getChartControlPanelRegistry().get(vizType);
|
||||||
|
return memoizedControls(
|
||||||
|
vizType,
|
||||||
|
(controlPanel as ControlPanelConfig) || { controlPanelSections: [] },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getControlsForVizType;
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
import { initFeatureFlags } from '@superset-ui/core';
|
import { initFeatureFlags } from '@superset-ui/core';
|
||||||
import getBootstrapData from './getBootstrapData';
|
import getBootstrapData from './getBootstrapData';
|
||||||
|
|
||||||
function getDomainsConfig() {
|
function getDomainsConfig(): string[] {
|
||||||
const appContainer = document.getElementById('app');
|
const appContainer = document.getElementById('app');
|
||||||
if (!appContainer) {
|
if (!appContainer) {
|
||||||
return [];
|
return [];
|
||||||
@@ -42,13 +42,15 @@ function getDomainsConfig() {
|
|||||||
initFeatureFlags(bootstrapData.common.feature_flags);
|
initFeatureFlags(bootstrapData.common.feature_flags);
|
||||||
|
|
||||||
if (bootstrapData?.common?.conf?.SUPERSET_WEBSERVER_DOMAINS) {
|
if (bootstrapData?.common?.conf?.SUPERSET_WEBSERVER_DOMAINS) {
|
||||||
bootstrapData.common.conf.SUPERSET_WEBSERVER_DOMAINS.forEach(hostName => {
|
bootstrapData.common.conf.SUPERSET_WEBSERVER_DOMAINS.forEach(
|
||||||
availableDomains.add(hostName);
|
(hostName: string) => {
|
||||||
});
|
availableDomains.add(hostName);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Array.from(availableDomains);
|
return Array.from(availableDomains);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const availableDomains = getDomainsConfig();
|
export const availableDomains: string[] = getDomainsConfig();
|
||||||
|
|
||||||
export const allowCrossDomain = availableDomains.length > 1;
|
export const allowCrossDomain: boolean = availableDomains.length > 1;
|
||||||
@@ -18,7 +18,16 @@
|
|||||||
*/
|
*/
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
export function addToObject(state, arrKey, obj) {
|
interface StateWithId {
|
||||||
|
id?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addToObject<T extends Record<string, any>>(
|
||||||
|
state: T,
|
||||||
|
arrKey: keyof T,
|
||||||
|
obj: StateWithId,
|
||||||
|
): T {
|
||||||
const newObject = { ...state[arrKey] };
|
const newObject = { ...state[arrKey] };
|
||||||
const copiedObject = { ...obj };
|
const copiedObject = { ...obj };
|
||||||
|
|
||||||
@@ -29,18 +38,28 @@ export function addToObject(state, arrKey, obj) {
|
|||||||
return { ...state, [arrKey]: newObject };
|
return { ...state, [arrKey]: newObject };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function alterInObject(state, arrKey, obj, alterations) {
|
export function alterInObject<T extends Record<string, any>>(
|
||||||
|
state: T,
|
||||||
|
arrKey: keyof T,
|
||||||
|
obj: StateWithId,
|
||||||
|
alterations: Record<string, any>,
|
||||||
|
): T {
|
||||||
const newObject = { ...state[arrKey] };
|
const newObject = { ...state[arrKey] };
|
||||||
newObject[obj.id] = { ...newObject[obj.id], ...alterations };
|
newObject[obj.id!] = { ...newObject[obj.id!], ...alterations };
|
||||||
return { ...state, [arrKey]: newObject };
|
return { ...state, [arrKey]: newObject };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function alterInArr(state, arrKey, obj, alterations) {
|
export function alterInArr<T extends Record<string, any>>(
|
||||||
|
state: T,
|
||||||
|
arrKey: keyof T,
|
||||||
|
obj: StateWithId,
|
||||||
|
alterations: Record<string, any>,
|
||||||
|
): T {
|
||||||
// Finds an item in an array in the state and replaces it with a
|
// Finds an item in an array in the state and replaces it with a
|
||||||
// new object with an altered property
|
// new object with an altered property
|
||||||
const idKey = 'id';
|
const idKey = 'id';
|
||||||
const newArr = [];
|
const newArr: any[] = [];
|
||||||
state[arrKey].forEach(arrItem => {
|
state[arrKey].forEach((arrItem: any) => {
|
||||||
if (obj[idKey] === arrItem[idKey]) {
|
if (obj[idKey] === arrItem[idKey]) {
|
||||||
newArr.push({ ...arrItem, ...alterations });
|
newArr.push({ ...arrItem, ...alterations });
|
||||||
} else {
|
} else {
|
||||||
@@ -50,9 +69,14 @@ export function alterInArr(state, arrKey, obj, alterations) {
|
|||||||
return { ...state, [arrKey]: newArr };
|
return { ...state, [arrKey]: newArr };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeFromArr(state, arrKey, obj, idKey = 'id') {
|
export function removeFromArr<T extends Record<string, any>>(
|
||||||
const newArr = [];
|
state: T,
|
||||||
state[arrKey].forEach(arrItem => {
|
arrKey: keyof T,
|
||||||
|
obj: StateWithId,
|
||||||
|
idKey: string = 'id',
|
||||||
|
): T {
|
||||||
|
const newArr: any[] = [];
|
||||||
|
state[arrKey].forEach((arrItem: any) => {
|
||||||
if (!(obj[idKey] === arrItem[idKey])) {
|
if (!(obj[idKey] === arrItem[idKey])) {
|
||||||
newArr.push(arrItem);
|
newArr.push(arrItem);
|
||||||
}
|
}
|
||||||
@@ -60,12 +84,16 @@ export function removeFromArr(state, arrKey, obj, idKey = 'id') {
|
|||||||
return { ...state, [arrKey]: newArr };
|
return { ...state, [arrKey]: newArr };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addToArr(state, arrKey, obj) {
|
export function addToArr<T extends Record<string, any>>(
|
||||||
|
state: T,
|
||||||
|
arrKey: keyof T,
|
||||||
|
obj: StateWithId,
|
||||||
|
): T {
|
||||||
const newObj = { ...obj };
|
const newObj = { ...obj };
|
||||||
if (!newObj.id) {
|
if (!newObj.id) {
|
||||||
newObj.id = nanoid();
|
newObj.id = nanoid();
|
||||||
}
|
}
|
||||||
const newState = {};
|
const newState: Record<string, any> = {};
|
||||||
newState[arrKey] = [...state[arrKey], newObj];
|
newState[arrKey as string] = [...state[arrKey], newObj];
|
||||||
return { ...state, ...newState };
|
return { ...state, ...newState };
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user