mirror of https://github.com/apache/superset.git
[SIP-9] Introduce TypeScript (#6120)
* [SIP-9] Introduce TypeScript - Introduce TypeScript and co to both source and tests - Define alias for src directory in both webpack config and jest config so we can avoid using long relative paths like ../../src in both source and tests - Type check feature flags system to prevent typos of flag names - Change the feature flags system and the flags on window instead of populating them through the state tree. When introducing the first SCOPED_FILTER feature flag, it became too difficult to pipe the flags through the state initializers and layers of components and containers (the resulting code is hard to read and has a handful of methods taking an additional feature flag map parameter). Given that feature flags don't change throughout the life time of the app, it is better to leave them on window for easy access than piping them through the global state tree, which is meant to store the state of the app which changes frequently. - Add a barebone filter panel that only shows when the SCOPED_FILTER feature flag is on * Remove unnecessary dev-dependency on gl * - Adding linting for TypeScript files via tslint. - Fixing linting for Javascript files importing Typscript files - Also fix linting for Javascript files that now leverage the webpack alias for the src directory - up Typescript and type def versions * Rename src directory's webpack alias from @ to src to be more explicit.
This commit is contained in:
parent
9e6b171ee9
commit
5f1eaa49f2
|
@ -3,6 +3,7 @@
|
|||
*.pyc
|
||||
*.sqllite
|
||||
*.swp
|
||||
.cache-loader
|
||||
.coverage
|
||||
.DS_Store
|
||||
.eggs
|
||||
|
|
|
@ -292,6 +292,21 @@ npm run dev-server -- --supersetPort=8081
|
|||
|
||||
After adding or upgrading an NPM package by changing `package.json`, you must run `yarn install`, which will regenerate the `yarn.lock` file. Then, be sure to commit the new `yarn.lock` so that other users' builds are reproducible. See [the Yarn docs](https://yarnpkg.com/blog/2016/11/24/lockfiles-for-all/) for more information.
|
||||
|
||||
#### Feature flags
|
||||
|
||||
Superset supports a server-wide feature flag system, which eases the incremental development of features. To add a new feature flag, simply modify `superset_config.py` with something like the following:
|
||||
```
|
||||
FEATURE_FLAGS = {
|
||||
'SCOPED_FILTER': True,
|
||||
}
|
||||
```
|
||||
If you want to use the same flag in the client code, also add it to the FeatureFlag TypeScript enum in `superset/assets/src/featureFlags.ts`. For example,
|
||||
```
|
||||
export enum FeatureFlag {
|
||||
SCOPED_FILTER = 'SCOPED_FILTER',
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
All tests are carried out in [tox](http://tox.readthedocs.io/en/latest/index.html)
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
"func-names": 0,
|
||||
"react/jsx-no-bind": 0,
|
||||
"no-confusing-arrow": 0,
|
||||
|
||||
"jsx-a11y/no-static-element-interactions": 0,
|
||||
"jsx-a11y/anchor-has-content": 0,
|
||||
"react/require-default-props": 0,
|
||||
|
@ -40,6 +39,10 @@
|
|||
"react/no-string-refs": 0,
|
||||
"indent": 0,
|
||||
"no-multi-spaces": 0,
|
||||
"padded-blocks": 0
|
||||
"padded-blocks": 0,
|
||||
"import/extensions": [".js", ".jsx", ".ts", ".tsx", ".json"]
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": "webpack"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
module.exports = {
|
||||
testRegex: '\\/spec\\/.*_spec\\.jsx?$',
|
||||
testRegex: '\\/spec\\/.*_spec\\.(j|t)sx?$',
|
||||
moduleNameMapper: {
|
||||
'\\.(css|less)$': '<rootDir>/spec/__mocks__/styleMock.js',
|
||||
'\\.(gif|ttf|eot|svg)$': '<rootDir>/spec/__mocks__/fileMock.js',
|
||||
'^src/(.*)$': '<rootDir>/src/$1',
|
||||
},
|
||||
setupTestFrameworkScriptFile: '<rootDir>/spec/helpers/shim.js',
|
||||
testURL: 'http://localhost',
|
||||
collectCoverageFrom: ['src/**/*.{js,jsx}'],
|
||||
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'],
|
||||
coverageDirectory: '<rootDir>/coverage/',
|
||||
transform: {
|
||||
'^.+\\.jsx?$': 'babel-jest',
|
||||
'^.+\\.tsx?$': 'ts-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
||||
};
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
"dev-server": "webpack-dev-server --mode=development --progress",
|
||||
"prod": "node --max_old_space_size=4096 webpack --mode=production --colors --progress",
|
||||
"build": "NODE_ENV=production webpack --mode=production --colors --progress",
|
||||
"lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx .",
|
||||
"lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx .",
|
||||
"lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx . && tslint -c tslint.json ./src/**/*.ts{,x}",
|
||||
"lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx . && tslint -c tslint.json --fix ./src/**/*.ts{,x}",
|
||||
"sync-backend": "babel-node --presets env src/syncBackend.js",
|
||||
"cypress": "cypress",
|
||||
"cypress-debug": "cypress open --config watchForFileChanges=true"
|
||||
|
@ -131,6 +131,9 @@
|
|||
"viewport-mercator-project": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^23.3.5",
|
||||
"@types/react": "^16.4.18",
|
||||
"@types/react-dom": "^16.0.9",
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-core": "^6.10.4",
|
||||
"babel-eslint": "^8.2.2",
|
||||
|
@ -143,6 +146,7 @@
|
|||
"babel-polyfill": "^6.23.0",
|
||||
"babel-preset-airbnb": "^2.1.1",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"cache-loader": "^1.2.2",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"css-loader": "^1.0.0",
|
||||
"cypress": "^3.0.3",
|
||||
|
@ -151,6 +155,7 @@
|
|||
"eslint": "^4.19.0",
|
||||
"eslint-config-airbnb": "^15.0.1",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-import-resolver-webpack": "^0.10.1",
|
||||
"eslint-plugin-cypress": "^2.0.1",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eslint-plugin-jest": "^21.24.1",
|
||||
|
@ -161,7 +166,7 @@
|
|||
"exports-loader": "^0.7.0",
|
||||
"fetch-mock": "^7.0.0-alpha.6",
|
||||
"file-loader": "^1.1.11",
|
||||
"gl": "^4.0.4",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.9",
|
||||
"ignore-styles": "^5.0.1",
|
||||
"imports-loader": "^0.7.1",
|
||||
"jest": "^23.6.0",
|
||||
|
@ -181,7 +186,13 @@
|
|||
"speed-measure-webpack-plugin": "^1.2.3",
|
||||
"style-loader": "^0.21.0",
|
||||
"terser-webpack-plugin": "^1.1.0",
|
||||
"thread-loader": "^1.2.0",
|
||||
"transform-loader": "^0.2.3",
|
||||
"ts-jest": "^23.10.4",
|
||||
"ts-loader": "^5.2.0",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-react": "^3.6.0",
|
||||
"typescript": "^3.1.3",
|
||||
"url-loader": "^1.0.1",
|
||||
"webpack": "^4.19.0",
|
||||
"webpack-assets-manifest": "^3.0.1",
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Dashboard from '../../../../src/dashboard/containers/Dashboard';
|
||||
import getInitialState from '../../../../src/dashboard/reducers/getInitialState';
|
||||
|
||||
describe('Dashboard Container', () => {
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
let store;
|
||||
let wrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
const bootstrapData = {
|
||||
dashboard_data: {
|
||||
slices: [],
|
||||
metadata: {},
|
||||
},
|
||||
common: {
|
||||
feature_flags: {
|
||||
FOO_BAR: true,
|
||||
},
|
||||
conf: {},
|
||||
},
|
||||
};
|
||||
store = mockStore(getInitialState(bootstrapData), {});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<Dashboard />, { context: { store } });
|
||||
});
|
||||
|
||||
it('should set feature flags', () => {
|
||||
expect(wrapper.prop('isFeatureEnabled')('FOO_BAR')).toBe(true);
|
||||
});
|
||||
});
|
|
@ -1,11 +1,9 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { getFormDataFromControls, defaultControls }
|
||||
from '../../../../src/explore/store';
|
||||
import {
|
||||
ControlPanelsContainer,
|
||||
} from '../../../../src/explore/components/ControlPanelsContainer';
|
||||
import ControlPanelSection from '../../../../src/explore/components/ControlPanelSection';
|
||||
import { getFormDataFromControls, defaultControls } from 'src/explore/store';
|
||||
import { ControlPanelsContainer } from 'src/explore/components/ControlPanelsContainer';
|
||||
import ControlPanelSection from 'src/explore/components/ControlPanelSection';
|
||||
import * as featureFlags from 'src/featureFlags';
|
||||
|
||||
const defaultProps = {
|
||||
datasource_type: 'table',
|
||||
|
@ -18,12 +16,22 @@ const defaultProps = {
|
|||
|
||||
describe('ControlPanelsContainer', () => {
|
||||
let wrapper;
|
||||
let scopedFilterOn = false;
|
||||
const isFeatureEnabledMock = jest.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockImplementation(() => scopedFilterOn);
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<ControlPanelsContainer {...defaultProps} />);
|
||||
afterAll(() => {
|
||||
isFeatureEnabledMock.mockRestore();
|
||||
});
|
||||
|
||||
it('renders ControlPanelSections', () => {
|
||||
wrapper = shallow(<ControlPanelsContainer {...defaultProps} />);
|
||||
expect(wrapper.find(ControlPanelSection)).toHaveLength(6);
|
||||
});
|
||||
|
||||
it('renders filter panel when SCOPED_FILTER flag is on', () => {
|
||||
scopedFilterOn = true;
|
||||
wrapper = shallow(<ControlPanelsContainer {...defaultProps} />);
|
||||
expect(wrapper.find(ControlPanelSection)).toHaveLength(7);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,40 +3,39 @@ import configureStore from 'redux-mock-store';
|
|||
import thunk from 'redux-thunk';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import getInitialState from '../../../../src/explore/reducers/getInitialState';
|
||||
import ExploreViewContainer
|
||||
from '../../../../src/explore/components/ExploreViewContainer';
|
||||
import QueryAndSaveBtns
|
||||
from '../../../../src/explore/components/QueryAndSaveBtns';
|
||||
import ControlPanelsContainer
|
||||
from '../../../../src/explore/components/ControlPanelsContainer';
|
||||
import ChartContainer
|
||||
from '../../../../src/explore/components/ExploreChartPanel';
|
||||
import getInitialState from 'src/explore/reducers/getInitialState';
|
||||
import ExploreViewContainer from 'src/explore/components/ExploreViewContainer';
|
||||
import QueryAndSaveBtns from 'src/explore/components/QueryAndSaveBtns';
|
||||
import ControlPanelsContainer from 'src/explore/components/ControlPanelsContainer';
|
||||
import ChartContainer from 'src/explore/components/ExploreChartPanel';
|
||||
import * as featureFlags from 'src/featureFlags';
|
||||
|
||||
describe('ExploreViewContainer', () => {
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
let store;
|
||||
let wrapper;
|
||||
let isFeatureEnabledMock;
|
||||
|
||||
beforeAll(() => {
|
||||
isFeatureEnabledMock = jest.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockReturnValue(false);
|
||||
|
||||
const bootstrapData = {
|
||||
common: {
|
||||
feature_flags: {
|
||||
FOO_BAR: true,
|
||||
},
|
||||
conf: {},
|
||||
},
|
||||
datasource: {
|
||||
columns: [],
|
||||
},
|
||||
form_data: {
|
||||
datasource: {},
|
||||
},
|
||||
};
|
||||
store = mockStore(getInitialState(bootstrapData), {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
isFeatureEnabledMock.mockRestore();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<ExploreViewContainer />, {
|
||||
context: { store },
|
||||
|
@ -44,10 +43,6 @@ describe('ExploreViewContainer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should set feature flags', () => {
|
||||
expect(wrapper.prop('isFeatureEnabled')('FOO_BAR')).toBe(true);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(
|
||||
React.isValidElement(<ExploreViewContainer />),
|
||||
|
|
|
@ -5,35 +5,20 @@ import thunk from 'redux-thunk';
|
|||
import { shallow } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import App from '../../../src/SqlLab/components/App';
|
||||
import TabbedSqlEditors from '../../../src/SqlLab/components/TabbedSqlEditors';
|
||||
import getInitialState from '../../../src/SqlLab/getInitialState';
|
||||
import App from 'src/SqlLab/components/App';
|
||||
import TabbedSqlEditors from 'src/SqlLab/components/TabbedSqlEditors';
|
||||
import { sqlLabReducer } from 'src/SqlLab/reducers';
|
||||
|
||||
describe('SqlLab App', () => {
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
let store;
|
||||
const store = mockStore(sqlLabReducer(undefined, {}), {});
|
||||
let wrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
const bootstrapData = {
|
||||
common: {
|
||||
feature_flags: {
|
||||
FOO_BAR: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
store = mockStore(getInitialState(bootstrapData), {});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<App />, { context: { store } });
|
||||
});
|
||||
|
||||
it('should set feature flags', () => {
|
||||
expect(wrapper.prop('isFeatureEnabled')('FOO_BAR')).toBe(true);
|
||||
});
|
||||
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<App />)).toBe(true);
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Provider } from 'react-redux';
|
|||
import thunkMiddleware from 'redux-thunk';
|
||||
import { hot } from 'react-hot-loader';
|
||||
|
||||
import { initFeatureFlags } from 'src/featureFlags';
|
||||
import getInitialState from './getInitialState';
|
||||
import rootReducer from './reducers';
|
||||
import { initEnhancer } from '../reduxUtils';
|
||||
|
@ -18,6 +19,7 @@ appSetup();
|
|||
|
||||
const appContainer = document.getElementById('app');
|
||||
const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
|
||||
initFeatureFlags(bootstrapData.common.feature_flags);
|
||||
const state = getInitialState(bootstrapData);
|
||||
|
||||
const store = createStore(
|
||||
|
|
|
@ -9,7 +9,6 @@ import QueryAutoRefresh from './QueryAutoRefresh';
|
|||
import QuerySearch from './QuerySearch';
|
||||
import ToastPresenter from '../../messageToasts/containers/ToastPresenter';
|
||||
import * as Actions from '../actions';
|
||||
import { isFeatureEnabledCreator } from '../../featureFlags';
|
||||
|
||||
class App extends React.PureComponent {
|
||||
constructor(props) {
|
||||
|
@ -84,10 +83,6 @@ App.propTypes = {
|
|||
actions: PropTypes.object,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isFeatureEnabled: isFeatureEnabledCreator(state),
|
||||
});
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
|
@ -96,6 +91,6 @@ function mapDispatchToProps(dispatch) {
|
|||
|
||||
export { App };
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
mapDispatchToProps,
|
||||
)(App);
|
||||
|
|
|
@ -14,7 +14,6 @@ export default function getInitialState({ defaultDbId, ...restBootstrapData }) {
|
|||
};
|
||||
|
||||
return {
|
||||
featureFlags: restBootstrapData.common.feature_flags,
|
||||
sqlLab: {
|
||||
activeSouthPaneTab: 'Results',
|
||||
alerts: [],
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
getFromArr,
|
||||
addToArr,
|
||||
} from '../reduxUtils';
|
||||
import featureFlags from '../featureFlags';
|
||||
import { t } from '../locales';
|
||||
|
||||
export const sqlLabReducer = function (state = {}, action) {
|
||||
|
@ -280,7 +279,6 @@ export const sqlLabReducer = function (state = {}, action) {
|
|||
};
|
||||
|
||||
export default combineReducers({
|
||||
featureFlags,
|
||||
sqlLab: sqlLabReducer,
|
||||
messageToasts,
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ import { createStore, applyMiddleware, compose } from 'redux';
|
|||
import { Provider } from 'react-redux';
|
||||
import { hot } from 'react-hot-loader';
|
||||
|
||||
import { initFeatureFlags } from 'src/featureFlags';
|
||||
import { initEnhancer } from '../reduxUtils';
|
||||
import { appSetup } from '../common';
|
||||
import DashboardContainer from './containers/Dashboard';
|
||||
|
@ -14,6 +15,7 @@ appSetup();
|
|||
|
||||
const appContainer = document.getElementById('app');
|
||||
const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
|
||||
initFeatureFlags(bootstrapData.common.feature_flags);
|
||||
const initState = getInitialState(bootstrapData);
|
||||
|
||||
const store = createStore(
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { isFeatureEnabledCreator } from '../../featureFlags';
|
||||
import Dashboard from '../components/Dashboard';
|
||||
|
||||
import {
|
||||
|
@ -23,7 +22,6 @@ function mapStateToProps(state) {
|
|||
} = state;
|
||||
|
||||
return {
|
||||
isFeatureEnabled: isFeatureEnabledCreator(state),
|
||||
initMessages: dashboardInfo.common.flash_messages,
|
||||
timeout: dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT,
|
||||
userId: dashboardInfo.userId,
|
||||
|
|
|
@ -138,7 +138,6 @@ export default function(bootstrapData) {
|
|||
};
|
||||
|
||||
return {
|
||||
featureFlags: common.feature_flags,
|
||||
datasources,
|
||||
sliceEntities: { ...initSliceEntities, slices, isLoading: false },
|
||||
charts: chartQueries,
|
||||
|
|
|
@ -5,14 +5,12 @@ import dashboardState from './dashboardState';
|
|||
import datasources from './datasources';
|
||||
import sliceEntities from './sliceEntities';
|
||||
import dashboardLayout from '../reducers/undoableDashboardLayout';
|
||||
import featureFlags from '../../featureFlags';
|
||||
import messageToasts from '../../messageToasts/reducers';
|
||||
|
||||
const dashboardInfo = (state = {}) => state;
|
||||
const impressionId = (state = '') => state;
|
||||
|
||||
export default combineReducers({
|
||||
featureFlags,
|
||||
charts,
|
||||
datasources,
|
||||
dashboardInfo,
|
||||
|
|
|
@ -4,6 +4,7 @@ import { createStore, applyMiddleware, compose } from 'redux';
|
|||
import { Provider } from 'react-redux';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
import { initFeatureFlags } from 'src/featureFlags';
|
||||
import { initEnhancer } from '../reduxUtils';
|
||||
import ToastPresenter from '../messageToasts/containers/ToastPresenter';
|
||||
import ExploreViewContainer from './components/ExploreViewContainer';
|
||||
|
@ -18,6 +19,7 @@ appSetup();
|
|||
|
||||
const exploreViewContainer = document.getElementById('app');
|
||||
const bootstrapData = JSON.parse(exploreViewContainer.getAttribute('data-bootstrap'));
|
||||
initFeatureFlags(bootstrapData.common.feature_flags);
|
||||
const initState = getInitialState(bootstrapData);
|
||||
|
||||
const store = createStore(
|
||||
|
|
|
@ -15,7 +15,6 @@ import { chartPropShape } from '../../dashboard/util/propShapes';
|
|||
import * as exploreActions from '../actions/exploreActions';
|
||||
import * as saveModalActions from '../actions/saveModalActions';
|
||||
import * as chartActions from '../../chart/chartAction';
|
||||
import { isFeatureEnabledCreator } from '../../featureFlags';
|
||||
import { Logger, ActionLog, EXPLORE_EVENT_NAMES, LOG_ACTIONS_MOUNT_EXPLORER } from '../../logger';
|
||||
|
||||
const propTypes = {
|
||||
|
@ -308,7 +307,6 @@ function mapStateToProps(state) {
|
|||
const chartKey = Object.keys(charts)[0];
|
||||
const chart = charts[chartKey];
|
||||
return {
|
||||
isFeatureEnabled: isFeatureEnabledCreator(state),
|
||||
isDatasourceMetaLoading: explore.isDatasourceMetaLoading,
|
||||
datasource: explore.datasource,
|
||||
datasource_type: explore.datasource.type,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import * as React from 'react';
|
||||
|
||||
export default function FilterPanel() {
|
||||
return (
|
||||
<div>
|
||||
test filter
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -19,6 +19,7 @@ import ViewportControl from './ViewportControl';
|
|||
import VizTypeControl from './VizTypeControl';
|
||||
import MetricsControl from './MetricsControl';
|
||||
import AdhocFilterControl from './AdhocFilterControl';
|
||||
import FilterPanel from './FilterPanel';
|
||||
|
||||
const controlMap = {
|
||||
AnnotationLayerControl,
|
||||
|
@ -42,5 +43,6 @@ const controlMap = {
|
|||
VizTypeControl,
|
||||
MetricsControl,
|
||||
AdhocFilterControl,
|
||||
FilterPanel,
|
||||
};
|
||||
export default controlMap;
|
||||
|
|
|
@ -1880,6 +1880,10 @@ export const controls = {
|
|||
provideFormDataToProps: true,
|
||||
},
|
||||
|
||||
filters: {
|
||||
type: 'FilterPanel',
|
||||
},
|
||||
|
||||
slice_id: {
|
||||
type: 'HiddenControl',
|
||||
label: t('Chart ID'),
|
||||
|
|
|
@ -31,7 +31,6 @@ export default function getInitialState(bootstrapData) {
|
|||
const chartKey = getChartKey(bootstrappedState);
|
||||
|
||||
return {
|
||||
featureFlags: bootstrapData.common.feature_flags,
|
||||
charts: {
|
||||
[chartKey]: {
|
||||
id: chartKey,
|
||||
|
|
|
@ -3,13 +3,11 @@ import { combineReducers } from 'redux';
|
|||
import charts from '../../chart/chartReducer';
|
||||
import saveModal from './saveModalReducer';
|
||||
import explore from './exploreReducer';
|
||||
import featureFlags from '../../featureFlags';
|
||||
import messageToasts from '../../messageToasts/reducers';
|
||||
|
||||
const impressionId = (state = '') => state;
|
||||
|
||||
export default combineReducers({
|
||||
featureFlags,
|
||||
charts,
|
||||
saveModal,
|
||||
explore,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* and associated with each and every visualization type.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
||||
import { D3_TIME_FORMAT_OPTIONS } from './controls';
|
||||
import * as v from './validators';
|
||||
import { t } from '../locales';
|
||||
|
@ -41,6 +42,13 @@ export const sections = {
|
|||
['time_range'],
|
||||
],
|
||||
},
|
||||
filters: {
|
||||
label: t('Filters'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['filters'],
|
||||
],
|
||||
},
|
||||
annotations: {
|
||||
label: t('Annotations and Layers'),
|
||||
expanded: true,
|
||||
|
@ -1919,6 +1927,7 @@ export function sectionsToRender(vizType, datasourceType) {
|
|||
return [].concat(
|
||||
sectionsCopy.datasourceAndVizType,
|
||||
datasourceType === 'table' ? sectionsCopy.sqlaTimeSeries : sectionsCopy.druidTimeSeries,
|
||||
isFeatureEnabled(FeatureFlag.SCOPED_FILTER) ? sectionsCopy.filters : undefined,
|
||||
viz.controlPanelSections,
|
||||
).filter(section => section);
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
// A higher-order function that takes the redux state tree and returns a
|
||||
// `isFeatureEnabled` function which takes a feature and returns whether it is enabled.
|
||||
// Note that we assume the featureFlags subtree is at the root of the redux state tree.
|
||||
export function isFeatureEnabledCreator(state) {
|
||||
return feature => !!state.featureFlags[feature];
|
||||
}
|
||||
|
||||
// Feature flags are not altered throughout the life time of the app
|
||||
export default function featureFlagsReducer(state = {}) {
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// We can codegen the enum definition based on a list of supported flags that we
|
||||
// check into source control. We're hardcoding the supported flags for now.
|
||||
export enum FeatureFlag {
|
||||
SCOPED_FILTER = 'SCOPED_FILTER',
|
||||
}
|
||||
|
||||
export type FeatureFlagMap = {
|
||||
[key in FeatureFlag]?: boolean;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
featureFlags: FeatureFlagMap;
|
||||
}
|
||||
}
|
||||
|
||||
export function initFeatureFlags(featureFlags: FeatureFlagMap) {
|
||||
window.featureFlags = featureFlags || {};
|
||||
}
|
||||
|
||||
export function isFeatureEnabled(feature: FeatureFlag) {
|
||||
return !!window.featureFlags[feature];
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "./dist",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react",
|
||||
"moduleResolution": "node",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"importHelpers": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./src/**/*", "./spec/**/*"]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": ["tslint:recommended", "tslint-react"],
|
||||
"jsRules": {
|
||||
},
|
||||
"rules": {
|
||||
"interface-name" : [true, "never-prefix"],
|
||||
"quotemark": [true, "single"]
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
const os = require('os');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
|
@ -7,6 +8,7 @@ const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
|||
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const WebpackAssetsManifest = require('webpack-assets-manifest');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
|
||||
// Parse command-line arguments
|
||||
const parsedArgs = require('minimist')(process.argv.slice(2));
|
||||
|
@ -45,6 +47,11 @@ const plugins = [
|
|||
new webpack.DefinePlugin({
|
||||
'process.env.WEBPACK_MODE': JSON.stringify(mode),
|
||||
}),
|
||||
|
||||
// runs type checking on a separate process to speed up the build
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
checkSyntacticErrors: true,
|
||||
}),
|
||||
];
|
||||
|
||||
if (isDevMode) {
|
||||
|
@ -102,8 +109,12 @@ const config = {
|
|||
},
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx'],
|
||||
alias: {
|
||||
src: path.resolve(APP_DIR, './src'),
|
||||
},
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
||||
},
|
||||
context: APP_DIR, // to automatically find tsconfig.json
|
||||
module: {
|
||||
// Uglifying mapbox-gl results in undefined errors, see
|
||||
// https://github.com/mapbox/mapbox-gl-js/issues/4359#issuecomment-288001933
|
||||
|
@ -113,6 +124,27 @@ const config = {
|
|||
test: /datatables\.net.*/,
|
||||
loader: 'imports-loader?define=>false',
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: [
|
||||
{ loader: 'cache-loader' },
|
||||
{
|
||||
loader: 'thread-loader',
|
||||
options: {
|
||||
// there should be 1 cpu for the fork-ts-checker-webpack-plugin
|
||||
workers: os.cpus().length - 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
// transpile only in happyPack mode
|
||||
// type checking is done via fork-ts-checker-webpack-plugin
|
||||
happyPackMode: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue