diff --git a/superset-frontend/src/cccs-viz/plugins/index.ts b/superset-frontend/src/cccs-viz/plugins/index.ts index f8453be3c882f..916006072772f 100644 --- a/superset-frontend/src/cccs-viz/plugins/index.ts +++ b/superset-frontend/src/cccs-viz/plugins/index.ts @@ -29,3 +29,4 @@ export { default as AtAGlanceUserIdChartPlugin } from './plugin-chart-at-a-glanc export { default as AtAGlanceUserIDSasChartPlugin } from './plugin-chart-at-a-glance-user-id-sas/src/plugin'; export { default as ApplicationLinksChartPlugin } from './plugin-chart-application-links/src/plugin'; export { default as IFrameVisualizationChartPlugin } from './plugin-chart-iframe/src/plugin'; +export { default as DatasetExplorerChartPlugin } from './plugin-chart-dataset-explorer/src/plugin'; diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/advancedDataTypeValueControl/index.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/advancedDataTypeValueControl/index.tsx new file mode 100644 index 0000000000000..eee9e5dcaa6c6 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/advancedDataTypeValueControl/index.tsx @@ -0,0 +1,131 @@ +import React, { useEffect, useState } from 'react'; +import { ensureIsArray, withTheme } from '@superset-ui/core'; +import { connect } from 'react-redux'; +import SelectControl from 'src/explore/components/controls/SelectControl'; +import useAdvancedDataTypes from 'src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/useAdvancedDataTypes'; + +export interface Props { + colorScheme: string; + annotationError: object; + annotationQuery: object; + vizType: string; + theme: any; + validationErrors: string[]; + externalValidationErrors: string[]; + name: string; + actions: object; + label: string; + value?: any[] | any; + datasource: any; + multi: boolean; + freeForm: boolean; + advancedDataType: string; + onChange: (values: any, errors: any[]) => void; + disabled: boolean; + description: any; +} + +const AdvancedDataTypeValueControlValueControl: React.FC = ({ + onChange, + externalValidationErrors, + datasource, + multi, + freeForm, + advancedDataType, + label, + disabled, + value = [], + description, +}) => { + const [rawValues, setRawValues] = useState([]); + const [validationErrors, setValidationErrors] = useState([]); + const [currentAdvancedDataType, setCurrentAdvancedDataType] = + useState(); + + const { + advancedDataTypesState, + subjectAdvancedDataType, + fetchAdvancedDataTypeValueCallback, + } = useAdvancedDataTypes(() => {}); + + const onChangeWrapper = (selection: any) => { + setValidationErrors([...validationErrors, 'Validation in progress']); + setRawValues(selection); + }; + + // clear selection on advancedDataType change + useEffect(() => { + const rawData = value[0] ? value[0].rawData : []; + onChangeWrapper( + (currentAdvancedDataType && + currentAdvancedDataType !== advancedDataType) || + !advancedDataType + ? [] + : rawData, + ); + setCurrentAdvancedDataType(advancedDataType); + }, [advancedDataType]); + + useEffect(() => { + const data = + advancedDataTypesState.parsedAdvancedDataType.length > 0 && + advancedDataTypesState.parsedAdvancedDataType.split(',').length > 0 + ? { + data: advancedDataTypesState.values, + columns: datasource.columns + .filter((col: any) => col.advanced_data_type === advancedDataType) + .map((col: any) => col.column_name), + rawData: rawValues, + } + : { data: [], columns: [], rawData: [] }; + onChange(data, validationErrors); + }, [advancedDataTypesState, validationErrors]); + + useEffect(() => { + fetchAdvancedDataTypeValueCallback( + rawValues, + advancedDataTypesState, + ensureIsArray(advancedDataType)[0], + ); + }, [advancedDataType, rawValues, subjectAdvancedDataType]); + + useEffect(() => { + setValidationErrors( + advancedDataTypesState.errorMessage + ? [advancedDataTypesState.errorMessage] + : [], + ); + }, [advancedDataTypesState]); + + return ( + + ); +}; + +function mapStateToProps({ charts, explore }: any) { + return { + // eslint-disable-next-line camelcase + colorScheme: explore.controls?.color_scheme?.value, + vizType: explore.controls.viz_type.value, + }; +} + +const themedAdvancedDataTypeValueControlValueControl = withTheme( + AdvancedDataTypeValueControlValueControl, +); + +export default connect(mapStateToProps)( + themedAdvancedDataTypeValueControlValueControl, +); diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/changeDatasourceButton/index.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/changeDatasourceButton/index.tsx new file mode 100644 index 0000000000000..755d56fa218aa --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/changeDatasourceButton/index.tsx @@ -0,0 +1,73 @@ +import React, { useState } from 'react'; +import { ChangeDatasourceModal } from 'src/components/Datasource'; +import { withTheme } from '@superset-ui/core'; +import { connect, useDispatch } from 'react-redux'; +import Button from 'src/components/Button'; +import { updateFormDataByDatasource } from 'src/explore/actions/exploreActions'; + +export interface Props { + colorScheme: string; + annotationError: object; + annotationQuery: object; + vizType: string; + theme: any; + validationErrors: string[]; + name: string; + actions: object; + label: string; + value?: object[]; + datasource: any; + onChange: (a: any) => void; +} + +const ChangeDatasourceButtonControll: React.FC = ({ + onChange, + datasource, +}) => { + const dispatch = useDispatch(); + const [showChangeDatasourceModal, setShowChangeDatasourceModal] = + useState(false); + + const toggleChangeDatasourceModal = () => { + setShowChangeDatasourceModal(!showChangeDatasourceModal); + }; + const onDatasourceSave = (new_datasource: any) => { + dispatch(updateFormDataByDatasource(datasource, new_datasource)); + }; + const onChangeWrapper = (a: any) => { + onChange(a); + }; + + return ( + <> + + {showChangeDatasourceModal && ( + + )} + + ); +}; + +function mapStateToProps({ explore }: any) { + return { + // eslint-disable-next-line camelcase + colorScheme: explore.controls?.color_scheme?.value, + vizType: explore.controls.viz_type.value, + }; +} + +const themedChangeDatasourceButtonControll = withTheme( + ChangeDatasourceButtonControll, +); + +export default connect(mapStateToProps)(themedChangeDatasourceButtonControll); diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/datetimeControl/index.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/datetimeControl/index.tsx new file mode 100644 index 0000000000000..3d106234261ef --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/datetimeControl/index.tsx @@ -0,0 +1,131 @@ +import React, { useEffect, useState } from 'react'; +import { SLOW_DEBOUNCE, SupersetClient, t, withTheme } from '@superset-ui/core'; +import { + buildTimeRangeString, + formatTimeRange, +} from 'src/explore/components/controls/DateFilterControl/utils'; +import { Input } from 'src/components/Input'; +import { connect } from 'react-redux'; +import rison from 'rison'; +import ControlHeader from 'src/explore/components/ControlHeader'; +import { getClientErrorObject } from 'src/utils/getClientErrorObject'; +import { useDebouncedEffect } from 'src/explore/exploreUtils'; + +export interface Props { + colorScheme: string; + annotationError: object; + annotationQuery: object; + vizType: string; + theme: any; + validationErrors: string[]; + name: string; + actions: object; + label: string; + value?: object[]; + onChange: (value: any, errors: any[]) => void; + default: string; + disabled: boolean; +} + +const SEPARATOR = ' : '; +const fetchTimeRange = async (timeRange: string) => { + const query = rison.encode_uri(timeRange); + const endpoint = `/api/v1/time_range/?q=${query}`; + try { + const response = await SupersetClient.get({ endpoint }); + const timeRangeString = buildTimeRangeString( + response?.json?.result?.since || '', + response?.json?.result?.until || '', + ); + return { + value: formatTimeRange(timeRangeString), + }; + } catch (response) { + const clientError = await getClientErrorObject(response); + return { + // keep labelling consistent in error messages + error: (clientError.message || clientError.error) + .replace('From date', 'Start date') + .replace('to date', 'end date'), + }; + } +}; + +const DatetimeControl: React.FC = props => { + const [timeRange, setTimeRange] = useState(props.default); + const [validationErrors, setValidationErrors] = useState([]); + const [actualTimeRange, setactualTimeRange] = useState(); + + const [since, until] = timeRange.split(SEPARATOR); + + useEffect(() => { + props.onChange(timeRange, validationErrors); + }, [timeRange, since, until, validationErrors]); + + function onChange(control: 'since' | 'until', value: string) { + if (control === 'since') { + setTimeRange(`${value}${SEPARATOR}${until}`); + } else { + setTimeRange(`${since}${SEPARATOR}${value}`); + } + } + + useDebouncedEffect( + () => { + fetchTimeRange(timeRange) + .then(value => { + setactualTimeRange( + value?.value ? `Actual Time Range ${value?.value}` : '', + ); + setValidationErrors(value?.error ? [value?.error] : []); + }) + .catch(error => { + setValidationErrors(error); + }); + }, + SLOW_DEBOUNCE, + [timeRange], + ); + + const headerProps = { + name: props.name, + label: props.label, + validationErrors, + description: actualTimeRange, + hovered: true, + }; + + return ( + <> + +
{t('START (INCLUSIVE)')}
+ onChange('since', e.target.value)} + disabled={props.disabled} + /> +
{t('END (EXCLUSIVE)')}
+ onChange('until', e.target.value)} + disabled={props.disabled} + /> + + ); +}; + +// Tried to hook this up through stores/control.jsx instead of using redux +// directly, could not figure out how to get access to the color_scheme +function mapStateToProps({ charts, explore }: any) { + return { + // eslint-disable-next-line camelcase + colorScheme: explore.controls?.color_scheme?.value, + vizType: explore.controls.viz_type.value, + }; +} + +const themedDrillActionConfigControl = withTheme(DatetimeControl); + +export default connect(mapStateToProps)(themedDrillActionConfigControl); diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/plugin/transformProps.ts index c35777b1dccd0..787772931ebac 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/plugin/transformProps.ts @@ -33,6 +33,9 @@ import { DEFAULT_FORM_DATA, } from '../types'; +import {rendererMap, formatIpV4} from '../utils/advancedDataTypes' + + export default function transformProps(chartProps: CccsGridChartProps) { /** * This function is called after a successful response has been @@ -176,22 +179,7 @@ export default function transformProps(chartProps: CccsGridChartProps) { }, sortingColumnMap); - // Key is column advanced type, value is renderer name - const rendererMap = { - IPV4: 'ipv4ValueRenderer', - IPV6: 'ipv6ValueRenderer', - DOMAIN: 'domainValueRenderer', - COUNTRY: 'countryValueRenderer', - JSON: 'jsonValueRenderer', - }; - - const formatIpV4 = (v: any) => { - const converted = `${(v >> 24) & 0xff}.${(v >> 16) & 0xff}.${ - (v >> 8) & 0xff - }.${v & 0xff}`; - return converted; - }; - + const valueFormatter = (params: any) => { if ( params.value != null && diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/utils/advancedDataTypes.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/utils/advancedDataTypes.ts new file mode 100644 index 0000000000000..0165b55f025b1 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/utils/advancedDataTypes.ts @@ -0,0 +1,16 @@ + +// Key is column advanced type, value is renderer name +export const rendererMap = { + IPV4: 'ipv4ValueRenderer', + IPV6: 'ipv6ValueRenderer', + DOMAIN: 'domainValueRenderer', + COUNTRY: 'countryValueRenderer', + JSON: 'jsonValueRenderer', +}; + +export const formatIpV4 = (v: any) => { + const converted = `${(v >> 24) & 0xff}.${(v >> 16) & 0xff}.${ + (v >> 8) & 0xff + }.${v & 0xff}`; + return converted; + }; diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/images/Table1.png b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/images/Table1.png new file mode 100644 index 0000000000000..1c9791782465a Binary files /dev/null and b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/images/Table1.png differ diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/images/thumbnail.png b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/images/thumbnail.png new file mode 100644 index 0000000000000..8a06b78ed3017 Binary files /dev/null and b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/images/thumbnail.png differ diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/index.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/index.ts new file mode 100644 index 0000000000000..7a3040d0f7605 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/index.ts @@ -0,0 +1,27 @@ +/** + * 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. + */ +// eslint-disable-next-line import/prefer-default-export +export { default as CccsDataSetExplorerPlugin } from './plugin'; +/** + * Note: this file exports the default export from CccsGrid.tsx. + * If you want to export multiple visualization modules, you will need to + * either add additional plugin folders (similar in structure to ./plugin) + * OR export multiple instances of `ChartPlugin` extensions in ./plugin/index.ts + * which in turn load exports from CccsGrid.tsx + */ diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/buildQuery.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/buildQuery.ts new file mode 100644 index 0000000000000..80a02fab53ad7 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/buildQuery.ts @@ -0,0 +1,236 @@ +/** + * 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 { + buildQueryContext, + ensureIsArray, + getMetricLabel, + PostProcessingRule, + QueryMode, + QueryObject, + removeDuplicates, +} from '@superset-ui/core'; +import { BuildQuery } from '@superset-ui/core/src/chart/registries/ChartBuildQueryRegistrySingleton'; +import { CccsGridQueryFormData, DEFAULT_FORM_DATA } from '../types'; +/** + * The buildQuery function is used to create an instance of QueryContext that's + * sent to the chart data endpoint. In addition to containing information of which + * datasource to use, it specifies the type (e.g. full payload, samples, query) and + * format (e.g. CSV or JSON) of the result and whether or not to force refresh the data from + * the datasource as opposed to using a cached copy of the data, if available. + * + * More importantly though, QueryContext contains a property `queries`, which is an array of + * QueryObjects specifying individual data requests to be made. A QueryObject specifies which + * columns, metrics and filters, among others, to use during the query. Usually it will be enough + * to specify just one query based on the baseQueryObject, but for some more advanced use cases + * it is possible to define post processing operations in the QueryObject, or multiple queries + * if a viz needs multiple different result sets. + */ +export function getQueryMode(formData: CccsGridQueryFormData) { + const { query_mode: mode } = formData; + if (mode === QueryMode.aggregate || mode === QueryMode.raw) { + return mode; + } + const rawColumns = formData?.all_columns; + const hasRawColumns = rawColumns && rawColumns.length > 0; + return hasRawColumns ? QueryMode.raw : QueryMode.aggregate; +} + +const isRange = (data: any) => { + const v = data.hasOwnProperty('start'); + return v; +}; + +const buildQuery: BuildQuery = ( + formData: CccsGridQueryFormData, + options: any, +) => { + const queryMode = getQueryMode(formData); + const sortByMetric = ensureIsArray(formData.timeseries_limit_metric)[0]; + let formDataCopy = { + ...formData, + ...DEFAULT_FORM_DATA, + }; + const { percent_metrics: percentMetrics, order_desc: orderDesc = false } = + formData; + // never include time in raw records mode + if (queryMode === QueryMode.raw) { + formDataCopy = { + ...formData, + ...DEFAULT_FORM_DATA, + include_time: false, + }; + } + + formDataCopy.adhoc_filters = formData.adhoc_filters_no_date_default; + + return buildQueryContext(formDataCopy, baseQueryObject => { + let { metrics, orderby = [] } = baseQueryObject; + const postProcessing: PostProcessingRule[] = []; + + if (queryMode === QueryMode.aggregate) { + metrics = metrics || []; + if (sortByMetric) { + orderby = [[sortByMetric, !orderDesc]]; + } else if (metrics?.length > 0) { + // default to ordering by first metric in descending order + orderby = [[metrics[0], false]]; + } + // add postprocessing for percent metrics only when in aggregation mode + if (percentMetrics && percentMetrics.length > 0) { + const percentMetricLabels = removeDuplicates( + percentMetrics.map(getMetricLabel), + ); + metrics = removeDuplicates( + metrics.concat(percentMetrics), + getMetricLabel, + ); + postProcessing.push({ + operation: 'contribution', + options: { + columns: percentMetricLabels as string[], + rename_columns: percentMetricLabels.map(x => `%${x}`), + }, + }); + } + } + + const moreProps: Partial = {}; + const ownState = options?.ownState ?? {}; + if (formDataCopy.server_pagination) { + moreProps.row_limit = + ownState.pageSize ?? formDataCopy.server_page_length; + moreProps.row_offset = + (ownState.currentPage ?? 0) * (ownState.pageSize ?? 0); + } + + const { datasource_config } = formData; + formDataCopy.datasource = datasource_config || formDataCopy.datasource; + + const { advanced_data_type_value } = formData; + const { advanced_data_type_selection } = formData; + let filter = []; + if (advanced_data_type_selection.length > 0) { + // in the case of ipv4s sometimes they can be ranges and not simple values + // this will be handled in the advanced data type definition in the future + // to avoid this complex logic + let simple: any[] = []; + let range: any[] = []; + advanced_data_type_value[0].data.map((d: any) => { + if (isRange(d)) { + range = [...range, d]; + } else { + simple = [...simple, d]; + } + return d; + }); + filter = advanced_data_type_value[0].columns.reduce( + (arr: string[], curr: string) => { + const new_arr = [ + ...arr, + ...range.map(r => `${curr} BETWEEN ${r.start} AND ${r.end}`), + ]; + return simple.length > 0 + ? new_arr.concat(`${curr} IN (${simple.map((s: any) => `${s}`)})`) + : new_arr; + }, + [], + ); + } + const queryObject = { + ...baseQueryObject, + formData: formDataCopy, + orderby, + metrics, + post_processing: postProcessing, + ...moreProps, + extras: { + ...baseQueryObject.extras, + where: filter.join(' OR '), + }, + }; + + // Because we use same buildQuery for all table on the page we need split them by id + options?.hooks?.setCachedChanges({ + [formData.slice_id]: queryObject.filters, + }); + + const extraQueries: QueryObject[] = []; + if ( + metrics?.length && + formData.show_totals && + queryMode === QueryMode.aggregate + ) { + extraQueries.push({ + ...queryObject, + columns: [], + row_limit: 0, + row_offset: 0, + post_processing: [], + }); + } + + const interactiveGroupBy = formData.extra_form_data?.interactive_groupby; + if (interactiveGroupBy && queryObject.columns) { + queryObject.columns = [ + ...new Set([...queryObject.columns, ...interactiveGroupBy]), + ]; + } + + if (formData.server_pagination) { + return [ + { ...queryObject }, + { + ...queryObject, + row_limit: 0, + row_offset: 0, + post_processing: [], + is_rowcount: true, + }, + ...extraQueries, + ]; + } + + return [queryObject, ...extraQueries]; + }); +}; + +// Use this closure to cache changing of external filters, if we have server pagination we need reset page to 0, after +// external filter changed +export const cachedBuildQuery = (): BuildQuery => { + let cachedChanges: any = {}; + const setCachedChanges = (newChanges: any) => { + cachedChanges = { ...cachedChanges, ...newChanges }; + }; + + return (formData: any, options: any) => + buildQuery( + { ...formData }, + { + extras: { cachedChanges }, + ownState: options?.ownState ?? {}, + hooks: { + ...options?.hooks, + setDataMask: () => {}, + setCachedChanges, + }, + }, + ); +}; + +export default cachedBuildQuery(); diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/controlPanel.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/controlPanel.tsx new file mode 100644 index 0000000000000..26269084b8ece --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/controlPanel.tsx @@ -0,0 +1,398 @@ +/** + * 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 React from 'react'; + +import { + t, + QueryMode, + ensureIsArray, + validateNonEmpty, +} from '@superset-ui/core'; +import { + Dataset, + ControlConfig, + ControlStateMapping, + ControlPanelConfig, + ControlPanelsContainerProps, + QueryModeLabel, + sharedControls, + ControlPanelState, + ControlState, + formatSelectOptions, + ColumnMeta, +} from '@superset-ui/chart-controls'; +import { StyledColumnOption } from 'src/explore/components/optionRenderers'; + +import { bootstrapData } from 'src/preamble'; +import AdhocFilterControl from 'src/explore/components/controls/FilterControl/AdhocFilterControl'; +import ChangeDataSourceButton from '../../../plugin-chart-cccs-grid/src/components/controls/changeDatasourceButton'; +import AdvancedDataTypeValue from '../../../plugin-chart-cccs-grid/src/components/controls/advancedDataTypeValueControl'; +import DateTimeControl from '../../../plugin-chart-cccs-grid/src/components/controls/datetimeControl'; + +export const PAGE_SIZE_OPTIONS = formatSelectOptions([ + [0, t('page_size.all')], + 10, + 20, + 50, + 100, + 200, +]); + +function getQueryMode(controls: ControlStateMapping): QueryMode { + const mode = controls?.query_mode?.value; + if (mode === QueryMode.aggregate || mode === QueryMode.raw) { + return mode as QueryMode; + } + return QueryMode.raw; +} + +/** + * Visibility check + */ +function isQueryMode(mode: QueryMode) { + return ({ controls }: Pick) => + getQueryMode(controls) === mode; +} + +const isAggMode = isQueryMode(QueryMode.aggregate); +const isRawMode = isQueryMode(QueryMode.raw); +const isTimeColumnSelected = ({ + controls, +}: Pick) => + controls.granularity_sqla && !!controls.granularity_sqla.value; + +const queryMode: ControlConfig<'RadioButtonControl'> = { + type: 'RadioButtonControl', + label: t('Query mode'), + default: QueryMode.raw, + value: QueryMode.raw, + options: [ + [QueryMode.aggregate, QueryModeLabel[QueryMode.aggregate]], + [QueryMode.raw, QueryModeLabel[QueryMode.raw]], + ], + mapStateToProps: ({ controls }) => ({ value: getQueryMode(controls) }), + rerender: ['columns', 'groupby'], +}; + +const config: ControlPanelConfig = { + // For control input types, see: superset-frontend/src/explore/components/controls/index.js + controlPanelSections: [ + { + label: t('Query'), + expanded: true, + controlSetRows: [ + [ + { + name: 'datasource_config', + config: { + type: ChangeDataSourceButton, + label: t('Jump Actions'), + description: t('Configure dashboard jump actions.'), + mapStateToProps: (state: ControlPanelState) => ({ + datasource: state.datasource, + }), + }, + }, + ], + [ + { + name: 'query_mode', + config: queryMode, + }, + ], + [ + { + name: 'granularity_sqla', + config: { + type: 'SelectControl', + label: 'Time Column', + description: t( + 'The time column for the visualization. Note that you ' + + 'can define arbitrary expression that return a DATETIME ' + + 'column in the table. Also note that the ' + + 'filter below is applied against this column or ' + + 'expression', + ), + clearable: false, + optionRenderer: (c: any) => ( + + ), + valueKey: 'column_name', + rerender: ['time_range'], + mapStateToProps: state => { + const props: any = {}; + if ( + state.datasource && + 'granularity_sqla' in state.datasource + ) { + props.choices = state.datasource.granularity_sqla; + props.default = null; + if (state.datasource.main_dttm_col) { + props.default = state.datasource.main_dttm_col; + } else if (props.choices && props.choices.length > 0) { + props.default = props.choices[0].column_name; + } + } + return props; + }, + }, + }, + ], + [ + { + name: 'time_range', + config: { + type: DateTimeControl, + label: t('Time Range'), + default: 'Today : Tomorrow', + resetOnHide: false, + mapStateToProps: (state: ControlPanelState) => { + const disabled = !isTimeColumnSelected(state); + return { disabled }; + }, + }, + }, + ], + [ + { + name: 'groupby', + config: { + type: 'SelectControl', + label: t('Dimensions'), + description: t('Columns to display'), + multi: true, + freeForm: true, + allowAll: true, + default: [], + canSelectAll: true, + rerender: ['advanced_data_type_selection'], + optionRenderer: (c: ColumnMeta) => ( + // eslint-disable-next-line react/react-in-jsx-scope + + ), + // eslint-disable-next-line react/react-in-jsx-scope + valueRenderer: (c: ColumnMeta) => ( + // eslint-disable-next-line react/react-in-jsx-scope + + ), + valueKey: 'column_name', + mapStateToProps: ( + state: ControlPanelState, + controlState: ControlState, + ) => { + const { controls } = state; + const originalMapStateToProps = + sharedControls?.groupby?.mapStateToProps; + const newState = + originalMapStateToProps?.(state, controlState) ?? {}; + newState.externalValidationErrors = + // @ts-ignore + isAggMode({ controls }) && + ensureIsArray(controlState.value).length === 0 + ? [t('must have a value')] + : []; + return newState; + }, + visibility: isAggMode, + canCopy: true, + }, + }, + ], + [ + { + name: 'columns', + config: { + type: 'SelectControl', + label: t('Dimensions'), + description: t('Columns to display'), + multi: true, + freeForm: true, + allowAll: true, + default: [], + canSelectAll: true, + rerender: ['advanced_data_type_selection'], + optionRenderer: (c: ColumnMeta) => ( + // eslint-disable-next-line react/react-in-jsx-scope + + ), + // eslint-disable-next-line react/react-in-jsx-scope + valueRenderer: (c: ColumnMeta) => ( + // eslint-disable-next-line react/react-in-jsx-scope + + ), + valueKey: 'column_name', + mapStateToProps: ( + state: ControlPanelState, + controlState: ControlState, + ) => { + const { controls } = state; + const originalMapStateToProps = + sharedControls?.columns?.mapStateToProps; + const newState = + originalMapStateToProps?.(state, controlState) ?? {}; + newState.externalValidationErrors = + // @ts-ignore + isRawMode({ controls }) && + ensureIsArray(controlState.value).length === 0 + ? [t('must have a value')] + : []; + return newState; + }, + visibility: isRawMode, + canCopy: true, + }, + }, + ], + [ + { + name: 'advanced_data_type_selection', + config: { + type: 'SelectControl', + label: t('Advanced Data Type'), + description: t( + 'The advanced data type used to filter the rows to display', + ), + multi: false, + rerender: ['advanced_data_type_value'], + default: [], + valueKey: 'value', + mapStateToProps: (state: ControlPanelState) => { + const options = state.datasource?.columns + ? bootstrapData?.common?.advanced_data_types + ?.filter(v => + (state.datasource as Dataset).columns.some( + c => c.advanced_data_type === v.id, + ), + ) + .map(v => ({ value: v.id, label: v.verbose_name })) + : []; + return { options }; + }, + resetOnHide: false, + }, + }, + ], + [ + { + name: 'advanced_data_type_value', + config: { + type: AdvancedDataTypeValue, + freeForm: true, + label: t('Values'), + description: t( + 'The value to be used along with the advanced data type to filter the rows to display', + ), + multi: true, + default: [], + resetOnHide: false, + mapStateToProps: ( + state: ControlPanelState, + controlState: ControlState, + ) => { + const { datasource } = state; + const advancedDataType = + state.controls?.advanced_data_type_selection?.value || ''; + const disabled = ensureIsArray(advancedDataType).length === 0; + const val: any = controlState.value; + const externalValidationErrors = + !disabled && ensureIsArray(val)[0]?.columns.length === 0 + ? [t('Must have a valid entry')] + : []; + return { + datasource, + advancedDataType, + disabled, + externalValidationErrors, + }; + }, + }, + }, + ], + [ + { + name: 'adhoc_filters_no_date_default', + config: { + type: AdhocFilterControl, + label: t('Filters'), + mapStateToProps(state, controlState, chartState) { + const { datasource } = state; + const columns = datasource?.columns; + return { datasource, columns }; + }, + }, + }, + ], + [ + { + name: 'order_by_cols', + config: { + type: 'SelectControl', + label: t('Ordering'), + description: t('Order results by selected columns'), + multi: true, + default: [], + mapStateToProps: ({ datasource }) => ({ + choices: datasource?.hasOwnProperty('order_by_choices') + ? (datasource as Dataset)?.order_by_choices + : datasource?.columns || [], + }), + resetOnHide: false, + }, + }, + ], + [ + { + name: 'row_limit', + override: { + label: t('Row Limit'), + default: 100, + }, + }, + ], + [ + { + name: 'column_state', + config: { + type: 'HiddenControl', + hidden: true, + label: t('Column state'), + description: t('State of AG Grid columns'), + dontRefreshOnChange: true, + }, + }, + ], + ], + }, + ], + // override controls that are inherited by the default configuration + controlOverrides: { + series: { + validators: [validateNonEmpty], + clearable: false, + }, + viz_type: { + default: 'cccs_grid', + }, + time_range: { + default: t('Last day'), + }, + }, +}; + +export default config; diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/index.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/index.ts new file mode 100644 index 0000000000000..0de32b4ec8318 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/index.ts @@ -0,0 +1,67 @@ +/** + * 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 { t, Behavior, ChartMetadata, ChartPlugin } from '@superset-ui/core'; +import buildQuery from './buildQuery'; +import controlPanel from './controlPanel'; +import transformProps from './transformProps'; +import thumbnail from '../images/thumbnail.png'; +import { CccsGridQueryFormData, CccsGridChartProps } from '../types'; +import example1 from '../images/Table1.png'; + +export default class CccsDataSetExplorerPlugin extends ChartPlugin< + CccsGridQueryFormData, + CccsGridChartProps +> { + /** + * The constructor is used to pass relevant metadata and callbacks that get + * registered in respective registries that are used throughout the library + * and application. A more thorough description of each property is given in + * the respective imported file. + * + * It is worth noting that `buildQuery` and is optional, and only needed for + * advanced visualizations that require either post processing operations + * (pivoting, rolling aggregations, sorting etc) or submitting multiple queries. + */ + constructor() { + const metadata = new ChartMetadata({ + description: t('Dataset Explorer: An AG Grid control for Hogwarts data.'), + name: t('Dataset Explorer'), + category: t('Table'), + tags: [ + t('Hogwarts'), + t('Table'), + t('Grid'), + t('Popular'), + t('Report'), + t('Tabular'), + ], + exampleGallery: [{ url: example1 }], + thumbnail, + behaviors: [Behavior.INTERACTIVE_CHART], + }); + + super({ + buildQuery, + controlPanel, + loadChart: () => import('../../../plugin-chart-cccs-grid/src/CccsGrid'), + metadata, + transformProps, + }); + } +} diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/transformProps.ts new file mode 100644 index 0000000000000..ed796c38339a9 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/transformProps.ts @@ -0,0 +1,348 @@ +/** + * 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 { ValueFormatterParams } from '@ag-grid-enterprise/all-modules'; +import { + Column, + getMetricLabel, + getNumberFormatter, + Metric, + NumberFormats, + QueryMode, + TimeseriesDataRecord, +} from '@superset-ui/core'; +import { + CccsGridChartProps, + CccsGridQueryFormData, + DEFAULT_FORM_DATA, +} from '../types'; +import {rendererMap, formatIpV4} from '../../../plugin-chart-cccs-grid/src/utils/advancedDataTypes' +export default function transformProps(chartProps: CccsGridChartProps) { + /** + * This function is called after a successful response has been + * received from the chart data endpoint, and is used to transform + * the incoming data prior to being sent to the Visualization. + * + * The transformProps function is also quite useful to return + * additional/modified props to your data viz component. The formData + * can also be accessed from your CccsGrid.tsx file, but + * supplying custom props here is often handy for integrating third + * party libraries that rely on specific props. + * + * A description of properties in `chartProps`: + * - `height`, `width`: the height/width of the DOM element in which + * the chart is located + * - `formData`: the chart data request payload that was sent to the + * backend. + * - `queriesData`: the chart data response payload that was received + * from the backend. Some notable properties of `queriesData`: + * - `data`: an array with data, each row with an object mapping + * the column/alias to its value. Example: + * `[{ col1: 'abc', metric1: 10 }, { col1: 'xyz', metric1: 20 }]` + * - `rowcount`: the number of rows in `data` + * - `query`: the query that was issued. + * + * Please note: the transformProps function gets cached when the + * application loads. When making changes to the `transformProps` + * function during development with hot reloading, changes won't + * be seen until restarting the development server. + */ + const { + datasource, + hooks, + width, + height, + rawFormData: formData, + queriesData, + } = chartProps; + const { + boldText, + headerFontSize, + headerText, + emitFilter, + principalColumns, + query_mode, + column_state, + }: CccsGridQueryFormData = { ...DEFAULT_FORM_DATA, ...formData }; + const data = queriesData[0].data as TimeseriesDataRecord[]; + const agGridLicenseKey = queriesData[0].agGridLicenseKey as String; + + const default_group_by: any[] = []; + const enable_row_numbers: boolean = true; + const jump_action_configs: string[] = [] + const enable_json_expand: boolean = false + + const include_search: boolean = true; + const page_length: number = 50; + const enable_grouping: boolean = true; + + + const { setDataMask = () => {}, setControlValue } = hooks; + const columns = datasource?.columns as Column[]; + const metrics = datasource?.metrics as Metric[]; + + const columnTypeMap = new Map(); + columns.reduce(function (columnMap, column: Column) { + // @ts-ignore + const name = column.column_name; + // @ts-ignore + columnMap[name] = column.type; + return columnMap; + }, columnTypeMap); + + // Map of column advanced types, key is column name, value is column type + const columnAdvancedTypeMap = new Map(); + columns.reduce(function (columnMap, column: Column) { + // @ts-ignore + const name = column.column_name; + // @ts-ignore + columnMap[name] = ( + (column.advanced_data_type as string) ?? '' + ).toUpperCase(); + return columnMap; + }, columnAdvancedTypeMap); + + // Map of verbose names, key is column name, value is verbose name + const columnVerboseNameMap = new Map(); + columns.reduce(function (columnMap, column: Column) { + // @ts-ignore + const name = column.column_name; + // @ts-ignore + columnMap[name] = column.verbose_name; + return columnMap; + }, columnVerboseNameMap); + + // Map of column descriptions, key is column name, value is the description + const columnDescriptionMap = new Map(); + columns.reduce(function (columnMap, column: Column) { + // @ts-ignore + const name = column.column_name; + // @ts-ignore + columnMap[name] = column.description; + return columnMap; + }, columnDescriptionMap); + + // Map of verbose names, key is metric name, value is verbose name + const metricVerboseNameMap = new Map(); + metrics.reduce(function (metricMap, metric: Metric) { + // @ts-ignore + const name = metric.metric_name; + // @ts-ignore + metricMap[name] = metric.verbose_name; + return metricMap; + }, metricVerboseNameMap); + + + const valueFormatter = (params: any) => { + if ( + params.value != null && + params.colDef.cellRenderer === 'ipv4ValueRenderer' + ) { + return formatIpV4(params.value.toString()); + } + return params.value != null ? params.value.toString() : ''; + }; + + const percentMetricValueFormatter = function (params: ValueFormatterParams) { + return getNumberFormatter(NumberFormats.PERCENT_3_POINT).format( + params.value, + ); + }; + + let columnDefs: Column[] = []; + + if (query_mode === QueryMode.raw) { + columnDefs = formData.columns.map((column: any) => { + const columnType = columnTypeMap[column]; + const columnAdvancedType = columnAdvancedTypeMap[column]; + const columnHeader = columnVerboseNameMap[column] + ? columnVerboseNameMap[column] + : column; + const cellRenderer = + columnAdvancedType in rendererMap + ? rendererMap[columnAdvancedType] + : columnType in rendererMap + ? rendererMap[columnType] + : undefined; + const isSortable = true; + const enableRowGroup = true; + const columnDescription = columnDescriptionMap[column]; + const autoHeight = true; + const rowGroupIndex = default_group_by.findIndex( + (element: any) => element === column, + ); + const rowGroup = rowGroupIndex >= 0; + const hide = rowGroup; + return { + field: column, + headerName: columnHeader, + cellRenderer, + sortable: isSortable, + enableRowGroup, + rowGroup, + hide, + rowGroupIndex, + getQuickFilterText: (params: any) => valueFormatter(params), + headerTooltip: columnDescription, + autoHeight, + }; + }); + } else { + if (formData.groupby) { + const groupByColumnDefs = formData.groupby.map((column: any) => { + const columnType = columnTypeMap[column]; + const columnAdvancedType = columnAdvancedTypeMap[column]; + const columnHeader = columnVerboseNameMap[column] + ? columnVerboseNameMap[column] + : column; + const cellRenderer = + columnAdvancedType in rendererMap + ? rendererMap[columnAdvancedType] + : columnType in rendererMap + ? rendererMap[columnType] + : undefined; + const isSortable = true; + const enableRowGroup = true; + const columnDescription = columnDescriptionMap[column]; + const autoHeight = true; + const rowGroupIndex = default_group_by.findIndex( + (element: any) => element === column, + ); + const initialRowGroupIndex = rowGroupIndex; + const rowGroup = rowGroupIndex >= 0; + const hide = rowGroup; + return { + field: column, + headerName: columnHeader, + cellRenderer, + sortable: isSortable, + enableRowGroup, + rowGroup, + rowGroupIndex, + initialRowGroupIndex, + hide, + getQuickFilterText: (params: any) => valueFormatter(params), + headerTooltip: columnDescription, + autoHeight, + }; + }); + columnDefs = columnDefs.concat(groupByColumnDefs); + } + + if (formData.metrics) { + const metricsColumnDefs = formData.metrics + .map(getMetricLabel) + .map((metric: any) => { + const metricHeader = metricVerboseNameMap[metric] + ? metricVerboseNameMap[metric] + : metric; + return { + field: metric, + headerName: metricHeader, + sortable: true, + enableRowGroup: true, + }; + }); + columnDefs = columnDefs.concat(metricsColumnDefs); + } + + if (formData.percent_metrics) { + const percentMetricsColumnDefs = formData.percent_metrics + .map(getMetricLabel) + .map((metric: any) => { + const metricHeader = metricVerboseNameMap[metric] + ? metricVerboseNameMap[metric] + : metric; + return { + field: `%${metric}`, + headerName: `%${metricHeader}`, + sortable: true, + valueFormatter: percentMetricValueFormatter, + }; + }); + columnDefs = columnDefs.concat(percentMetricsColumnDefs); + } + } + + if (enable_row_numbers) { + columnDefs.splice(0, 0, { + headerName: '#', + colId: 'rowNum', + pinned: 'left', + lockVisible: true, + valueGetter: (params: any) => + params.node ? params.node.rowIndex + 1 : null, + } as any); + } + const parsed_jump_action_configs = {}; + jump_action_configs?.forEach((e: any) => { + if (e.dashboardID in parsed_jump_action_configs) { + parsed_jump_action_configs[e.dashboardID] = parsed_jump_action_configs[ + e.dashboardID + ].concat({ + advancedDataType: e.advancedDataType, + nativefilters: e.filters, + name: e.dashBoardName, + }); + } else { + parsed_jump_action_configs[e.dashboardID] = [ + { + advancedDataType: e.advancedDataType, + nativefilters: e.filters, + name: e.dashBoardName, + }, + ]; + } + }); + + // If the flag is set to true, add a column which will contain + // a button to expand all JSON blobs in the row + if (enable_json_expand) { + columnDefs.splice(1, 0, { + colId: 'jsonExpand', + pinned: 'left', + cellRenderer: 'expandAllValueRenderer', + autoHeight: true, + minWidth: 105, + lockVisible: true, + } as any); + } + + return { + formData, + setDataMask, + setControlValue, + width, + height, + columnDefs, + rowData: data, + // and now your control data, manipulated as needed, and passed through as props! + boldText, + headerFontSize, + headerText, + emitFilter, + principalColumns, + include_search, + page_length, + enable_grouping, + column_state, + agGridLicenseKey, + datasetColumns: columns, + jumpActionConfigs: parsed_jump_action_configs, + }; +} diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/types.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/types.ts new file mode 100644 index 0000000000000..acbc1cc6d3e92 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/types.ts @@ -0,0 +1,103 @@ +/** + * 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 { ColumnState } from '@ag-grid-enterprise/all-modules'; +import { + ChartDataResponseResult, + ChartProps, + HandlerFunction, + QueryFormData, + SetDataMaskHook, + supersetTheme, + TimeseriesDataRecord, + Column, + AdhocFilter, +} from '@superset-ui/core'; + +export type CccsGridQueryFormData = QueryFormData & { + headerText?: string; + setDataMask?: SetDataMaskHook; + selectedValues?: Record; + emitFilter: boolean; + include_search: boolean; + page_length: number; + enable_grouping: boolean; + column_state: ColumnState[]; + enable_row_numbers: boolean; + jump_action_configs?: any[]; + datasource_config?: string; + advanced_data_type_selection: string[]; + advanced_data_type_value: { + columns: string[]; + data: string[]; + rawData: string[]; + }; + adhoc_filters_no_date_default: AdhocFilter[]; +}; + +export interface CccsGridStylesProps { + height: number; + width: number; + headerFontSize?: keyof typeof supersetTheme.typography.sizes; + boldText?: boolean; +} + +// @ts-ignore +export const DEFAULT_FORM_DATA: CccsGridQueryFormData = { + result_type: 'post_processed', + viz_type: 'cccs_grid', +}; + +export interface CccsGridChartDataResponseResult + extends ChartDataResponseResult { + agGridLicenseKey: string; +} + +export class CccsGridChartProps extends ChartProps { + declare formData: CccsGridQueryFormData; + + declare queriesData: CccsGridChartDataResponseResult[]; +} + +export interface CccsGridTransformedProps extends CccsGridStylesProps { + formData: CccsGridQueryFormData; + setDataMask: SetDataMaskHook; + setControlValue: HandlerFunction; + selectedValues: Record; + emitFilter: boolean; + principalColumns: any; + data: TimeseriesDataRecord[]; + columnDefs: any; + rowData: any; + tooltipShowDelay: any; + frameworkComponents: any; + modules: any; + defaultColDef: any; + rowSelection: any; + filters: any; + include_search: boolean; + page_length: number; + enable_grouping: boolean; + column_state: ColumnState[]; + // add typing here for the props you pass in from transformProps.ts! + agGridLicenseKey: string; + datasetColumns: Column[]; + jumpActionConfigs?: any[]; +} + +export type EventHandlers = Record; diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx index e65e25b9b3756..eb7c45fb3cbfd 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx @@ -112,6 +112,7 @@ export interface AdvancedDataTypesState { advancedDataTypeOperatorList: string[]; errorMessage: string; useDefaultOperators: boolean; + values: any[]; } export const useSimpleTabFilterProps = (props: Props) => { diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/useAdvancedDataTypes.ts b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/useAdvancedDataTypes.ts index 31c6af0ba652a..3d8fe972842ab 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/useAdvancedDataTypes.ts +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/useAdvancedDataTypes.ts @@ -27,6 +27,7 @@ const INITIAL_ADVANCED_DATA_TYPES_STATE: AdvancedDataTypesState = { advancedDataTypeOperatorList: [], errorMessage: '', useDefaultOperators: false, + values: [], }; const useAdvancedDataTypes = (validHandler: (isValid: boolean) => void) => { @@ -61,6 +62,7 @@ const useAdvancedDataTypes = (validHandler: (isValid: boolean) => void) => { advancedDataTypeOperatorList: json.result.valid_filter_operators, errorMessage: json.result.error_message, useDefaultOperators: false, + values: json.result.values, }); // Changed due to removal of status field validHandler(!json.result.error_message); @@ -72,6 +74,7 @@ const useAdvancedDataTypes = (validHandler: (isValid: boolean) => void) => { advancedDataTypesState.advancedDataTypeOperatorList, errorMessage: t('Failed to retrieve advanced type'), useDefaultOperators: true, + values: [], }); validHandler(true); }); diff --git a/superset-frontend/src/visualizations/presets/MainPreset.js b/superset-frontend/src/visualizations/presets/MainPreset.js index 11c68fd8f23c5..7026b59c3ab28 100644 --- a/superset-frontend/src/visualizations/presets/MainPreset.js +++ b/superset-frontend/src/visualizations/presets/MainPreset.js @@ -85,6 +85,7 @@ import { GwwkDatasetsChartPlugin, GwwkDashboardsChartPlugin, CccsGridChartPlugin, + DatasetExplorerChartPlugin, BigNumberChartPlugin, AtAGlanceChartIpPlugin, AtAGlanceChartDnsPlugin, @@ -120,6 +121,7 @@ export default class MainPreset extends Preset { new AtAGlanceChartIpPlugin().configure({ key: 'at_a_glance_ip' }), new AtAGlanceChartDnsPlugin().configure({ key: 'at_a_glance_dns' }), new IFrameVisualizationChartPlugin().configure({ key: 'i_frame' }), + new DatasetExplorerChartPlugin().configure({ key: 'dataset_explorer' }), new GwwkChartsChartPlugin().configure({ key: 'gwwk_charts' }), new GwwkDatasetsChartPlugin().configure({ key: 'gwwk_datasets' }), new GwwkDashboardsChartPlugin().configure({ key: 'gwwk_dashboards' }),