SIP-32: Moving frontend code to the base of the repo (#9098)

* move assets out, get webpack dev working

* update docs to reference superset-frontend

* draw the rest of the owl

* fix docs

* fix webpack script

* rats

* correct docs

* fix tox dox
This commit is contained in:
David Aaron Suddjian
2020-02-09 17:53:56 -08:00
committed by GitHub
parent 0cf354cc88
commit 2913063924
930 changed files with 681 additions and 314 deletions

View File

@@ -0,0 +1,57 @@
/**
* 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 componentIsResizable from '../../../../src/dashboard/util/componentIsResizable';
import {
CHART_TYPE,
COLUMN_TYPE,
DASHBOARD_GRID_TYPE,
DASHBOARD_ROOT_TYPE,
DIVIDER_TYPE,
HEADER_TYPE,
MARKDOWN_TYPE,
ROW_TYPE,
TABS_TYPE,
TAB_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
const notResizable = [
DASHBOARD_GRID_TYPE,
DASHBOARD_ROOT_TYPE,
DIVIDER_TYPE,
HEADER_TYPE,
ROW_TYPE,
TABS_TYPE,
TAB_TYPE,
];
const resizable = [COLUMN_TYPE, CHART_TYPE, MARKDOWN_TYPE];
describe('componentIsResizable', () => {
resizable.forEach(type => {
it(`should return true for ${type}`, () => {
expect(componentIsResizable({ type })).toBe(true);
});
});
notResizable.forEach(type => {
it(`should return false for ${type}`, () => {
expect(componentIsResizable({ type })).toBe(false);
});
});
});

View File

@@ -0,0 +1,77 @@
/**
* 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 reorderItem from '../../../../src/dashboard/util/dnd-reorder';
describe('dnd-reorderItem', () => {
it('should remove the item from its source entity and add it to its destination entity', () => {
const result = reorderItem({
entitiesMap: {
a: {
id: 'a',
children: ['x', 'y', 'z'],
},
b: {
id: 'b',
children: ['banana'],
},
},
source: { id: 'a', index: 2 },
destination: { id: 'b', index: 1 },
});
expect(result.a.children).toEqual(['x', 'y']);
expect(result.b.children).toEqual(['banana', 'z']);
});
it('should correctly move elements within the same list', () => {
const result = reorderItem({
entitiesMap: {
a: {
id: 'a',
children: ['x', 'y', 'z'],
},
},
source: { id: 'a', index: 2 },
destination: { id: 'a', index: 0 },
});
expect(result.a.children).toEqual(['z', 'x', 'y']);
});
it('should copy items that do not move into the result', () => {
const extraEntity = {};
const result = reorderItem({
entitiesMap: {
a: {
id: 'a',
children: ['x', 'y', 'z'],
},
b: {
id: 'b',
children: ['banana'],
},
iAmExtra: extraEntity,
},
source: { id: 'a', index: 2 },
destination: { id: 'b', index: 1 },
});
expect(result.iAmExtra === extraEntity).toBe(true);
});
});

View File

@@ -0,0 +1,242 @@
/**
* 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 dropOverflowsParent from '../../../../src/dashboard/util/dropOverflowsParent';
import { NEW_COMPONENTS_SOURCE_ID } from '../../../../src/dashboard/util/constants';
import {
CHART_TYPE,
COLUMN_TYPE,
ROW_TYPE,
HEADER_TYPE,
TAB_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
describe('dropOverflowsParent', () => {
it('returns true if a parent does NOT have adequate width for child', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'a' },
dragging: { id: 'z' },
};
const layout = {
a: {
id: 'a',
type: ROW_TYPE,
children: ['b', 'b', 'b', 'b'], // width = 4x bs = 12
},
b: {
id: 'b',
type: CHART_TYPE,
meta: {
width: 3,
},
},
z: {
id: 'z',
type: CHART_TYPE,
meta: {
width: 2,
},
},
};
expect(dropOverflowsParent(dropResult, layout)).toBe(true);
});
it('returns false if a parent DOES have adequate width for child', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'a' },
dragging: { id: 'z' },
};
const layout = {
a: {
id: 'a',
type: ROW_TYPE,
children: ['b', 'b'],
},
b: {
id: 'b',
type: CHART_TYPE,
meta: {
width: 3,
},
},
z: {
id: 'z',
type: CHART_TYPE,
meta: {
width: 2,
},
},
};
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
});
it('returns false if a child CAN shrink to available parent space', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'a' },
dragging: { id: 'z' },
};
const layout = {
a: {
id: 'a',
type: ROW_TYPE,
children: ['b', 'b'], // 2x b = 10
},
b: {
id: 'b',
type: CHART_TYPE,
meta: {
width: 5,
},
},
z: {
id: 'z',
type: CHART_TYPE,
meta: {
width: 10,
},
},
};
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
});
it('returns true if a child CANNOT shrink to available parent space', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'a' },
dragging: { id: 'b' },
};
const layout = {
a: {
id: 'a',
type: COLUMN_TYPE,
meta: {
width: 6,
},
},
// rows with children cannot shrink
b: {
id: 'b',
type: ROW_TYPE,
children: ['bChild', 'bChild', 'bChild'],
},
bChild: {
id: 'bChild',
type: CHART_TYPE,
meta: {
width: 3,
},
},
};
expect(dropOverflowsParent(dropResult, layout)).toBe(true);
});
it('returns true if a column has children that CANNOT shrink to available parent space', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'destination' },
dragging: { id: 'dragging' },
};
const layout = {
destination: {
id: 'destination',
type: ROW_TYPE,
children: ['b', 'b'], // 2x b = 10, 2 available
},
b: {
id: 'b',
type: CHART_TYPE,
meta: {
width: 5,
},
},
dragging: {
id: 'dragging',
type: COLUMN_TYPE,
meta: {
width: 10,
},
children: ['rowWithChildren'], // 2x b = width 10
},
rowWithChildren: {
id: 'rowWithChildren',
type: ROW_TYPE,
children: ['b', 'b'],
},
};
expect(dropOverflowsParent(dropResult, layout)).toBe(true);
// remove children
expect(
dropOverflowsParent(dropResult, {
...layout,
dragging: { ...layout.dragging, children: [] },
}),
).toBe(false);
});
it('should work with new components that are not in the layout', () => {
const dropResult = {
source: { id: NEW_COMPONENTS_SOURCE_ID },
destination: { id: 'a' },
dragging: { type: CHART_TYPE },
};
const layout = {
a: {
id: 'a',
type: ROW_TYPE,
children: [],
},
};
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
});
it('source/destination without widths should not overflow parent', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'tab' },
dragging: { id: 'header' },
};
const layout = {
tab: {
id: 'tab',
type: TAB_TYPE,
},
header: {
id: 'header',
type: HEADER_TYPE,
},
};
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
});
});

View File

@@ -0,0 +1,126 @@
/**
* 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 findFirstParentContainerId from '../../../../src/dashboard/util/findFirstParentContainer';
import {
DASHBOARD_GRID_ID,
DASHBOARD_ROOT_ID,
} from '../../../../src/dashboard/util/constants';
describe('findFirstParentContainer', () => {
const mockGridLayout = {
DASHBOARD_VERSION_KEY: 'v2',
ROOT_ID: {
type: 'ROOT',
id: 'ROOT_ID',
children: ['GRID_ID'],
},
GRID_ID: {
type: 'GRID',
id: 'GRID_ID',
children: ['ROW-Bk45URrlQ'],
},
'ROW-Bk45URrlQ': {
type: 'ROW',
id: 'ROW-Bk45URrlQ',
children: ['CHART-ryxVc8RHlX'],
},
'CHART-ryxVc8RHlX': {
type: 'CHART',
id: 'CHART-ryxVc8RHlX',
children: [],
},
HEADER_ID: {
id: 'HEADER_ID',
type: 'HEADER',
},
};
const mockTabsLayout = {
'CHART-S1gilYABe7': {
children: [],
id: 'CHART-S1gilYABe7',
type: 'CHART',
},
'CHART-SJli5K0HlQ': {
children: [],
id: 'CHART-SJli5K0HlQ',
type: 'CHART',
},
GRID_ID: {
children: [],
id: 'GRID_ID',
type: 'GRID',
},
HEADER_ID: {
id: 'HEADER_ID',
type: 'HEADER',
},
ROOT_ID: {
children: ['TABS-SkgJ5t0Bem'],
id: 'ROOT_ID',
type: 'ROOT',
},
'ROW-S1B8-JLgX': {
children: ['CHART-SJli5K0HlQ'],
id: 'ROW-S1B8-JLgX',
type: 'ROW',
},
'ROW-S1bUb1Ilm': {
children: ['CHART-S1gilYABe7'],
id: 'ROW-S1bUb1Ilm',
type: 'ROW',
},
'TABS-ByeLSWyLe7': {
children: ['TAB-BJbLSZ1UeQ'],
id: 'TABS-ByeLSWyLe7',
type: 'TABS',
},
'TABS-SkgJ5t0Bem': {
children: ['TAB-HkWJcFCHxQ', 'TAB-ByDBbkLlQ'],
id: 'TABS-SkgJ5t0Bem',
meta: {},
type: 'TABS',
},
'TAB-BJbLSZ1UeQ': {
children: ['ROW-S1bUb1Ilm'],
id: 'TAB-BJbLSZ1UeQ',
type: 'TAB',
},
'TAB-ByDBbkLlQ': {
children: ['ROW-S1B8-JLgX'],
id: 'TAB-ByDBbkLlQ',
type: 'TAB',
},
'TAB-HkWJcFCHxQ': {
children: ['TABS-ByeLSWyLe7'],
id: 'TAB-HkWJcFCHxQ',
type: 'TAB',
},
DASHBOARD_VERSION_KEY: 'v2',
};
it('should return grid root', () => {
expect(findFirstParentContainerId(mockGridLayout)).toBe(DASHBOARD_GRID_ID);
});
it('should return first tab', () => {
const tabsId = mockTabsLayout[DASHBOARD_ROOT_ID].children[0];
const firstTabId = mockTabsLayout[tabsId].children[0];
expect(findFirstParentContainerId(mockTabsLayout)).toBe(firstTabId);
});
});

View File

@@ -0,0 +1,44 @@
/**
* 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 findParentId from '../../../../src/dashboard/util/findParentId';
describe('findParentId', () => {
const layout = {
a: {
id: 'a',
children: ['b', 'r', 'k'],
},
b: {
id: 'b',
children: ['x', 'y', 'z'],
},
z: {
id: 'z',
children: [],
},
};
it('should return the correct parentId', () => {
expect(findParentId({ childId: 'b', layout })).toBe('a');
expect(findParentId({ childId: 'z', layout })).toBe('b');
});
it('should return null if the parent cannot be found', () => {
expect(findParentId({ childId: 'a', layout })).toBeNull();
});
});

View File

@@ -0,0 +1,85 @@
/**
* 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 findTabIndexByComponentId from '../../../../src/dashboard/util/findTabIndexByComponentId';
describe('findTabIndexByComponentId', () => {
const topLevelTabsComponent = {
children: ['TAB-0g-5l347I2', 'TAB-qrwN_9VB5'],
id: 'TABS-MNQQSW-kyd',
meta: {},
parents: ['ROOT_ID'],
type: 'TABS',
};
const rowLevelTabsComponent = {
children: [
'TAB-TwyUUGp2Bg',
'TAB-Zl1BQAUvN',
'TAB-P0DllxzTU',
'TAB---e53RNei',
],
id: 'TABS-Oduxop1L7I',
meta: {},
parents: ['ROOT_ID', 'TABS-MNQQSW-kyd', 'TAB-qrwN_9VB5'],
type: 'TABS',
};
const goodPathToChild = [
'ROOT_ID',
'TABS-MNQQSW-kyd',
'TAB-qrwN_9VB5',
'TABS-Oduxop1L7I',
'TAB-P0DllxzTU',
'ROW-JXhrFnVP8',
'CHART-dUIVg-ENq6',
];
const badPath = ['ROOT_ID', 'TABS-MNQQSW-kyd', 'TAB-ABC', 'TABS-Oduxop1L7I'];
it('should return -1 if no directPathToChild', () => {
expect(
findTabIndexByComponentId({
currentComponent: topLevelTabsComponent,
directPathToChild: [],
}),
).toBe(-1);
});
it('should return -1 if not found tab id', () => {
expect(
findTabIndexByComponentId({
currentComponent: topLevelTabsComponent,
directPathToChild: badPath,
}),
).toBe(-1);
});
it('should return children index if matched an id in the path', () => {
expect(
findTabIndexByComponentId({
currentComponent: topLevelTabsComponent,
directPathToChild: goodPathToChild,
}),
).toBe(1);
expect(
findTabIndexByComponentId({
currentComponent: rowLevelTabsComponent,
directPathToChild: goodPathToChild,
}),
).toBe(2);
});
});

View File

@@ -0,0 +1,38 @@
/**
* 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 getChartAndLabelComponentIdFromPath from '../../../../src/dashboard/util/getChartAndLabelComponentIdFromPath';
describe('getChartAndLabelComponentIdFromPath', () => {
it('should return label and component id', () => {
const directPathToChild = [
'ROOT_ID',
'TABS-aX1uNK-ryo',
'TAB-ZRgxfD2ktj',
'ROW-46632bc2',
'COLUMN-XjlxaS-flc',
'CHART-x-RMdAtlDb',
'LABEL-region',
];
expect(getChartAndLabelComponentIdFromPath(directPathToChild)).toEqual({
label: 'LABEL-region',
chart: 'CHART-x-RMdAtlDb',
});
});
});

View File

@@ -0,0 +1,56 @@
/**
* 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 getChartIdsFromLayout from '../../../../src/dashboard/util/getChartIdsFromLayout';
import {
ROW_TYPE,
CHART_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
describe('getChartIdsFromLayout', () => {
const mockLayout = {
a: {
id: 'a',
type: CHART_TYPE,
meta: { chartId: 'A' },
},
b: {
id: 'b',
type: CHART_TYPE,
meta: { chartId: 'B' },
},
c: {
id: 'c',
type: ROW_TYPE,
meta: { chartId: 'C' },
},
};
it('should return an array of chartIds', () => {
const result = getChartIdsFromLayout(mockLayout);
expect(Array.isArray(result)).toBe(true);
expect(result.includes('A')).toBe(true);
expect(result.includes('B')).toBe(true);
});
it('should return ids only from CHART_TYPE components', () => {
const result = getChartIdsFromLayout(mockLayout);
expect(result).toHaveLength(2);
expect(result.includes('C')).toBe(false);
});
});

View File

@@ -0,0 +1,35 @@
/**
* 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 getDashboardUrl from '../../../../src/dashboard/util/getDashboardUrl';
import { DASHBOARD_FILTER_SCOPE_GLOBAL } from '../../../../src/dashboard/reducers/dashboardFilters';
describe('getChartIdsFromLayout', () => {
it('should encode filters', () => {
const filters = {
'35_key': {
values: ['value'],
scope: DASHBOARD_FILTER_SCOPE_GLOBAL,
},
};
const url = getDashboardUrl('path', filters);
expect(url).toBe(
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D',
);
});
});

View File

@@ -0,0 +1,240 @@
/**
* 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 getDetailedComponentWidth from '../../../../src/dashboard/util/getDetailedComponentWidth';
import * as types from '../../../../src/dashboard/util/componentTypes';
import {
GRID_COLUMN_COUNT,
GRID_MIN_COLUMN_COUNT,
} from '../../../../src/dashboard/util/constants';
describe('getDetailedComponentWidth', () => {
it('should return an object with width, minimumWidth, and occupiedWidth', () => {
expect(
Object.keys(getDetailedComponentWidth({ id: '_', components: {} })),
).toEqual(
expect.arrayContaining(['minimumWidth', 'occupiedWidth', 'width']),
);
});
describe('width', () => {
it('should be undefined if the component is not resizable and has no defined width', () => {
const empty = {
width: undefined,
occupiedWidth: undefined,
minimumWidth: undefined,
};
expect(
getDetailedComponentWidth({
component: { id: '', type: types.HEADER_TYPE },
}),
).toEqual(empty);
expect(
getDetailedComponentWidth({
component: { id: '', type: types.DIVIDER_TYPE },
}),
).toEqual(empty);
expect(
getDetailedComponentWidth({
component: { id: '', type: types.TAB_TYPE },
}),
).toEqual(empty);
});
it('should match component meta width for resizeable components', () => {
expect(
getDetailedComponentWidth({
component: { id: '', type: types.CHART_TYPE, meta: { width: 1 } },
}),
).toEqual({ width: 1, occupiedWidth: 1, minimumWidth: 1 });
expect(
getDetailedComponentWidth({
component: { id: '', type: types.MARKDOWN_TYPE, meta: { width: 2 } },
}),
).toEqual({ width: 2, occupiedWidth: 2, minimumWidth: 1 });
expect(
getDetailedComponentWidth({
component: { id: '', type: types.COLUMN_TYPE, meta: { width: 3 } },
}),
// note: occupiedWidth is zero for colunns/see test below
).toEqual({ width: 3, occupiedWidth: 0, minimumWidth: 1 });
});
it('should be GRID_COLUMN_COUNT for row components WITHOUT parents', () => {
expect(
getDetailedComponentWidth({
id: 'row',
components: { row: { id: 'row', type: types.ROW_TYPE } },
}),
).toEqual({
width: GRID_COLUMN_COUNT,
occupiedWidth: 0,
minimumWidth: GRID_MIN_COLUMN_COUNT,
});
});
it('should match parent width for row components WITH parents', () => {
expect(
getDetailedComponentWidth({
id: 'row',
components: {
row: { id: 'row', type: types.ROW_TYPE },
parent: {
id: 'parent',
type: types.COLUMN_TYPE,
children: ['row'],
meta: { width: 7 },
},
},
}),
).toEqual({
width: 7,
occupiedWidth: 0,
minimumWidth: GRID_MIN_COLUMN_COUNT,
});
});
it('should use either id or component (to support new components)', () => {
expect(
getDetailedComponentWidth({
id: 'id',
components: {
id: { id: 'id', type: types.CHART_TYPE, meta: { width: 6 } },
},
}).width,
).toBe(6);
expect(
getDetailedComponentWidth({
component: { id: 'id', type: types.CHART_TYPE, meta: { width: 6 } },
}).width,
).toBe(6);
});
});
describe('occupiedWidth', () => {
it('should reflect the sum of child widths for row components', () => {
expect(
getDetailedComponentWidth({
id: 'row',
components: {
row: {
id: 'row',
type: types.ROW_TYPE,
children: ['child', 'child'],
},
child: { id: 'child', meta: { width: 3.5 } },
},
}),
).toEqual({ width: 12, occupiedWidth: 7, minimumWidth: 7 });
});
it('should always be zero for column components', () => {
expect(
getDetailedComponentWidth({
component: { id: '', type: types.COLUMN_TYPE, meta: { width: 2 } },
}),
).toEqual({ width: 2, occupiedWidth: 0, minimumWidth: 1 });
});
});
describe('minimumWidth', () => {
it('should equal GRID_MIN_COLUMN_COUNT for resizable components', () => {
expect(
getDetailedComponentWidth({
component: { id: '', type: types.CHART_TYPE, meta: { width: 1 } },
}),
).toEqual({
width: 1,
minimumWidth: GRID_MIN_COLUMN_COUNT,
occupiedWidth: 1,
});
expect(
getDetailedComponentWidth({
component: { id: '', type: types.MARKDOWN_TYPE, meta: { width: 2 } },
}),
).toEqual({
width: 2,
minimumWidth: GRID_MIN_COLUMN_COUNT,
occupiedWidth: 2,
});
expect(
getDetailedComponentWidth({
component: { id: '', type: types.COLUMN_TYPE, meta: { width: 3 } },
}),
).toEqual({
width: 3,
minimumWidth: GRID_MIN_COLUMN_COUNT,
occupiedWidth: 0,
});
});
it('should equal the width of row children for column components with row children', () => {
expect(
getDetailedComponentWidth({
id: 'column',
components: {
column: {
id: 'column',
type: types.COLUMN_TYPE,
children: ['rowChild', 'ignoredChartChild'],
meta: { width: 12 },
},
rowChild: {
id: 'rowChild',
type: types.ROW_TYPE,
children: ['rowChildChild', 'rowChildChild'],
},
rowChildChild: {
id: 'rowChildChild',
meta: { width: 3.5 },
},
ignoredChartChild: {
id: 'ignoredChartChild',
meta: { width: 100 },
},
},
}),
// occupiedWidth is zero for colunns/see test below
).toEqual({ width: 12, occupiedWidth: 0, minimumWidth: 7 });
});
it('should equal occupiedWidth for row components', () => {
expect(
getDetailedComponentWidth({
id: 'row',
components: {
row: {
id: 'row',
type: types.ROW_TYPE,
children: ['child', 'child'],
},
child: { id: 'child', meta: { width: 3.5 } },
},
}),
).toEqual({ width: 12, occupiedWidth: 7, minimumWidth: 7 });
});
});
});

View File

@@ -0,0 +1,437 @@
/**
* 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 getDropPosition, {
DROP_TOP,
DROP_RIGHT,
DROP_BOTTOM,
DROP_LEFT,
} from '../../../../src/dashboard/util/getDropPosition';
import {
CHART_TYPE,
DASHBOARD_GRID_TYPE,
DASHBOARD_ROOT_TYPE,
HEADER_TYPE,
ROW_TYPE,
TAB_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
describe('getDropPosition', () => {
// helper to easily configure test
function getMocks({
parentType,
componentType,
draggingType,
depth = 1,
hasChildren = false,
orientation = 'row',
clientOffset = { x: 0, y: 0 },
boundingClientRect = {
top: 0,
right: 0,
bottom: 0,
left: 0,
},
isDraggingOverShallow = true,
}) {
const monitorMock = {
getItem: () => ({
id: 'id',
type: draggingType,
}),
getClientOffset: () => clientOffset,
};
const ComponentMock = {
props: {
depth,
parentComponent: {
type: parentType,
},
component: {
type: componentType,
children: hasChildren ? [''] : [],
},
orientation,
isDraggingOverShallow,
},
ref: {
getBoundingClientRect: () => boundingClientRect,
},
};
return [monitorMock, ComponentMock];
}
describe('invalid child + invalid sibling', () => {
it('should return null', () => {
const result = getDropPosition(
// TAB is an invalid child + sibling of GRID > ROW
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: TAB_TYPE,
}),
);
expect(result).toBeNull();
});
});
describe('valid child + invalid sibling', () => {
it('should return DROP_LEFT if component has NO children, and orientation is "row"', () => {
// HEADER is a valid child + invalid sibling of ROOT > GRID
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_ROOT_TYPE,
componentType: DASHBOARD_GRID_TYPE,
draggingType: HEADER_TYPE,
}),
);
expect(result).toBe(DROP_LEFT);
});
it('should return DROP_RIGHT if component HAS children, and orientation is "row"', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_ROOT_TYPE,
componentType: DASHBOARD_GRID_TYPE,
draggingType: HEADER_TYPE,
hasChildren: true,
}),
);
expect(result).toBe(DROP_RIGHT);
});
it('should return DROP_TOP if component has NO children, and orientation is "column"', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_ROOT_TYPE,
componentType: DASHBOARD_GRID_TYPE,
draggingType: HEADER_TYPE,
orientation: 'column',
}),
);
expect(result).toBe(DROP_TOP);
});
it('should return DROP_BOTTOM if component HAS children, and orientation is "column"', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_ROOT_TYPE,
componentType: DASHBOARD_GRID_TYPE,
draggingType: HEADER_TYPE,
orientation: 'column',
hasChildren: true,
}),
);
expect(result).toBe(DROP_BOTTOM);
});
});
describe('invalid child + valid sibling', () => {
it('should return DROP_TOP if orientation="row" and clientOffset is closer to component top than bottom', () => {
const result = getDropPosition(
// HEADER is an invalid child but valid sibling of GRID > ROW
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: HEADER_TYPE,
clientOffset: { y: 10 },
boundingClientRect: {
top: 0,
bottom: 100,
},
}),
);
expect(result).toBe(DROP_TOP);
});
it('should return DROP_BOTTOM if orientation="row" and clientOffset is closer to component bottom than top', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: HEADER_TYPE,
clientOffset: { y: 55 },
boundingClientRect: {
top: 0,
bottom: 100,
},
}),
);
expect(result).toBe(DROP_BOTTOM);
});
it('should return DROP_LEFT if orientation="column" and clientOffset is closer to component left than right', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: HEADER_TYPE,
orientation: 'column',
clientOffset: { x: 45 },
boundingClientRect: {
left: 0,
right: 100,
},
}),
);
expect(result).toBe(DROP_LEFT);
});
it('should return DROP_RIGHT if orientation="column" and clientOffset is closer to component right than left', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: HEADER_TYPE,
orientation: 'column',
clientOffset: { x: 55 },
boundingClientRect: {
left: 0,
right: 100,
},
}),
);
expect(result).toBe(DROP_RIGHT);
});
});
describe('child + valid sibling (row orientation)', () => {
it('should return DROP_LEFT if component has NO children, and clientOffset is NOT near top/bottom sibling boundary', () => {
const result = getDropPosition(
// CHART is a valid child + sibling of GRID > ROW
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
clientOffset: { x: 10, y: 50 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(result).toBe(DROP_LEFT);
});
it('should return DROP_RIGHT if component HAS children, and clientOffset is NOT near top/bottom sibling boundary', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
hasChildren: true,
clientOffset: { x: 10, y: 50 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(result).toBe(DROP_RIGHT);
});
it('should return DROP_TOP regardless of component children if clientOffset IS near top sibling boundary', () => {
const noChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
clientOffset: { x: 10, y: 2 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
const withChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
hasChildren: true,
clientOffset: { x: 10, y: 2 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(noChildren).toBe(DROP_TOP);
expect(withChildren).toBe(DROP_TOP);
});
it('should return DROP_BOTTOM regardless of component children if clientOffset IS near bottom sibling boundary', () => {
const noChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
clientOffset: { x: 10, y: 95 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
const withChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
hasChildren: true,
clientOffset: { x: 10, y: 95 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(noChildren).toBe(DROP_BOTTOM);
expect(withChildren).toBe(DROP_BOTTOM);
});
});
describe('child + valid sibling (column orientation)', () => {
it('should return DROP_TOP if component has NO children, and clientOffset is NOT near left/right sibling boundary', () => {
const result = getDropPosition(
// CHART is a valid child + sibling of GRID > ROW
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
clientOffset: { x: 50, y: 0 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(result).toBe(DROP_TOP);
});
it('should return DROP_BOTTOM if component HAS children, and clientOffset is NOT near left/right sibling boundary', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
hasChildren: true,
clientOffset: { x: 50, y: 0 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(result).toBe(DROP_BOTTOM);
});
it('should return DROP_LEFT regardless of component children if clientOffset IS near left sibling boundary', () => {
const noChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
clientOffset: { x: 10, y: 2 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
const withChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
hasChildren: true,
clientOffset: { x: 10, y: 2 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(noChildren).toBe(DROP_LEFT);
expect(withChildren).toBe(DROP_LEFT);
});
it('should return DROP_RIGHT regardless of component children if clientOffset IS near right sibling boundary', () => {
const noChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
clientOffset: { x: 90, y: 95 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
const withChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
hasChildren: true,
clientOffset: { x: 90, y: 95 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(noChildren).toBe(DROP_RIGHT);
expect(withChildren).toBe(DROP_RIGHT);
});
});
});

View File

@@ -0,0 +1,87 @@
/**
* 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 getFilterConfigsFromFormdata from '../../../../src/dashboard/util/getFilterConfigsFromFormdata';
describe('getFilterConfigsFromFormdata', () => {
const testFormdata = {
filter_configs: [
{
asc: true,
clearable: true,
column: 'state',
defaultValue: 'CA',
key: 'fvwncPjUf',
multiple: true,
},
],
date_filter: true,
granularity_sqla: '__time',
time_grain_sqla: 'P1M',
time_range: '2018-12-30T00:00:00+:+last+saturday',
};
it('should add time grain', () => {
const result = getFilterConfigsFromFormdata({
...testFormdata,
show_sqla_time_granularity: true,
});
expect(result.columns).toMatchObject({
__time_grain: testFormdata.time_grain_sqla,
});
});
it('should add time column', () => {
const result = getFilterConfigsFromFormdata({
...testFormdata,
show_sqla_time_column: true,
});
expect(result.columns).toMatchObject({
__time_col: testFormdata.granularity_sqla,
});
});
it('should use default value from form_data', () => {
const result = getFilterConfigsFromFormdata({
...testFormdata,
show_sqla_time_column: true,
});
expect(result.columns).toMatchObject({
state: ['CA'],
});
});
it('should read multi values from form_data', () => {
const result = getFilterConfigsFromFormdata({
...testFormdata,
filter_configs: [
{
asc: true,
clearable: true,
column: 'state',
defaultValue: 'CA;NY',
key: 'fvwncPjUf',
multiple: true,
},
],
});
expect(result.columns).toMatchObject({
state: ['CA', 'NY'],
});
});
});

View File

@@ -0,0 +1,216 @@
/**
* 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 getFilterScopeFromNodesTree from '../../../../src/dashboard/util/getFilterScopeFromNodesTree';
describe('getFilterScopeFromNodesTree', () => {
it('should return empty scope', () => {
const nodes = [];
expect(
getFilterScopeFromNodesTree({
filterKey: '107_region',
nodes,
checkedChartIds: [],
}),
).toEqual({});
});
it('should return scope for simple grid', () => {
const nodes = [
{
label: 'All dashboard',
type: 'ROOT',
value: 'ROOT_ID',
children: [
{
value: 104,
label: 'Life Expectancy VS Rural %',
type: 'CHART',
},
{ value: 105, label: 'Rural Breakdown', type: 'CHART' },
{
value: 106,
label: "World's Pop Growth",
type: 'CHART',
},
{
label: 'Time Filter',
showCheckbox: false,
type: 'CHART',
value: 108,
},
],
},
];
const checkedChartIds = [104, 106];
expect(
getFilterScopeFromNodesTree({
filterKey: '108___time_range',
nodes,
checkedChartIds,
}),
).toEqual({
scope: ['ROOT_ID'],
immune: [105],
});
});
describe('should return scope for tabbed dashboard', () => {
const nodes = [
{
label: 'All dashboard',
type: 'ROOT',
value: 'ROOT_ID',
children: [
{
label: 'Tab 1',
type: 'TAB',
value: 'TAB-Rb5aaqKWgG',
children: [
{
label: 'Geo Filters',
showCheckbox: false,
type: 'CHART',
value: 107,
},
{
label: "World's Pop Growth",
showCheckbox: true,
type: 'CHART',
value: 106,
},
],
},
{
label: 'Tab 2',
type: 'TAB',
value: 'TAB-w5Fp904Rs',
children: [
{
label: 'Time Filter',
showCheckbox: true,
type: 'CHART',
value: 108,
},
{
label: 'Life Expectancy VS Rural %',
showCheckbox: true,
type: 'CHART',
value: 104,
},
{
label: 'Row Tab 1',
type: 'TAB',
value: 'TAB-E4mJaZ-uQM',
children: [
{
value: 105,
label: 'Rural Breakdown',
type: 'CHART',
showCheckbox: true,
},
{
value: 103,
label: '% Rural',
type: 'CHART',
showCheckbox: true,
},
],
},
{
value: 'TAB-rLYu-Cryu',
label: 'New Tab',
type: 'TAB',
children: [
{
value: 102,
label: 'Most Populated Countries',
type: 'CHART',
showCheckbox: true,
},
{
value: 101,
label: "World's Population",
type: 'CHART',
showCheckbox: true,
},
],
},
],
},
],
},
];
it('root level tab scope', () => {
const checkedChartIds = [106];
expect(
getFilterScopeFromNodesTree({
filterKey: '107_region',
nodes,
checkedChartIds,
}),
).toEqual({
scope: ['TAB-Rb5aaqKWgG'],
immune: [],
});
});
it('global scope', () => {
const checkedChartIds = [106, 104, 101, 102, 103, 105];
expect(
getFilterScopeFromNodesTree({
filterKey: '107_country_name',
nodes,
checkedChartIds,
}),
).toEqual({
scope: ['ROOT_ID'],
immune: [108],
});
});
it('row level tab scope', () => {
const checkedChartIds = [103, 105];
expect(
getFilterScopeFromNodesTree({
filterKey: '108___time_range',
nodes,
checkedChartIds,
}),
).toEqual({
scope: ['TAB-E4mJaZ-uQM'],
immune: [],
});
});
it('mixed row level and root level scope', () => {
const checkedChartIds = [103, 105, 106];
expect(
getFilterScopeFromNodesTree({
filterKey: '107_region',
nodes,
checkedChartIds,
}),
).toEqual({
scope: ['TAB-Rb5aaqKWgG', 'TAB-E4mJaZ-uQM'],
immune: [],
});
});
});
});

View File

@@ -0,0 +1,57 @@
/**
* 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 getFormDataWithExtraFilters from '../../../../src/dashboard/util/charts/getFormDataWithExtraFilters';
describe('getFormDataWithExtraFilters', () => {
const chartId = 'chartId';
const mockArgs = {
chart: {
id: chartId,
formData: {
filters: [
{
col: 'country_name',
op: 'in',
val: ['United States'],
},
],
},
},
filters: {
region: ['Spain'],
color: ['pink', 'purple'],
},
sliceId: chartId,
};
it('should include filters from the passed filters', () => {
const result = getFormDataWithExtraFilters(mockArgs);
expect(result.extra_filters).toHaveLength(2);
expect(result.extra_filters[0]).toEqual({
col: 'region',
op: 'in',
val: ['Spain'],
});
expect(result.extra_filters[1]).toEqual({
col: 'color',
op: 'in',
val: ['pink', 'purple'],
});
});
});

View File

@@ -0,0 +1,37 @@
/**
* 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 getLeafComponentIdFromPath from '../../../../src/dashboard/util/getLeafComponentIdFromPath';
import { filterId } from '../fixtures/mockSliceEntities';
import { dashboardFilters } from '../fixtures/mockDashboardFilters';
describe('getLeafComponentIdFromPath', () => {
const path = dashboardFilters[filterId].directPathToFilter;
const leaf = path.slice().pop();
it('should return component id', () => {
expect(getLeafComponentIdFromPath(path)).toBe(leaf);
});
it('should not return label component', () => {
const updatedPath = dashboardFilters[filterId].directPathToFilter.concat(
'LABEL-test123',
);
expect(getLeafComponentIdFromPath(updatedPath)).toBe(leaf);
});
});

View File

@@ -0,0 +1,162 @@
/**
* 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 isValidChild from '../../../../src/dashboard/util/isValidChild';
import {
CHART_TYPE as CHART,
COLUMN_TYPE as COLUMN,
DASHBOARD_GRID_TYPE as GRID,
DASHBOARD_ROOT_TYPE as ROOT,
DIVIDER_TYPE as DIVIDER,
HEADER_TYPE as HEADER,
MARKDOWN_TYPE as MARKDOWN,
ROW_TYPE as ROW,
TABS_TYPE as TABS,
TAB_TYPE as TAB,
} from '../../../../src/dashboard/util/componentTypes';
const getIndentation = depth =>
Array(depth * 3)
.fill('')
.join('-');
describe('isValidChild', () => {
describe('valid calls', () => {
// these are representations of nested structures for easy testing
// [ROOT (depth 0) > GRID (depth 1) > HEADER (depth 2)]
// every unique parent > child relationship is tested, but because this
// test representation WILL result in duplicates, we hash each test
// to keep track of which we've run
const didTest = {};
const validExamples = [
[ROOT, GRID, CHART], // chart is valid because it is wrapped in a row
[ROOT, GRID, MARKDOWN], // markdown is valid because it is wrapped in a row
[ROOT, GRID, COLUMN], // column is valid because it is wrapped in a row
[ROOT, GRID, HEADER],
[ROOT, GRID, ROW, MARKDOWN],
[ROOT, GRID, ROW, CHART],
[ROOT, GRID, ROW, COLUMN, HEADER],
[ROOT, GRID, ROW, COLUMN, DIVIDER],
[ROOT, GRID, ROW, COLUMN, CHART],
[ROOT, GRID, ROW, COLUMN, MARKDOWN],
[ROOT, GRID, ROW, COLUMN, ROW, CHART],
[ROOT, GRID, ROW, COLUMN, ROW, MARKDOWN],
[ROOT, GRID, ROW, COLUMN, ROW, COLUMN, CHART],
[ROOT, GRID, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
[ROOT, GRID, TABS, TAB, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
// tab equivalents
[ROOT, TABS, TAB, CHART],
[ROOT, TABS, TAB, MARKDOWN],
[ROOT, TABS, TAB, COLUMN],
[ROOT, TABS, TAB, HEADER],
[ROOT, TABS, TAB, ROW, MARKDOWN],
[ROOT, TABS, TAB, ROW, CHART],
[ROOT, TABS, TAB, ROW, COLUMN, HEADER],
[ROOT, TABS, TAB, ROW, COLUMN, DIVIDER],
[ROOT, TABS, TAB, ROW, COLUMN, CHART],
[ROOT, TABS, TAB, ROW, COLUMN, MARKDOWN],
[ROOT, TABS, TAB, ROW, COLUMN, ROW, CHART],
[ROOT, TABS, TAB, ROW, COLUMN, ROW, MARKDOWN],
[ROOT, TABS, TAB, ROW, COLUMN, ROW, COLUMN, CHART],
[ROOT, TABS, TAB, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
[ROOT, TABS, TAB, TABS, TAB, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
];
validExamples.forEach((example, exampleIdx) => {
let childDepth = 0;
example.forEach((childType, i) => {
const parentDepth = childDepth - 1;
const parentType = example[i - 1];
const testKey = `${parentType}-${childType}-${parentDepth}`;
if (i > 0 && !didTest[testKey]) {
didTest[testKey] = true;
it(`(${exampleIdx})${getIndentation(
childDepth,
)}${parentType} (depth ${parentDepth}) > ${childType}`, () => {
expect(
isValidChild({
parentDepth,
parentType,
childType,
}),
).toBe(true);
});
}
// see isValidChild.js for why tabs do not increment the depth of their children
childDepth += childType !== TABS && childType !== TAB ? 1 : 0;
});
});
});
describe('invalid calls', () => {
// In order to assert that a parent > child hierarchy at a given depth is invalid
// we also define some valid hierarchies in doing so. we indicate which
// parent > [child] relationships should be asserted as invalid using a nested array
const invalidExamples = [
[ROOT, [DIVIDER]],
[ROOT, [CHART]],
[ROOT, [MARKDOWN]],
[ROOT, GRID, [TAB]],
[ROOT, GRID, TABS, [ROW]],
// [ROOT, GRID, TABS, TAB, [TABS]], // @TODO this needs to be fixed
[ROOT, GRID, ROW, [TABS]],
[ROOT, GRID, ROW, [TAB]],
[ROOT, GRID, ROW, [DIVIDER]],
[ROOT, GRID, ROW, COLUMN, [TABS]],
[ROOT, GRID, ROW, COLUMN, [TAB]],
[ROOT, GRID, ROW, COLUMN, ROW, [DIVIDER]],
[ROOT, GRID, ROW, COLUMN, ROW, COLUMN, [ROW]], // too nested
];
invalidExamples.forEach((example, exampleIdx) => {
let childDepth = 0;
example.forEach((childType, i) => {
const shouldTestChild = Array.isArray(childType);
if (i > 0 && shouldTestChild) {
const parentDepth = childDepth - 1;
const parentType = example[i - 1];
it(`(${exampleIdx})${getIndentation(
childDepth,
)}${parentType} (depth ${parentDepth}) > ${childType}`, () => {
expect(
isValidChild({
parentDepth,
parentType,
childType,
}),
).toBe(false);
});
}
// see isValidChild.js for why tabs do not increment the depth of their children
childDepth += childType !== TABS && childType !== TAB ? 1 : 0;
});
});
});
});

View 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 newComponentFactory from '../../../../src/dashboard/util/newComponentFactory';
import {
CHART_TYPE,
COLUMN_TYPE,
DASHBOARD_GRID_TYPE,
DASHBOARD_ROOT_TYPE,
DIVIDER_TYPE,
HEADER_TYPE,
MARKDOWN_TYPE,
NEW_COMPONENT_SOURCE_TYPE,
ROW_TYPE,
TABS_TYPE,
TAB_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
const types = [
CHART_TYPE,
COLUMN_TYPE,
DASHBOARD_GRID_TYPE,
DASHBOARD_ROOT_TYPE,
DIVIDER_TYPE,
HEADER_TYPE,
MARKDOWN_TYPE,
NEW_COMPONENT_SOURCE_TYPE,
ROW_TYPE,
TABS_TYPE,
TAB_TYPE,
];
describe('newEntityFactory', () => {
types.forEach(type => {
it(`returns a new ${type}`, () => {
const result = newComponentFactory(type);
expect(result.type).toBe(type);
expect(typeof result.id).toBe('string');
expect(typeof result.meta).toBe('object');
expect(Array.isArray(result.children)).toBe(true);
});
});
it('adds passed meta data to the entity', () => {
const banana = 'banana';
const result = newComponentFactory(CHART_TYPE, { banana });
expect(result.meta.banana).toBe(banana);
});
});

View File

@@ -0,0 +1,100 @@
/**
* 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 newEntitiesFromDrop from '../../../../src/dashboard/util/newEntitiesFromDrop';
import {
CHART_TYPE,
DASHBOARD_GRID_TYPE,
ROW_TYPE,
TABS_TYPE,
TAB_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
describe('newEntitiesFromDrop', () => {
it('should return a new Entity of appropriate type, and add it to the drop target children', () => {
const result = newEntitiesFromDrop({
dropResult: {
destination: { id: 'a', index: 0 },
dragging: { type: CHART_TYPE },
source: { id: 'b', index: 0 },
},
layout: {
a: {
id: 'a',
type: ROW_TYPE,
children: [],
},
},
});
const newId = result.a.children[0];
expect(result.a.children).toHaveLength(1);
expect(Object.keys(result)).toHaveLength(2);
expect(result[newId].type).toBe(CHART_TYPE);
});
it('should create Tab AND Tabs components if the drag entity is Tabs', () => {
const result = newEntitiesFromDrop({
dropResult: {
destination: { id: 'a', index: 0 },
dragging: { type: TABS_TYPE },
source: { id: 'b', index: 0 },
},
layout: {
a: {
id: 'a',
type: DASHBOARD_GRID_TYPE,
children: [],
},
},
});
const newTabsId = result.a.children[0];
const newTabId = result[newTabsId].children[0];
expect(result.a.children).toHaveLength(1);
expect(Object.keys(result)).toHaveLength(3);
expect(result[newTabsId].type).toBe(TABS_TYPE);
expect(result[newTabId].type).toBe(TAB_TYPE);
});
it('should create a Row if the drag entity should be wrapped in a row', () => {
const result = newEntitiesFromDrop({
dropResult: {
destination: { id: 'a', index: 0 },
dragging: { type: CHART_TYPE },
source: { id: 'b', index: 0 },
},
layout: {
a: {
id: 'a',
type: DASHBOARD_GRID_TYPE,
children: [],
},
},
});
const newRowId = result.a.children[0];
const newChartId = result[newRowId].children[0];
expect(result.a.children).toHaveLength(1);
expect(Object.keys(result)).toHaveLength(3);
expect(result[newRowId].type).toBe(ROW_TYPE);
expect(result[newChartId].type).toBe(CHART_TYPE);
});
});

View File

@@ -0,0 +1,97 @@
/**
* 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 updateComponentParentsList from '../../../../src/dashboard/util/updateComponentParentsList';
import { DASHBOARD_ROOT_ID } from '../../../../src/dashboard/util/constants';
import {
dashboardLayout,
dashboardLayoutWithTabs,
} from '../fixtures/mockDashboardLayout';
describe('updateComponentParentsList', () => {
const emptyLayout = {
DASHBOARD_VERSION_KEY: 'v2',
GRID_ID: {
children: [],
id: 'GRID_ID',
type: 'GRID',
},
ROOT_ID: {
children: ['GRID_ID'],
id: 'ROOT_ID',
type: 'ROOT',
},
};
const gridLayout = {
...dashboardLayout.present,
};
const tabsLayout = {
...dashboardLayoutWithTabs.present,
};
it('should handle empty layout', () => {
const nextState = {
...emptyLayout,
};
updateComponentParentsList({
currentComponent: nextState[DASHBOARD_ROOT_ID],
layout: nextState,
});
expect(nextState.GRID_ID.parents).toEqual(['ROOT_ID']);
});
it('should handle grid layout', () => {
const nextState = {
...gridLayout,
};
updateComponentParentsList({
currentComponent: nextState[DASHBOARD_ROOT_ID],
layout: nextState,
});
expect(nextState.GRID_ID.parents).toEqual(['ROOT_ID']);
expect(nextState.CHART_ID.parents).toEqual([
'ROOT_ID',
'GRID_ID',
'ROW_ID',
'COLUMN_ID',
]);
});
it('should handle root level tabs', () => {
const nextState = {
...tabsLayout,
};
updateComponentParentsList({
currentComponent: nextState[DASHBOARD_ROOT_ID],
layout: nextState,
});
expect(nextState.GRID_ID.parents).toEqual(['ROOT_ID']);
expect(nextState.CHART_ID2.parents).toEqual([
'ROOT_ID',
'TABS_ID',
'TAB_ID2',
'ROW_ID2',
]);
});
});