diff --git a/superset-frontend/spec/javascripts/components/SupersetResourceSelect_spec.tsx b/superset-frontend/spec/javascripts/components/SupersetResourceSelect_spec.tsx
index 0d5859de1af..28509bfe4d7 100644
--- a/superset-frontend/spec/javascripts/components/SupersetResourceSelect_spec.tsx
+++ b/superset-frontend/spec/javascripts/components/SupersetResourceSelect_spec.tsx
@@ -22,6 +22,16 @@ import SupersetResourceSelect from 'src/component/SupersetResourceSelect';
describe('SupersetREsourceSelect', () => {
it('is a valid element', () => {
- expect(React.isValidElement()).toBe(true);
+ });
+ it('take in props', () => {
+ const selectProps = {
+ resource: 'dataset',
+ searchColumn: 'table_name',
+ transformItem: jest.fn(),
+ isMulti: false,
+ };
+ const wrapper = mount();
+ expect(wrapper.props().props.resource).toEqual('dataset');
+ });
+});
diff --git a/superset-frontend/src/dashboard/actions/nativeFilters.ts b/superset-frontend/src/dashboard/actions/nativeFilters.ts
index 2a090fcc591..3a1b7cfc29d 100644
--- a/superset-frontend/src/dashboard/actions/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/actions/nativeFilters.ts
@@ -19,7 +19,7 @@
import { SupersetClient } from '@superset-ui/core';
import { Dispatch } from 'redux';
-import { Filter } from '../components/filterConfig/types';
+import { Filter } from '../components/nativeFilters/types';
import { dashboardInfoChanged } from './dashboardInfo';
export const CREATE_FILTER_BEGIN = 'CREATE_FILTER_BEGIN';
@@ -97,6 +97,7 @@ export interface SelectFilterOptionAction {
filterId: string;
selectedValues: string[] | null;
}
+
/**
* Sets the selected option(s) for a given filter
* @param filterId the id of the native filter
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx
index 3772893bf96..7d05c342c06 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx
@@ -46,6 +46,7 @@ import {
DASHBOARD_ROOT_ID,
DASHBOARD_ROOT_DEPTH,
} from '../util/constants';
+import FilterBar from './nativeFilters/FilterBar';
const TABS_HEIGHT = 47;
const HEADER_HEIGHT = 67;
@@ -82,10 +83,13 @@ const StyledDashboardContent = styled.div`
padding-left: 0;
}
- & > div:first-child {
+ .grid-container {
+ /* without this, the grid will not get smaller upon toggling the builder panel on */
+ min-width: 0;
width: 100%;
flex-grow: 1;
position: relative;
+ margin: 24px 36px 24px;
}
.dashboard-component-chart-holder {
@@ -251,6 +255,9 @@ class DashboardBuilder extends React.Component {
+
+
+
{({ width }) => (
diff --git a/superset-frontend/src/dashboard/components/Header.jsx b/superset-frontend/src/dashboard/components/Header.jsx
index 4cc66384b22..2dea03525e8 100644
--- a/superset-frontend/src/dashboard/components/Header.jsx
+++ b/superset-frontend/src/dashboard/components/Header.jsx
@@ -48,7 +48,7 @@ import {
} from '../util/constants';
import setPeriodicRunner from '../util/setPeriodicRunner';
import { options as PeriodicRefreshOptions } from './RefreshIntervalModal';
-import CreateFilterButton from './filterConfig/CreateFilterButton';
+import CreateFilterButton from './nativeFilters/CreateFilterButton';
const propTypes = {
addSuccessToast: PropTypes.func.isRequired,
@@ -103,6 +103,14 @@ const defaultProps = {
// Styled Components
const StyledDashboardHeader = styled.div`
+ background: ${({ theme }) => theme.colors.grayscale.light5};
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 ${({ theme }) => theme.gridUnit * 6}px;
+ border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
+
button,
.fave-unfave-icon {
margin-left: ${({ theme }) => theme.gridUnit * 2}px;
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar.tsx
new file mode 100644
index 00000000000..3887e8f8c79
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar.tsx
@@ -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 { styled } from '@superset-ui/core';
+import React from 'react';
+import { Button, Form, Input } from 'src/common/components';
+import {
+ useFilterConfigurations,
+ useFilterSetter,
+ useFilterState,
+} from './state';
+import { Filter } from './types';
+
+const Bar = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 250px; // arbitrary...
+ flex-grow: 1;
+ padding: ${({ theme }) => theme.gridUnit * 4}px;
+ background: ${({ theme }) => theme.colors.grayscale.light5};
+ border-right: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
+`;
+
+interface FilterProps {
+ filter: Filter;
+}
+
+const FilterValue: React.FC = ({ filter }) => {
+ // THIS ONE IS BUILT TO THROW AWAY
+ // this is a temporary POC implementation just to get state hooked up.
+ // Please don't send this component to prod.
+ const { selectedValues } = useFilterState(filter.id);
+ const setSelectedValues = useFilterSetter(filter.id);
+
+ if (selectedValues) {
+ return (
+
+ {selectedValues.join(', ')}
+
+
+ );
+ }
+ return (
+
+
+
+
+
+ );
+};
+
+const FilterControl: React.FC = ({ filter }) => {
+ return (
+
+
{filter.name}
+
+
+ );
+};
+
+const FilterBar: React.FC = () => {
+ const filterConfigs = useFilterConfigurations();
+ return (
+
+ {filterConfigs.map(filter => (
+
+ ))}
+
+ );
+};
+
+export default FilterBar;
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/state.ts b/superset-frontend/src/dashboard/components/nativeFilters/state.ts
new file mode 100644
index 00000000000..cf46b4c688e
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/state.ts
@@ -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 { useCallback } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { selectFilterOption } from 'src/dashboard/actions/nativeFilters';
+import { getInitialFilterState } from 'src/dashboard/reducers/nativeFilters';
+import { Filter, FilterState } from './types';
+
+export function useFilterConfigurations() {
+ return useSelector(
+ state => state.dashboardInfo.metadata.filter_configuration || [],
+ );
+}
+
+export function useFilterState(id: string) {
+ return useSelector(
+ state => state.nativeFilters[id] || getInitialFilterState(id),
+ );
+}
+
+export function useFilterSetter(id: string) {
+ const dispatch = useDispatch();
+ return useCallback(
+ (values: string | string[] | null) =>
+ dispatch(selectFilterOption(id, values)),
+ [id, dispatch],
+ );
+}
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/types.ts b/superset-frontend/src/dashboard/components/nativeFilters/types.ts
index 0b28d61c7f4..8b5695a647c 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/types.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/types.ts
@@ -34,6 +34,10 @@ export interface Target {
export type FilterType = 'text' | 'date';
+/**
+ * This is a filter configuration object, stored in the dashboard's json metadata.
+ * The values here do not reflect the current state of the filter.
+ */
export interface Filter {
id: string; // randomly generated at filter creation
name: string;
@@ -47,3 +51,16 @@ export interface Filter {
// maybe someday support this?
// displayColumnsInOptions: Column[];
}
+
+/** Current state of the filter, stored in `nativeFilters` in redux */
+export type FilterState = {
+ id: string; // ties this filter state to the config object
+ optionsStatus: 'loading' | 'success' | 'fail';
+ options: string[] | null;
+ selectedValues: string[] | null;
+ /**
+ * If the config changes, the current options/values may no longer be valid.
+ * isDirty indicates that state.
+ */
+ isDirty: boolean;
+};
diff --git a/superset-frontend/src/dashboard/reducers/getInitialState.js b/superset-frontend/src/dashboard/reducers/getInitialState.js
index 5b2901c502c..5a32c97dd2c 100644
--- a/superset-frontend/src/dashboard/reducers/getInitialState.js
+++ b/superset-frontend/src/dashboard/reducers/getInitialState.js
@@ -22,6 +22,7 @@ import shortid from 'shortid';
import { CategoricalColorNamespace } from '@superset-ui/core';
import { initSliceEntities } from 'src/dashboard/reducers/sliceEntities';
+import { getInitialState as getInitialNativeFilterState } from 'src/dashboard/reducers/nativeFilters';
import { getParam } from 'src/modules/utils';
import { applyDefaultFormData } from 'src/explore/store';
import { buildActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
@@ -255,7 +256,9 @@ export default function getInitialState(bootstrapData) {
directPathToChild.push(directLinkComponentId);
}
- const nativeFilters = getInitialNativeFilterState(dashboard.metadata);
+ const nativeFilters = getInitialNativeFilterState(
+ dashboard.metadata.filter_configuration || [],
+ );
return {
datasources,
@@ -279,6 +282,7 @@ export default function getInitialState(bootstrapData) {
lastModifiedTime: dashboard.last_modified_time,
},
dashboardFilters,
+ nativeFilters,
dashboardState: {
sliceIds: Array.from(sliceIds),
directPathToChild,
diff --git a/superset-frontend/src/dashboard/reducers/index.js b/superset-frontend/src/dashboard/reducers/index.js
index afc77ce6c8e..61964de92e6 100644
--- a/superset-frontend/src/dashboard/reducers/index.js
+++ b/superset-frontend/src/dashboard/reducers/index.js
@@ -22,6 +22,7 @@ import charts from '../../chart/chartReducer';
import dashboardInfo from './dashboardInfo';
import dashboardState from './dashboardState';
import dashboardFilters from './dashboardFilters';
+import nativeFilters from './nativeFilters';
import datasources from './datasources';
import sliceEntities from './sliceEntities';
import dashboardLayout from './undoableDashboardLayout';
@@ -34,6 +35,7 @@ export default combineReducers({
datasources,
dashboardInfo,
dashboardFilters,
+ nativeFilters,
dashboardState,
dashboardLayout,
impressionId,
diff --git a/superset-frontend/src/dashboard/reducers/nativeFilters.ts b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
index f0e5b3aa69d..ef7ca6b6304 100644
--- a/superset-frontend/src/dashboard/reducers/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
@@ -25,21 +25,15 @@ import {
SelectFilterOptionAction,
SELECT_FILTER_OPTION,
} from '../actions/nativeFilters';
-import { Filter } from '../components/nativeFilters/types';
-
-export type FilterState = {
- optionsStatus: 'loading' | 'success' | 'fail';
- isDirty: boolean;
- options: string[] | null;
- selectedValues: string[] | null;
-};
+import { Filter, FilterState } from '../components/nativeFilters/types';
export type State = {
[filterId: string]: FilterState;
};
-function getInitialFilterState(): FilterState {
+export function getInitialFilterState(id: string): FilterState {
return {
+ id,
optionsStatus: 'loading',
isDirty: false,
options: null,
@@ -50,7 +44,7 @@ function getInitialFilterState(): FilterState {
export function getInitialState(filterConfig: Filter[]): State {
const filters = {};
filterConfig.forEach(filter => {
- filters[filter.id] = getInitialFilterState();
+ filters[filter.id] = getInitialFilterState(filter.id);
});
return filters;
}
@@ -59,9 +53,10 @@ export default function nativeFilterReducer(
filters: State = {},
action: Action,
) {
- const reducerMap = {
+ const actionMap = {
[SELECT_FILTER_OPTION]: (action: SelectFilterOptionAction): State => {
const filterState = filters[action.filterId];
+ console.log('reducer', action, filterState);
return {
...filters,
[action.filterId]: {
@@ -72,7 +67,7 @@ export default function nativeFilterReducer(
},
[CREATE_FILTER_BEGIN]: (action: CreateFilterBeginAction): State => {
- const filterState = getInitialFilterState();
+ const filterState = getInitialFilterState(action.filter.id);
filterState.isDirty = true;
return {
...filters,
@@ -92,8 +87,8 @@ export default function nativeFilterReducer(
},
};
- if (reducerMap[action.type]) {
- reducerMap[action.type](action);
+ if (actionMap[action.type]) {
+ return actionMap[action.type](action);
}
return filters;
}
diff --git a/superset-frontend/src/dashboard/stylesheets/builder.less b/superset-frontend/src/dashboard/stylesheets/builder.less
index 82b4c59ef0d..ec268601f20 100644
--- a/superset-frontend/src/dashboard/stylesheets/builder.less
+++ b/superset-frontend/src/dashboard/stylesheets/builder.less
@@ -21,16 +21,6 @@
color: @almost-black;
}
-.dashboard-header {
- background: @lightest;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- padding: 0 24px;
- box-shadow: 0 4px 4px 0 fade(@darkest, @opacity-light); /* @TODO color */
-}
-
/* only top-level tabs have popover, give it more padding to match header + tabs */
.dashboard > .with-popover-menu > .popover-menu {
left: 24px;
diff --git a/superset-frontend/src/dashboard/stylesheets/grid.less b/superset-frontend/src/dashboard/stylesheets/grid.less
index 03a03af9cad..5b793b96bdb 100644
--- a/superset-frontend/src/dashboard/stylesheets/grid.less
+++ b/superset-frontend/src/dashboard/stylesheets/grid.less
@@ -16,13 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
-.grid-container {
- position: relative;
- margin: 24px 36px 24px;
- /* without this, the grid will not get smaller upon toggling the builder panel on */
- min-width: 0;
- width: 100%;
-}
/* this is the ParentSize wrapper */
.grid-container > div:first-child {