From b5ec0de1a236fd67e86902c728469b0642696980 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Fri, 14 Oct 2022 15:11:18 -0400 Subject: [PATCH 01/31] [CLDN-1749] adding new viz --- .../src/cccs-viz/plugins/index.ts | 1 + .../src/IFrameVisualization.tsx | 11 + .../plugin-chart-iframe/src/images/alfred.png | Bin 0 -> 2753 bytes .../src/images/thumbnail.png | Bin 0 -> 5658 bytes .../plugins/plugin-chart-iframe/src/index.ts | 1 + .../src/plugin/controlPanel.ts | 202 ++++++++++++++++++ .../plugin-chart-iframe/src/plugin/index.ts | 49 +++++ .../src/plugin/transformProps.ts | 75 +++++++ .../plugins/plugin-chart-iframe/src/styles.js | 22 ++ .../plugins/plugin-chart-iframe/src/types.ts | 7 + .../plugin-chart-iframe/types/external.d.ts | 4 + .../src/visualizations/presets/MainPreset.js | 2 + 12 files changed, 374 insertions(+) create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/alfred.png create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/index.ts create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/index.ts create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/styles.js create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/types/external.d.ts diff --git a/superset-frontend/src/cccs-viz/plugins/index.ts b/superset-frontend/src/cccs-viz/plugins/index.ts index 7851bb1560dc0..f8453be3c882f 100644 --- a/superset-frontend/src/cccs-viz/plugins/index.ts +++ b/superset-frontend/src/cccs-viz/plugins/index.ts @@ -28,3 +28,4 @@ export { default as AtAGlanceChartDnsPlugin } from './plugin-chart-at-a-glance-d export { default as AtAGlanceUserIdChartPlugin } from './plugin-chart-at-a-glance-user-id/src/plugin'; 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'; diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx new file mode 100644 index 0000000000000..112275f501ac0 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { IFrameVisualizationProps } from './types'; + + +export default function IFrameVisualization(props: IFrameVisualizationProps) { + const { url, url_parameter_value, parameter_name } = props + console.log(`${url}?${parameter_name}=${url_parameter_value}`) + return ( + + ); +} diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/alfred.png b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/alfred.png new file mode 100644 index 0000000000000000000000000000000000000000..ab1d3036b5f6f78d8974946c6bfe6c295edd48e4 GIT binary patch literal 2753 zcmV;y3O@CTP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D3RX!(K~#8N?V5W~ zRM#EHeU9Vf4@bwxf6Az{oxx#n*6QFeSrNgqsi;Uap=e^`1GHLUVlt-KZc2THR>An# zifd#68^wqqM#IC`DpIY42yrXbfKWh1bVU&s!Eetw_nylIcdNqgI?27?nKSJDopaA# z&gcAo=XcKDUQnedslXK`6}ZBr0#}$+;0lupTwzjyD@-bIg-HdjFsZ;5CKb5Cqykr% z+zx!|)T!vzs~0OeosQX_KYz~ts`9+t0=&Jwo&7B;Dng$=ebBdWUsn3{>xXOCuCY25 z`kRqkfk#G0GSBYayEiNJb98hxtK)4{c~))<>9w`B7&2rC13h~5Xp9;)ihUkFd^jFF zcp$b?I;n_qJ`@5FZ~eUGLt#o3&BB-1BmahuiITj2kzOdGvq*19125 zT~t(5ur-|8e0_a?Qfqj*Q4cRKFL=e~ik1#F-ORyXA_Cx}A@!t<_q%DhCDyrL^2^%*;&Y$rSFqdGnZ!Dkvz3*=XIlbLW^%?&WRmq(2XOy*)P; z8VXVqg>0K>bReg|%S{LsL3??5L+kGkzsQuIFYvD7UORKW*Sj=SOB!|vki#JkVq?bH)^+x!he zOvh36v<(Li96+z$eVF$yU%s5Rmxpu?JuEDY0jF*08h`aa^$1w?CEi-L2XFm$52k-; zg7HuxT1l9vqMq*3tiCt_)9AqI5(#>u(nRR!wQn3O4#vAP@LHTsT95VXS7NrWFxa|C zdQHn$py!BEd!0_`mlu&reeNgl<5|7dAO0lSKaI9$1{6PAPi^f$kT`-UVJ7_h%}mr zbSa-GH-(CDLy=I_(cW(R4(7#L>3duToXSR$tuL-w5q`cHi%wc_vAT}gn~1l!JZr;$ zA2wp;*XJK{HKVZ0SB_GhgA zl*J1D(qzn7WWwL}7U0q2W@hiPY}trn-lc;)g7Y<96O~Q!_s;4zaQ!aGomIXw2sb-O z5$zVlF^}cXRtN*T){tF9J!{fQ_AA)T@(&jRzi{U+7N5O@1s994v&4!Xxr4ZLfGJr}WTvc32nT)DAApAocy zbr*u!GcAMTU8D17aHjtYeC;{4v*{>;K0Snjk}4MZr%aiGh6bU3-lzNk=2G#G zSQvc(0pWYGD0;tW?T&8494-vdiyr2@&Sk*MjnGs3D-=E5GUUbz*Ig&4B$orfg6(Bi z61IgfOF7FumTtGGwr<_Z*5rX?Z(UMSg3AQzuamM6o^%W`2XYbo=Yt6PQyOBDPvTb_ z(lO_=!vuUi&YnGsK7ISLd-v}bK=MAR=y&mT`zguVKl|$m0{lyM-7QH29u7wlwKhFM z7+{{viE=$~;Z%+$YtG7&v|FABE|;@>iH2i(Zccyirb$>4i3(J=?ljgWFiC_EHdw}B9pZPZ0D1B`!iy6fXX#!VANYmEz%27(Ne<7_6LGe}F74ah2Aq3ka&oe?_C`cRFq@;b zR}Nafx8~x|XUv$v`l5a2$QP%Kw7O<0%WmRn5{d2_&pgJ8PWve*}kJ_ zp{i$kE#Mr^g$oxXIFlw#atWB`=4Ol;Glum^6TurdZZNwF-L>i#a2mq7cN>jHX6s3k z#rxf`VFT-vmUZjaFMjslXK`6}ZBr0#}$+;0lupTwzjyD@-bIMG5#Hr<^cI(hX2P00000NkvXX Hu0mjfpbJjG literal 0 HcmV?d00001 diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..7afef30bd4e6e4f85723208bf9f429647d03d3e5 GIT binary patch literal 5658 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 literal 0 HcmV?d00001 diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/index.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/index.ts new file mode 100644 index 0000000000000..aaa756b08cb8f --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/index.ts @@ -0,0 +1 @@ +export { default as IFrameVisualizationChartPlugin } from './plugin'; diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts new file mode 100644 index 0000000000000..9983dd5666a2e --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts @@ -0,0 +1,202 @@ +/** + * 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 { ensureIsArray, t, validateNonEmpty } from '@superset-ui/core'; +import { + ControlPanelConfig, + ControlPanelState, + ControlState, + ControlStateMapping, + sharedControls, +} from '@superset-ui/chart-controls'; + +const validateAggControlValues = ( + controls: ControlStateMapping, + values: any[], +) => { + const areControlsEmpty = values.every(val => ensureIsArray(val).length === 0); + // @ts-ignore + return areControlsEmpty ? [t('Metrics must have a value')] : []; +}; + +const config: ControlPanelConfig = { + /** + * The control panel is split into two tabs: "Query" and + * "Chart Options". The controls that define the inputs to + * the chart data request, such as columns and metrics, usually + * reside within "Query", while controls that affect the visual + * appearance or functionality of the chart are under the + * "Chart Options" section. + * + * There are several predefined controls that can be used. + * Some examples: + * - groupby: columns to group by (tranlated to GROUP BY statement) + * - series: same as groupby, but single selection. + * - metrics: multiple metrics (translated to aggregate expression) + * - metric: sane as metrics, but single selection + * - adhoc_filters: filters (translated to WHERE or HAVING + * depending on filter type) + * - row_limit: maximum number of rows (translated to LIMIT statement) + * + * If a control panel has both a `series` and `groupby` control, and + * the user has chosen `col1` as the value for the `series` control, + * and `col2` and `col3` as values for the `groupby` control, + * the resulting query will contain three `groupby` columns. This is because + * we considered `series` control a `groupby` query field and its value + * will automatically append the `groupby` field when the query is generated. + * + * It is also possible to define custom controls by importing the + * necessary dependencies and overriding the default parameters, which + * can then be placed in the `controlSetRows` section + * of the `Query` section instead of a predefined control. + * + * import { validateNonEmpty } from '@superset-ui/core'; + * import { + * sharedControls, + * ControlConfig, + * ControlPanelConfig, + * } from '@superset-ui/chart-controls'; + * + * const myControl: ControlConfig<'SelectControl'> = { + * name: 'secondary_entity', + * config: { + * ...sharedControls.entity, + * type: 'SelectControl', + * label: t('Secondary Entity'), + * mapStateToProps: state => ({ + * sharedControls.columnChoices(state.datasource) + * .columns.filter(c => c.groupby) + * }) + * validators: [validateNonEmpty], + * }, + * } + * + * In addition to the basic drop down control, there are several predefined + * control types (can be set via the `type` property) that can be used. Some + * commonly used examples: + * - SelectControl: Dropdown to select single or multiple values, + usually columns + * - MetricsControl: Dropdown to select metrics, triggering a modal + to define Metric details + * - AdhocFilterControl: Control to choose filters + * - CheckboxControl: A checkbox for choosing true/false values + * - SliderControl: A slider with min/max values + * - TextControl: Control for text data + * + * For more control input types, check out the `incubator-superset` repo + * and open this file: superset-frontend/src/explore/components/controls/index.js + * + * To ensure all controls have been filled out correctly, the following + * validators are provided + * by the `@superset-ui/core/lib/validator`: + * - validateNonEmpty: must have at least one value + * - validateInteger: must be an integer value + * - validateNumber: must be an intger or decimal value + */ + + // For control input types, see: superset-frontend/src/explore/components/controls/index.js + controlPanelSections: [ + { + label: t('Query'), + expanded: true, + controlSetRows: [ + ['adhoc_filters'], + [ + { + name: 'metrics', + override: { + // visibility: () => true, + validators: [], + mapStateToProps: ( + state: ControlPanelState, + controlState: ControlState, + ) => { + const { controls } = state; + const originalMapStateToProps = + sharedControls?.metrics?.mapStateToProps; + const newState = + originalMapStateToProps?.(state, controlState) ?? {}; + newState.externalValidationErrors = validateAggControlValues( + controls, + [controlState.value], + ); + return newState; + }, + }, + }, + ], + [ + { + name: 'row_limit', + override: { + default: 1, + }, + }, + ], + [ + { + name: 'url', + config: { + type: 'TextControl', + label: t('URL'), + renderTrigger: true, + default: '', + description: t('The Base URL for the Iframe.'), + }, + }, + ], + [ + { + name: 'parameter_column_name', + config: { + type: 'TextControl', + label: t('Parameter Column Name'), + renderTrigger: true, + default: '', + description: t('The Column name for the value that will populate the url parameter.'), + }, + }, + ], + [ + { + name: 'parameter_name', + config: { + type: 'TextControl', + label: t('Parameter Name'), + renderTrigger: true, + default: '', + description: t('The name for the URL parameter.'), + }, + }, + ] + ], + }, + ], + + controlOverrides: { + series: { + validators: [validateNonEmpty], + clearable: false, + }, + row_limit: { + default: 1, + }, + }, +}; + +export default config; diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/index.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/index.ts new file mode 100644 index 0000000000000..42d621626fac2 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/index.ts @@ -0,0 +1,49 @@ +/** + * 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, ChartMetadata, ChartPlugin } from '@superset-ui/core'; +import controlPanel from './controlPanel'; +import transformProps from './transformProps'; +import thumbnail from '../images/thumbnail.png'; + +export default class IFrameVisualizationChartPlugin extends ChartPlugin { + /** + * 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: 'IFrame Visualization', + name: t('IFrame Visualization'), + thumbnail, + }); + + super({ + controlPanel, + loadChart: () => import('../IFrameVisualization'), + metadata, + transformProps, + }); + } +} diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts new file mode 100644 index 0000000000000..417479dc0fd17 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -0,0 +1,75 @@ +/** + * 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 { ChartProps, TimeseriesDataRecord } from '@superset-ui/core'; + +export default function transformProps(chartProps: ChartProps) { + /** + * 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 IframeDemo.tsx file, but + * doing 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 { formData, } = chartProps; + + const { url, parameterColumnName, parameterName } = formData + + + + let url_parameter_value = ''; + + + // eslint-disable-next-line no-plusplus + for (let i = 0; i < formData?.adhocFilters?.length; i++) { + + const adhocfilter = formData?.adhocFilters[i]; + if (adhocfilter.subject === parameterColumnName) { + url_parameter_value = adhocfilter.comparator; + break; + } + } + + return { + url_parameter_value, + parameter_name: parameterName, + url, + }; +} diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/styles.js b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/styles.js new file mode 100644 index 0000000000000..d7bb66e9bb356 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/styles.js @@ -0,0 +1,22 @@ +const InlineBlock = { + display: 'inline-block', +}; + +const InlineImg = { + display: 'inline-block', + transform: 'translateY(-10%)', + '-ms-transform': 'translateY(-10%)', +}; + +const InlineText = { + display: 'inline-block', + 'text-align': 'center', +}; + +const styles = { + InlineBlock, + InlineImg, + InlineText, +}; + +export default styles; diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts new file mode 100644 index 0000000000000..0b20023c4870f --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts @@ -0,0 +1,7 @@ +import { QueryFormData, TimeseriesDataRecord } from '@superset-ui/core'; + +export type IFrameVisualizationProps = QueryFormData & { + url_parameter_value: string; + parameter_name: string; + url: string; +}; diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/types/external.d.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/types/external.d.ts new file mode 100644 index 0000000000000..0935dbbd8066c --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/types/external.d.ts @@ -0,0 +1,4 @@ +declare module '*.png' { + const value: any; + export default value; +} diff --git a/superset-frontend/src/visualizations/presets/MainPreset.js b/superset-frontend/src/visualizations/presets/MainPreset.js index 3382e1d0c4c8a..973bd8e5231c6 100644 --- a/superset-frontend/src/visualizations/presets/MainPreset.js +++ b/superset-frontend/src/visualizations/presets/MainPreset.js @@ -89,6 +89,7 @@ import { AtAGlanceUserIdChartPlugin, AtAGlanceUserIDSasChartPlugin, ApplicationLinksChartPlugin, + IFrameVisualizationChartPlugin, } from 'src/cccs-viz/plugins/'; import FilterBoxChartPlugin from '../FilterBox/FilterBoxChartPlugin'; import TimeTableChartPlugin from '../TimeTable'; @@ -116,6 +117,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 GwwkChartsChartPlugin().configure({ key: 'gwwk_charts' }), new GwwkDatasetsChartPlugin().configure({ key: 'gwwk_datasets' }), new GwwkDashboardsChartPlugin().configure({ key: 'gwwk_dashboards' }), From 24d9090b70a224b5b2fecfe2a91206ab0cab3ea5 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Mon, 17 Oct 2022 10:29:47 -0400 Subject: [PATCH 02/31] chanigng to work with standard filters --- .../plugin-chart-iframe/src/plugin/transformProps.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index 417479dc0fd17..55d0afa9cc12a 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -58,11 +58,11 @@ export default function transformProps(chartProps: ChartProps) { // eslint-disable-next-line no-plusplus - for (let i = 0; i < formData?.adhocFilters?.length; i++) { + for (let i = 0; i < formData?.extraFormData?.filters?.length; i++) { - const adhocfilter = formData?.adhocFilters[i]; - if (adhocfilter.subject === parameterColumnName) { - url_parameter_value = adhocfilter.comparator; + const adhocfilter = formData?.extraFormData?.filters[i]; + if (adhocfilter.col === parameterColumnName) { + url_parameter_value = adhocfilter.val; break; } } From 02763ef9941f7ad3a9ecd8281abdb4187c689d1e Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Mon, 17 Oct 2022 15:40:54 -0400 Subject: [PATCH 03/31] Adding generic filter extraction --- .../src/IFrameVisualization.tsx | 2 +- .../src/plugin/transformProps.ts | 36 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx index 112275f501ac0..fa0e2edb134a8 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx @@ -6,6 +6,6 @@ export default function IFrameVisualization(props: IFrameVisualizationProps) { const { url, url_parameter_value, parameter_name } = props console.log(`${url}?${parameter_name}=${url_parameter_value}`) return ( - + ); } diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index 55d0afa9cc12a..fbc9ab151c7de 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -16,7 +16,41 @@ * specific language governing permissions and limitations * under the License. */ -import { ChartProps, TimeseriesDataRecord } from '@superset-ui/core'; +import { ChartProps, QueryFormData, TimeseriesDataRecord, AdhocFilter } from '@superset-ui/core'; +import { string } from 'yargs'; + + +const extractFiltersFromFormData = (formData: QueryFormData): ({columnName: string, value: string | number | boolean | (string | number | boolean)[] } | null)[] | undefined => { + + const adhoc = formData?.adhoc_filters?.map( (filter: AdhocFilter) => { + if ( ("subject" in filter) && ("comparator" in filter) ) { + return {columnName: filter.subject, value: filter.comparator} + } + else { + return null + } + }) || [] + + const adhocExtra = formData?.extra_form_data?.adhoc_filters?.map( (filter: AdhocFilter) => { + if ( ("subject" in filter) && ("comparator" in filter) ) { + return {columnName: filter.subject, value: filter.comparator} + } + else { + return null + } + }) || [] + + const simpleExtra = formData?.extra_form_data?.filters?.map( (filter) => { + if ( ("col" in filter) && ("val" in filter) ) { + return {columnName: filter.col.toString(), value: filter.val} + } + else { + return null + } + }) || [] + + return [...adhoc, ...adhocExtra, ...simpleExtra] +} export default function transformProps(chartProps: ChartProps) { /** From 3ed190ccaab2e2d6a58f581b5ef35843ead1f763 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Mon, 17 Oct 2022 15:42:37 -0400 Subject: [PATCH 04/31] Removing unused imports --- .../plugins/plugin-chart-iframe/src/plugin/transformProps.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index fbc9ab151c7de..e2feec013f954 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -16,8 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { ChartProps, QueryFormData, TimeseriesDataRecord, AdhocFilter } from '@superset-ui/core'; -import { string } from 'yargs'; +import { ChartProps, QueryFormData, AdhocFilter } from '@superset-ui/core'; const extractFiltersFromFormData = (formData: QueryFormData): ({columnName: string, value: string | number | boolean | (string | number | boolean)[] } | null)[] | undefined => { @@ -93,7 +92,6 @@ export default function transformProps(chartProps: ChartProps) { // eslint-disable-next-line no-plusplus for (let i = 0; i < formData?.extraFormData?.filters?.length; i++) { - const adhocfilter = formData?.extraFormData?.filters[i]; if (adhocfilter.col === parameterColumnName) { url_parameter_value = adhocfilter.val; From 1dc2d51dd0d8499689071383a38534a3f6cbb2dc Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 18 Oct 2022 11:51:50 -0400 Subject: [PATCH 05/31] [CLDN-1749] Cleaning up code for reusablility --- .../src/IFrameVisualization.tsx | 6 +- .../src/plugin/transformProps.ts | 66 +++++++------------ .../plugins/plugin-chart-iframe/src/types.ts | 2 +- 3 files changed, 28 insertions(+), 46 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx index fa0e2edb134a8..659db9fa3ced8 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx @@ -4,8 +4,10 @@ import { IFrameVisualizationProps } from './types'; export default function IFrameVisualization(props: IFrameVisualizationProps) { const { url, url_parameter_value, parameter_name } = props - console.log(`${url}?${parameter_name}=${url_parameter_value}`) + + const parserdUrlParameterName = parameter_name.includes('=') ? parameter_name : `${parameter_name}=` + return ( - + ); } diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index e2feec013f954..8300534d6f7a2 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -16,39 +16,27 @@ * specific language governing permissions and limitations * under the License. */ -import { ChartProps, QueryFormData, AdhocFilter } from '@superset-ui/core'; +import { ChartProps, PlainObject, } from '@superset-ui/core'; -const extractFiltersFromFormData = (formData: QueryFormData): ({columnName: string, value: string | number | boolean | (string | number | boolean)[] } | null)[] | undefined => { +const extractFiltersFromFormData = (formData: PlainObject): ({columnName: string, value: string | number | boolean | (string | number | boolean)[] })[] => { - const adhoc = formData?.adhoc_filters?.map( (filter: AdhocFilter) => { - if ( ("subject" in filter) && ("comparator" in filter) ) { - return {columnName: filter.subject, value: filter.comparator} - } - else { - return null - } - }) || [] - - const adhocExtra = formData?.extra_form_data?.adhoc_filters?.map( (filter: AdhocFilter) => { - if ( ("subject" in filter) && ("comparator" in filter) ) { - return {columnName: filter.subject, value: filter.comparator} - } - else { - return null - } - }) || [] - - const simpleExtra = formData?.extra_form_data?.filters?.map( (filter) => { - if ( ("col" in filter) && ("val" in filter) ) { - return {columnName: filter.col.toString(), value: filter.val} - } - else { - return null - } - }) || [] + const filters = [...(formData?.adhoc_filters || []), ...(formData?.extra_form_data?.adhoc_filters || []), ...(formData?.extra_form_data?.filters || [])] - return [...adhoc, ...adhocExtra, ...simpleExtra] + const simpleAdhocFilters = filters.reduce( + (acc, filter) => { + + if ( ("subject" in filter) && ("comparator" in filter) ) { + acc.push( {columnName: filter.subject, value: filter.comparator} ) + } + else if ( ("col" in filter) && ("val" in filter) ) { + acc.push( {columnName: filter.col.toString(), value: filter.val} ) + } + return acc + }, <({columnName: string, value: string | number | boolean | (string | number | boolean)[]})[]> [] + ) + + return simpleAdhocFilters } export default function transformProps(chartProps: ChartProps) { @@ -81,23 +69,15 @@ export default function transformProps(chartProps: ChartProps) { * function during development with hot reloading, changes won't * be seen until restarting the development server. */ - const { formData, } = chartProps; + const formData = chartProps.formData; const { url, parameterColumnName, parameterName } = formData - - - let url_parameter_value = ''; - - - // eslint-disable-next-line no-plusplus - for (let i = 0; i < formData?.extraFormData?.filters?.length; i++) { - const adhocfilter = formData?.extraFormData?.filters[i]; - if (adhocfilter.col === parameterColumnName) { - url_parameter_value = adhocfilter.val; - break; - } - } + const allFilters = extractFiltersFromFormData(formData); + + const url_parameter_value = String(allFilters.find( e => { + return e.columnName == parameterColumnName + })?.value); return { url_parameter_value, diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts index 0b20023c4870f..0bbc30d050e49 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts @@ -1,4 +1,4 @@ -import { QueryFormData, TimeseriesDataRecord } from '@superset-ui/core'; +import { QueryFormData } from '@superset-ui/core'; export type IFrameVisualizationProps = QueryFormData & { url_parameter_value: string; From bc0419d91342e830338eebf01c60d7b455ef094f Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 18 Oct 2022 11:54:09 -0400 Subject: [PATCH 06/31] [CLDN-1749] removing unused files --- .../plugin-chart-iframe/src/images/alfred.png | Bin 2753 -> 0 bytes .../plugin-chart-iframe/src/images/thumbnail.png | Bin 5658 -> 0 bytes .../plugin-chart-iframe/types/external.d.ts | 4 ---- 3 files changed, 4 deletions(-) delete mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/alfred.png delete mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png delete mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/types/external.d.ts diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/alfred.png b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/alfred.png deleted file mode 100644 index ab1d3036b5f6f78d8974946c6bfe6c295edd48e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2753 zcmV;y3O@CTP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D3RX!(K~#8N?V5W~ zRM#EHeU9Vf4@bwxf6Az{oxx#n*6QFeSrNgqsi;Uap=e^`1GHLUVlt-KZc2THR>An# zifd#68^wqqM#IC`DpIY42yrXbfKWh1bVU&s!Eetw_nylIcdNqgI?27?nKSJDopaA# z&gcAo=XcKDUQnedslXK`6}ZBr0#}$+;0lupTwzjyD@-bIg-HdjFsZ;5CKb5Cqykr% z+zx!|)T!vzs~0OeosQX_KYz~ts`9+t0=&Jwo&7B;Dng$=ebBdWUsn3{>xXOCuCY25 z`kRqkfk#G0GSBYayEiNJb98hxtK)4{c~))<>9w`B7&2rC13h~5Xp9;)ihUkFd^jFF zcp$b?I;n_qJ`@5FZ~eUGLt#o3&BB-1BmahuiITj2kzOdGvq*19125 zT~t(5ur-|8e0_a?Qfqj*Q4cRKFL=e~ik1#F-ORyXA_Cx}A@!t<_q%DhCDyrL^2^%*;&Y$rSFqdGnZ!Dkvz3*=XIlbLW^%?&WRmq(2XOy*)P; z8VXVqg>0K>bReg|%S{LsL3??5L+kGkzsQuIFYvD7UORKW*Sj=SOB!|vki#JkVq?bH)^+x!he zOvh36v<(Li96+z$eVF$yU%s5Rmxpu?JuEDY0jF*08h`aa^$1w?CEi-L2XFm$52k-; zg7HuxT1l9vqMq*3tiCt_)9AqI5(#>u(nRR!wQn3O4#vAP@LHTsT95VXS7NrWFxa|C zdQHn$py!BEd!0_`mlu&reeNgl<5|7dAO0lSKaI9$1{6PAPi^f$kT`-UVJ7_h%}mr zbSa-GH-(CDLy=I_(cW(R4(7#L>3duToXSR$tuL-w5q`cHi%wc_vAT}gn~1l!JZr;$ zA2wp;*XJK{HKVZ0SB_GhgA zl*J1D(qzn7WWwL}7U0q2W@hiPY}trn-lc;)g7Y<96O~Q!_s;4zaQ!aGomIXw2sb-O z5$zVlF^}cXRtN*T){tF9J!{fQ_AA)T@(&jRzi{U+7N5O@1s994v&4!Xxr4ZLfGJr}WTvc32nT)DAApAocy zbr*u!GcAMTU8D17aHjtYeC;{4v*{>;K0Snjk}4MZr%aiGh6bU3-lzNk=2G#G zSQvc(0pWYGD0;tW?T&8494-vdiyr2@&Sk*MjnGs3D-=E5GUUbz*Ig&4B$orfg6(Bi z61IgfOF7FumTtGGwr<_Z*5rX?Z(UMSg3AQzuamM6o^%W`2XYbo=Yt6PQyOBDPvTb_ z(lO_=!vuUi&YnGsK7ISLd-v}bK=MAR=y&mT`zguVKl|$m0{lyM-7QH29u7wlwKhFM z7+{{viE=$~;Z%+$YtG7&v|FABE|;@>iH2i(Zccyirb$>4i3(J=?ljgWFiC_EHdw}B9pZPZ0D1B`!iy6fXX#!VANYmEz%27(Ne<7_6LGe}F74ah2Aq3ka&oe?_C`cRFq@;b zR}Nafx8~x|XUv$v`l5a2$QP%Kw7O<0%WmRn5{d2_&pgJ8PWve*}kJ_ zp{i$kE#Mr^g$oxXIFlw#atWB`=4Ol;Glum^6TurdZZNwF-L>i#a2mq7cN>jHX6s3k z#rxf`VFT-vmUZjaFMjslXK`6}ZBr0#}$+;0lupTwzjyD@-bIMG5#Hr<^cI(hX2P00000NkvXX Hu0mjfpbJjG diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png deleted file mode 100644 index 7afef30bd4e6e4f85723208bf9f429647d03d3e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5658 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 diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/types/external.d.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/types/external.d.ts deleted file mode 100644 index 0935dbbd8066c..0000000000000 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/types/external.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.png' { - const value: any; - export default value; -} From ae3aaa40ff312e8dcaf0f05c159d2f5f1146fcfd Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 18 Oct 2022 13:02:39 -0400 Subject: [PATCH 07/31] [CLDN-1746] fixing case --- .../src/images/thumbnail.png | Bin 0 -> 5658 bytes .../src/images/thumbnail.png:Zone.Identifier | 4 + .../src/plugin/transformProps.ts | 2 +- .../AnnotationLayer.jsx | 847 ++++++++++++++++++ .../AnnotationLayer.test.tsx | 240 +++++ .../DrillToDashboardLinkConfig/index.jsx | 258 ++++++ 6 files changed, 1350 insertions(+), 1 deletion(-) create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png:Zone.Identifier create mode 100644 superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.jsx create mode 100644 superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.test.tsx create mode 100644 superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/index.jsx diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..7afef30bd4e6e4f85723208bf9f429647d03d3e5 GIT binary patch literal 5658 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 literal 0 HcmV?d00001 diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png:Zone.Identifier b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png:Zone.Identifier new file mode 100644 index 0000000000000..33a7247de00d7 --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png:Zone.Identifier @@ -0,0 +1,4 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=https://github.com/CybercentreCanada/superset/blob/feature/CLDN-750-cccs-1.2/superset-frontend/src/cccs-viz/plugins/plugin-chart-at-a-glance/src/images/thumbnail.png +HostUrl=https://raw.githubusercontent.com/CybercentreCanada/superset/feature/CLDN-750-cccs-1.2/superset-frontend/src/cccs-viz/plugins/plugin-chart-at-a-glance/src/images/thumbnail.png diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index 8300534d6f7a2..b2c717fe55caa 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -21,7 +21,7 @@ import { ChartProps, PlainObject, } from '@superset-ui/core'; const extractFiltersFromFormData = (formData: PlainObject): ({columnName: string, value: string | number | boolean | (string | number | boolean)[] })[] => { - const filters = [...(formData?.adhoc_filters || []), ...(formData?.extra_form_data?.adhoc_filters || []), ...(formData?.extra_form_data?.filters || [])] + const filters = [...(formData?.adhocFilters || []), ...(formData?.extraFormData?.adhocFilters || []), ...(formData?.extraFormData?.filters || [])] const simpleAdhocFilters = filters.reduce( (acc, filter) => { diff --git a/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.jsx b/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.jsx new file mode 100644 index 0000000000000..8df36c1291ac8 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.jsx @@ -0,0 +1,847 @@ +/** + * 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 PropTypes from 'prop-types'; +import { CompactPicker } from 'react-color'; +import Button from 'src/components/Button'; +import { + t, + SupersetClient, + getCategoricalSchemeRegistry, + getChartMetadataRegistry, + validateNonEmpty, + isValidExpression, + styled, + withTheme, +} from '@superset-ui/core'; + +import SelectControl from 'src/explore/components/controls/SelectControl'; +import TextControl from 'src/explore/components/controls/TextControl'; +import CheckboxControl from 'src/explore/components/controls/CheckboxControl'; +import { + ANNOTATION_SOURCE_TYPES, + ANNOTATION_TYPES, + ANNOTATION_TYPES_METADATA, + DEFAULT_ANNOTATION_TYPE, + requiresQuery, + ANNOTATION_SOURCE_TYPES_METADATA, +} from 'src/modules/AnnotationTypes'; +import PopoverSection from 'src/components/PopoverSection'; +import ControlHeader from 'src/explore/components/ControlHeader'; +import { EmptyStateSmall } from 'src/components/EmptyState'; + +const AUTOMATIC_COLOR = ''; + +const propTypes = { + name: PropTypes.string, + annotationType: PropTypes.string, + sourceType: PropTypes.string, + color: PropTypes.string, + opacity: PropTypes.string, + style: PropTypes.string, + width: PropTypes.number, + showMarkers: PropTypes.bool, + hideLine: PropTypes.bool, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + overrides: PropTypes.object, + show: PropTypes.bool, + showLabel: PropTypes.bool, + titleColumn: PropTypes.string, + descriptionColumns: PropTypes.arrayOf(PropTypes.string), + timeColumn: PropTypes.string, + intervalEndColumn: PropTypes.string, + vizType: PropTypes.string, + + error: PropTypes.string, + colorScheme: PropTypes.string, + + addAnnotationLayer: PropTypes.func, + removeAnnotationLayer: PropTypes.func, + close: PropTypes.func, +}; + +const defaultProps = { + name: '', + annotationType: DEFAULT_ANNOTATION_TYPE, + sourceType: '', + color: AUTOMATIC_COLOR, + opacity: '', + style: 'solid', + width: 1, + showMarkers: false, + hideLine: false, + overrides: {}, + colorScheme: 'd3Category10', + show: true, + showLabel: false, + titleColumn: '', + descriptionColumns: [], + timeColumn: '', + intervalEndColumn: '', + + addAnnotationLayer: () => {}, + removeAnnotationLayer: () => {}, + close: () => {}, +}; + +const NotFoundContentWrapper = styled.div` + && > div:first-child { + padding-left: 0; + padding-right: 0; + } +`; + +const NotFoundContent = () => ( + + + {t('Add an annotation layer')}{' '} + + {t('here')} + + . + + } + image="empty.svg" + /> + +); + +class AnnotationLayer extends React.PureComponent { + constructor(props) { + super(props); + const { + name, + annotationType, + sourceType, + color, + opacity, + style, + width, + showMarkers, + hideLine, + value, + overrides, + show, + showLabel, + titleColumn, + descriptionColumns, + timeColumn, + intervalEndColumn, + vizType, + } = props; + + // Only allow override whole time_range + if ('since' in overrides || 'until' in overrides) { + overrides.time_range = null; + delete overrides.since; + delete overrides.until; + } + + // Check if annotationType is supported by this chart + const metadata = getChartMetadataRegistry().get(vizType); + const supportedAnnotationTypes = metadata?.supportedAnnotationTypes || []; + const validAnnotationType = supportedAnnotationTypes.includes( + annotationType, + ) + ? annotationType + : supportedAnnotationTypes[0]; + + this.state = { + // base + name, + annotationType: validAnnotationType, + sourceType, + value, + overrides, + show, + showLabel, + // slice + titleColumn, + descriptionColumns, + timeColumn, + intervalEndColumn, + // display + color: color || AUTOMATIC_COLOR, + opacity, + style, + width, + showMarkers, + hideLine, + // refData + isNew: !name, + isLoadingOptions: true, + valueOptions: [], + }; + this.submitAnnotation = this.submitAnnotation.bind(this); + this.deleteAnnotation = this.deleteAnnotation.bind(this); + this.applyAnnotation = this.applyAnnotation.bind(this); + this.fetchOptions = this.fetchOptions.bind(this); + this.handleAnnotationType = this.handleAnnotationType.bind(this); + this.handleAnnotationSourceType = + this.handleAnnotationSourceType.bind(this); + this.handleValue = this.handleValue.bind(this); + this.isValidForm = this.isValidForm.bind(this); + } + + componentDidMount() { + const { annotationType, sourceType, isLoadingOptions } = this.state; + this.fetchOptions(annotationType, sourceType, isLoadingOptions); + } + + componentDidUpdate(prevProps, prevState) { + if (prevState.sourceType !== this.state.sourceType) { + this.fetchOptions(this.state.annotationType, this.state.sourceType, true); + } + } + + getSupportedSourceTypes(annotationType) { + // Get vis types that can be source. + const sources = getChartMetadataRegistry() + .entries() + .filter(({ value: chartMetadata }) => + chartMetadata.canBeAnnotationType(annotationType), + ) + .map(({ key, value: chartMetadata }) => ({ + value: key, + label: chartMetadata.name, + })); + // Prepend native source if applicable + if (ANNOTATION_TYPES_METADATA[annotationType]?.supportNativeSource) { + sources.unshift(ANNOTATION_SOURCE_TYPES_METADATA.NATIVE); + } + return sources; + } + + isValidFormulaAnnotation(expression, annotationType) { + if (annotationType === ANNOTATION_TYPES.FORMULA) { + return isValidExpression(expression); + } + return true; + } + + isValidForm() { + const { + name, + annotationType, + sourceType, + value, + timeColumn, + intervalEndColumn, + } = this.state; + const errors = [ + validateNonEmpty(name), + validateNonEmpty(annotationType), + validateNonEmpty(value), + ]; + if (sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE) { + if (annotationType === ANNOTATION_TYPES.EVENT) { + errors.push(validateNonEmpty(timeColumn)); + } + if (annotationType === ANNOTATION_TYPES.INTERVAL) { + errors.push(validateNonEmpty(timeColumn)); + errors.push(validateNonEmpty(intervalEndColumn)); + } + } + errors.push(!this.isValidFormulaAnnotation(value, annotationType)); + return !errors.filter(x => x).length; + } + + handleAnnotationType(annotationType) { + this.setState({ + annotationType, + sourceType: null, + value: null, + }); + } + + handleAnnotationSourceType(sourceType) { + const { sourceType: prevSourceType } = this.state; + + if (prevSourceType !== sourceType) { + this.setState({ sourceType, value: null, isLoadingOptions: true }); + } + } + + handleValue(value) { + this.setState({ + value, + descriptionColumns: [], + intervalEndColumn: null, + timeColumn: null, + titleColumn: null, + overrides: { time_range: null }, + }); + } + + fetchOptions(annotationType, sourceType, isLoadingOptions) { + if (isLoadingOptions) { + if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) { + SupersetClient.get({ + endpoint: '/annotationlayermodelview/api/read?', + }).then(({ json }) => { + const layers = json + ? json.result.map(layer => ({ + value: layer.id, + label: layer.name, + })) + : []; + this.setState({ + isLoadingOptions: false, + valueOptions: layers, + }); + }); + } else if (requiresQuery(sourceType)) { + SupersetClient.get({ endpoint: '/superset/user_slices' }).then( + ({ json }) => { + const registry = getChartMetadataRegistry(); + this.setState({ + isLoadingOptions: false, + valueOptions: json + .filter(x => { + const metadata = registry.get(x.viz_type); + return ( + metadata && metadata.canBeAnnotationType(annotationType) + ); + }) + .map(x => ({ value: x.id, label: x.title, slice: x })), + }); + }, + ); + } else { + this.setState({ + isLoadingOptions: false, + valueOptions: [], + }); + } + } + } + + deleteAnnotation() { + this.props.removeAnnotationLayer(); + this.props.close(); + } + + applyAnnotation() { + if (this.isValidForm()) { + const annotationFields = [ + 'name', + 'annotationType', + 'sourceType', + 'color', + 'opacity', + 'style', + 'width', + 'showMarkers', + 'hideLine', + 'value', + 'overrides', + 'show', + 'showLabel', + 'titleColumn', + 'descriptionColumns', + 'timeColumn', + 'intervalEndColumn', + ]; + const newAnnotation = {}; + annotationFields.forEach(field => { + if (this.state[field] !== null) { + newAnnotation[field] = this.state[field]; + } + }); + + if (newAnnotation.color === AUTOMATIC_COLOR) { + newAnnotation.color = null; + } + + this.props.addAnnotationLayer(newAnnotation); + this.setState({ isNew: false }); + } + } + + submitAnnotation() { + this.applyAnnotation(); + this.props.close(); + } + + renderOption(option) { + return ( + + {option.label} + + ); + } + + renderValueConfiguration() { + const { + annotationType, + sourceType, + value, + valueOptions, + isLoadingOptions, + } = this.state; + let label = ''; + let description = ''; + if (requiresQuery(sourceType)) { + if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) { + label = 'Annotation layer'; + description = 'Select the Annotation Layer you would like to use.'; + } else { + label = t('Chart'); + description = t( + `Use another existing chart as a source for annotations and overlays. + Your chart must be one of these visualization types: [%s]`, + this.getSupportedSourceTypes(annotationType) + .map(x => x.label) + .join(', '), + ); + } + } else if (annotationType === ANNOTATION_TYPES.FORMULA) { + label = 'Formula'; + description = `Expects a formula with depending time parameter 'x' + in milliseconds since epoch. mathjs is used to evaluate the formulas. + Example: '2x+5'`; + } + if (requiresQuery(sourceType)) { + return ( + } + /> + ); + } + if (annotationType === ANNOTATION_TYPES.FORMULA) { + return ( + + ); + } + return ''; + } + + renderSliceConfiguration() { + const { + annotationType, + sourceType, + value, + valueOptions, + overrides, + titleColumn, + timeColumn, + intervalEndColumn, + descriptionColumns, + } = this.state; + const { slice } = valueOptions.find(x => x.value === value) || {}; + if (sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE && slice) { + const columns = (slice.data.groupby || []) + .concat(slice.data.all_columns || []) + .map(x => ({ value: x, label: x })); + const timeColumnOptions = slice.data.include_time + ? [{ value: '__timestamp', label: '__timestamp' }].concat(columns) + : columns; + return ( +

+ + {(annotationType === ANNOTATION_TYPES.EVENT || + annotationType === ANNOTATION_TYPES.INTERVAL) && ( + this.setState({ timeColumn: v })} + /> + )} + {annotationType === ANNOTATION_TYPES.INTERVAL && ( + this.setState({ intervalEndColumn: value })} + /> + )} + this.setState({ titleColumn: value })} + /> + {annotationType !== ANNOTATION_TYPES.TIME_SERIES && ( + this.setState({ descriptionColumns: value })} + /> + )} +
+ { + delete overrides.time_range; + if (v) { + this.setState({ + overrides: { ...overrides, time_range: null }, + }); + } else { + this.setState({ overrides: { ...overrides } }); + } + }} + /> + { + delete overrides.time_grain_sqla; + delete overrides.granularity; + if (v) { + this.setState({ + overrides: { + ...overrides, + time_grain_sqla: null, + granularity: null, + }, + }); + } else { + this.setState({ overrides: { ...overrides } }); + } + }} + /> + + this.setState({ overrides: { ...overrides, time_shift: v } }) + } + /> +
+
+
+ ); + } + return ''; + } + + renderDisplayConfiguration() { + const { + color, + opacity, + style, + width, + showMarkers, + hideLine, + annotationType, + } = this.state; + const colorScheme = getCategoricalSchemeRegistry() + .get(this.props.colorScheme) + .colors.concat(); + if ( + color && + color !== AUTOMATIC_COLOR && + !colorScheme.find(x => x.toLowerCase() === color.toLowerCase()) + ) { + colorScheme.push(color); + } + return ( + + this.setState({ style: v })} + /> + this.setState({ opacity: value })} + /> +
+ +
+ this.setState({ color: v.hex })} + /> + +
+
+ this.setState({ width: v })} + /> + {annotationType === ANNOTATION_TYPES.TIME_SERIES && ( + this.setState({ showMarkers: v })} + /> + )} + {annotationType === ANNOTATION_TYPES.TIME_SERIES && ( + this.setState({ hideLine: v })} + /> + )} +
+ ); + } + + render() { + const { isNew, name, annotationType, sourceType, show, showLabel } = + this.state; + const isValid = this.isValidForm(); + const metadata = getChartMetadataRegistry().get(this.props.vizType); + const supportedAnnotationTypes = metadata + ? metadata.supportedAnnotationTypes.map( + type => ANNOTATION_TYPES_METADATA[type], + ) + : []; + const supportedSourceTypes = this.getSupportedSourceTypes(annotationType); + + return ( + <> + {this.props.error && ( + + ERROR: {this.props.error} + + )} +
+
+ + this.setState({ name: v })} + validationErrors={!name ? [t('Mandatory')] : []} + /> + this.setState({ show: !v })} + /> + this.setState({ showLabel: v })} + /> + + {supportedSourceTypes.length > 0 && ( + } + value={sourceType} + onChange={this.handleAnnotationSourceType} + validationErrors={!sourceType ? [t('Mandatory')] : []} + /> + )} + {this.renderValueConfiguration()} + +
+ {this.renderSliceConfiguration()} + {this.renderDisplayConfiguration()} +
+
+ {isNew ? ( + + ) : ( + + )} +
+ + + +
+
+ + ); + } +} + +AnnotationLayer.propTypes = propTypes; +AnnotationLayer.defaultProps = defaultProps; + +export default withTheme(AnnotationLayer); diff --git a/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.test.tsx b/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.test.tsx new file mode 100644 index 0000000000000..ed8ae44339b2e --- /dev/null +++ b/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.test.tsx @@ -0,0 +1,240 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; +import userEvent from '@testing-library/user-event'; +import { getChartMetadataRegistry, ChartMetadata } from '@superset-ui/core'; +import fetchMock from 'fetch-mock'; +import setupColors from 'src/setup/setupColors'; +import { ANNOTATION_TYPES_METADATA } from 'src/modules/AnnotationTypes'; +import AnnotationLayer from './AnnotationLayer'; + +const defaultProps = { + value: '', + vizType: 'table', + annotationType: ANNOTATION_TYPES_METADATA.FORMULA.value, +}; + +beforeAll(() => { + const supportedAnnotationTypes = Object.values(ANNOTATION_TYPES_METADATA).map( + value => value.value, + ); + + fetchMock.get('glob:*/annotationlayermodelview/api/read?*', { + result: [{ label: 'Chart A', value: 'a' }], + }); + + fetchMock.get('glob:*/superset/user_slices*', [ + { id: 'a', title: 'Chart A', viz_type: 'table', data: {} }, + ]); + + setupColors(); + + getChartMetadataRegistry().registerValue( + 'table', + new ChartMetadata({ + name: 'Table', + thumbnail: '', + supportedAnnotationTypes, + canBeAnnotationTypes: ['EVENT'], + }), + ); +}); + +const waitForRender = (props?: any) => + waitFor(() => render()); + +test('renders with default props', async () => { + await waitForRender(); + expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'OK' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeEnabled(); +}); + +test('renders extra checkboxes when type is time series', async () => { + await waitForRender(); + expect( + screen.queryByRole('button', { name: 'Show Markers' }), + ).not.toBeInTheDocument(); + expect( + screen.queryByRole('button', { name: 'Hide Line' }), + ).not.toBeInTheDocument(); + userEvent.click(screen.getAllByText('Formula')[0]); + userEvent.click(screen.getByText('Time series')); + expect( + screen.getByRole('button', { name: 'Show Markers' }), + ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Hide Line' })).toBeInTheDocument(); +}); + +test('enables apply and ok buttons', async () => { + const { container } = render(); + + waitFor(() => { + expect(container).toBeInTheDocument(); + }); + + const nameInput = screen.getByRole('textbox', { name: 'Name' }); + const formulaInput = screen.getByRole('textbox', { name: 'Formula' }); + + expect(nameInput).toBeInTheDocument(); + expect(formulaInput).toBeInTheDocument(); + + userEvent.type(nameInput, 'Name'); + userEvent.type(formulaInput, '2x'); + + waitFor(() => { + expect(screen.getByRole('button', { name: 'Apply' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'OK' })).toBeEnabled(); + }); +}); + +test('triggers addAnnotationLayer when apply button is clicked', async () => { + const addAnnotationLayer = jest.fn(); + await waitForRender({ name: 'Test', value: '2x', addAnnotationLayer }); + userEvent.click(screen.getByRole('button', { name: 'Apply' })); + expect(addAnnotationLayer).toHaveBeenCalled(); +}); + +test('triggers addAnnotationLayer and close when ok button is clicked', async () => { + const addAnnotationLayer = jest.fn(); + const close = jest.fn(); + await waitForRender({ name: 'Test', value: '2x', addAnnotationLayer, close }); + userEvent.click(screen.getByRole('button', { name: 'OK' })); + expect(addAnnotationLayer).toHaveBeenCalled(); + expect(close).toHaveBeenCalled(); +}); + +test('triggers close when cancel button is clicked', async () => { + const close = jest.fn(); + await waitForRender({ close }); + userEvent.click(screen.getByRole('button', { name: 'Cancel' })); + expect(close).toHaveBeenCalled(); +}); + +test('triggers removeAnnotationLayer and close when remove button is clicked', async () => { + const removeAnnotationLayer = jest.fn(); + const close = jest.fn(); + await waitForRender({ + name: 'Test', + value: '2x', + removeAnnotationLayer, + close, + }); + userEvent.click(screen.getByRole('button', { name: 'Remove' })); + expect(removeAnnotationLayer).toHaveBeenCalled(); + expect(close).toHaveBeenCalled(); +}); + +test('renders chart options', async () => { + await waitForRender({ + annotationType: ANNOTATION_TYPES_METADATA.EVENT.value, + }); + userEvent.click( + screen.getByRole('combobox', { name: 'Annotation source type' }), + ); + userEvent.click(screen.getByText('Superset annotation')); + expect(screen.getByText('Annotation layer')).toBeInTheDocument(); + + userEvent.click( + screen.getByRole('combobox', { name: 'Annotation source type' }), + ); + userEvent.click(screen.getByText('Table')); + expect(screen.getByText('Chart')).toBeInTheDocument(); +}); + +test('keeps apply disabled when missing required fields', async () => { + await waitForRender({ + annotationType: ANNOTATION_TYPES_METADATA.EVENT.value, + sourceType: 'Table', + }); + userEvent.click( + screen.getByRole('combobox', { name: 'Annotation layer value' }), + ); + userEvent.click(await screen.findByText('Chart A')); + expect( + screen.getByText('Annotation Slice Configuration'), + ).toBeInTheDocument(); + + userEvent.click(screen.getByRole('button', { name: 'Automatic Color' })); + userEvent.click( + screen.getByRole('combobox', { name: 'Annotation layer title column' }), + ); + userEvent.click(screen.getByText('None')); + userEvent.click(screen.getByText('Style')); + userEvent.click( + screen.getByRole('combobox', { name: 'Annotation layer stroke' }), + ); + userEvent.click(screen.getByText('Dashed')); + userEvent.click(screen.getByText('Opacity')); + userEvent.click( + screen.getByRole('combobox', { name: 'Annotation layer opacity' }), + ); + userEvent.click(screen.getByText('0.5')); + + const checkboxes = screen.getAllByRole('checkbox'); + checkboxes.forEach(checkbox => userEvent.click(checkbox)); + + expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled(); +}); + +test.skip('Disable apply button if formula is incorrect', async () => { + // TODO: fix flaky test that passes locally but fails on CI + await waitForRender({ name: 'test' }); + + userEvent.clear(screen.getByLabelText('Formula')); + userEvent.type(screen.getByLabelText('Formula'), 'x+1'); + await waitFor(() => { + expect(screen.getByLabelText('Formula')).toHaveValue('x+1'); + expect(screen.getByRole('button', { name: 'OK' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Apply' })).toBeEnabled(); + }); + + userEvent.clear(screen.getByLabelText('Formula')); + userEvent.type(screen.getByLabelText('Formula'), 'y = x*2+1'); + await waitFor(() => { + expect(screen.getByLabelText('Formula')).toHaveValue('y = x*2+1'); + expect(screen.getByRole('button', { name: 'OK' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Apply' })).toBeEnabled(); + }); + + userEvent.clear(screen.getByLabelText('Formula')); + userEvent.type(screen.getByLabelText('Formula'), 'y+1'); + await waitFor(() => { + expect(screen.getByLabelText('Formula')).toHaveValue('y+1'); + expect(screen.getByRole('button', { name: 'OK' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled(); + }); + + userEvent.clear(screen.getByLabelText('Formula')); + userEvent.type(screen.getByLabelText('Formula'), 'x+'); + await waitFor(() => { + expect(screen.getByLabelText('Formula')).toHaveValue('x+'); + expect(screen.getByRole('button', { name: 'OK' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled(); + }); + + userEvent.clear(screen.getByLabelText('Formula')); + userEvent.type(screen.getByLabelText('Formula'), 'y = z+1'); + await waitFor(() => { + expect(screen.getByLabelText('Formula')).toHaveValue('y = z+1'); + expect(screen.getByRole('button', { name: 'OK' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled(); + }); +}); diff --git a/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/index.jsx b/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/index.jsx new file mode 100644 index 0000000000000..f1381abee1aa4 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/index.jsx @@ -0,0 +1,258 @@ +/** + * 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 PropTypes from 'prop-types'; +import { List } from 'src/components'; +import { connect } from 'react-redux'; +import { t, withTheme } from '@superset-ui/core'; +import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls'; +import AsyncEsmComponent from 'src/components/AsyncEsmComponent'; +import { getChartKey } from 'src/explore/exploreUtils'; +import { runAnnotationQuery } from 'src/components/Chart/chartAction'; +import CustomListItem from 'src/explore/components/controls/CustomListItem'; +import ControlPopover from '../ControlPopover/ControlPopover'; + +const AnnotationLayer = AsyncEsmComponent( + () => import('./AnnotationLayer'), + // size of overlay inner content + () =>
, +); + +const propTypes = { + colorScheme: PropTypes.string.isRequired, + annotationError: PropTypes.object, + annotationQuery: PropTypes.object, + vizType: PropTypes.string, + + validationErrors: PropTypes.array, + name: PropTypes.string.isRequired, + actions: PropTypes.object, + value: PropTypes.arrayOf(PropTypes.object), + onChange: PropTypes.func, + refreshAnnotationData: PropTypes.func, +}; + +const defaultProps = { + vizType: '', + value: [], + annotationError: {}, + annotationQuery: {}, + onChange: () => {}, +}; + +class AnnotationLayerControl extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + popoverVisible: {}, + addedAnnotationIndex: null, + }; + this.addAnnotationLayer = this.addAnnotationLayer.bind(this); + this.removeAnnotationLayer = this.removeAnnotationLayer.bind(this); + this.handleVisibleChange = this.handleVisibleChange.bind(this); + } + + componentDidMount() { + // preload the AnotationLayer component and dependent libraries i.e. mathjs + AnnotationLayer.preload(); + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { name, annotationError, validationErrors, value } = nextProps; + if (Object.keys(annotationError).length && !validationErrors.length) { + this.props.actions.setControlValue( + name, + value, + Object.keys(annotationError), + ); + } + if (!Object.keys(annotationError).length && validationErrors.length) { + this.props.actions.setControlValue(name, value, []); + } + } + + addAnnotationLayer(originalAnnotation, newAnnotation) { + let annotations = this.props.value; + if (annotations.includes(originalAnnotation)) { + annotations = annotations.map(anno => + anno === originalAnnotation ? newAnnotation : anno, + ); + } else { + annotations = [...annotations, newAnnotation]; + this.setState({ addedAnnotationIndex: annotations.length - 1 }); + } + + this.props.refreshAnnotationData({ + annotation: newAnnotation, + force: true, + }); + + this.props.onChange(annotations); + } + + handleVisibleChange(visible, popoverKey) { + this.setState(prevState => ({ + popoverVisible: { ...prevState.popoverVisible, [popoverKey]: visible }, + })); + } + + removeAnnotationLayer(annotation) { + const annotations = this.props.value.filter(anno => anno !== annotation); + this.props.onChange(annotations); + } + + renderPopover(popoverKey, annotation, error) { + const id = annotation?.name || '_new'; + + return ( +
+ + this.addAnnotationLayer(annotation, newAnnotation) + } + removeAnnotationLayer={() => this.removeAnnotationLayer(annotation)} + close={() => { + this.handleVisibleChange(false, popoverKey); + this.setState({ addedAnnotationIndex: null }); + }} + /> +
+ ); + } + + renderInfo(anno) { + const { annotationError, annotationQuery, theme } = this.props; + if (annotationQuery[anno.name]) { + return ( + + ); + } + if (annotationError[anno.name]) { + return ( + + ); + } + if (!anno.show) { + return Hidden ; + } + return ''; + } + + render() { + const { addedAnnotationIndex } = this.state; + const addedAnnotation = this.props.value[addedAnnotationIndex]; + + const annotations = this.props.value.map((anno, i) => ( + ({ + '&:hover': { + cursor: 'pointer', + backgroundColor: theme.colors.grayscale.light4, + }, + })} + content={this.renderPopover( + i, + anno, + this.props.annotationError[anno.name], + )} + visible={this.state.popoverVisible[i]} + onVisibleChange={visible => this.handleVisibleChange(visible, i)} + > + + {anno.name} + {this.renderInfo(anno)} + + + )); + + const addLayerPopoverKey = 'add'; + return ( +
+ ({ borderRadius: theme.gridUnit })}> + {annotations} + + this.handleVisibleChange(visible, addLayerPopoverKey) + } + > + + {' '} +   {t('Add annotation layer')} + + + +
+ ); + } +} + +AnnotationLayerControl.propTypes = propTypes; +AnnotationLayerControl.defaultProps = defaultProps; + +// 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 }) { + const chartKey = getChartKey(explore); + const chart = charts[chartKey] || charts[0] || {}; + + return { + // eslint-disable-next-line camelcase + colorScheme: explore.controls?.color_scheme?.value, + annotationError: chart.annotationError, + annotationQuery: chart.annotationQuery, + vizType: explore.controls.viz_type.value, + }; +} + +function mapDispatchToProps(dispatch) { + return { + refreshAnnotationData: annotationObj => + dispatch(runAnnotationQuery(annotationObj)), + }; +} + +const themedAnnotationLayerControl = withTheme(AnnotationLayerControl); + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(themedAnnotationLayerControl); From 500a8995feac263be4e3ac2940584f83546ea818 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Wed, 26 Oct 2022 13:54:28 -0400 Subject: [PATCH 08/31] [CLDN-1749] adding parameter validation --- .../plugin-chart-iframe/src/plugin/controlPanel.ts | 12 ++++++++++++ .../plugin-chart-iframe/src/plugin/transformProps.ts | 3 ++- .../plugins/plugin-chart-iframe/src/types.ts | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts index 9983dd5666a2e..dda69f9c1ec07 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts @@ -183,6 +183,18 @@ const config: ControlPanelConfig = { description: t('The name for the URL parameter.'), }, }, + ], + [ + { + name: 'parameter_prefix', + config: { + type: 'TextControl', + label: t('Parameter Prefix'), + renderTrigger: true, + default: '', + description: t('A value that will be predened the parameter value.'), + }, + }, ] ], }, diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index b2c717fe55caa..6a654ab8b50c8 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -71,7 +71,7 @@ export default function transformProps(chartProps: ChartProps) { */ const formData = chartProps.formData; - const { url, parameterColumnName, parameterName } = formData + const { url, parameterColumnName, parameterName, parameterPrefix } = formData const allFilters = extractFiltersFromFormData(formData); @@ -83,5 +83,6 @@ export default function transformProps(chartProps: ChartProps) { url_parameter_value, parameter_name: parameterName, url, + parameter_prefix: parameterPrefix, }; } diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts index 0bbc30d050e49..bdf1feecb2fc0 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts @@ -4,4 +4,5 @@ export type IFrameVisualizationProps = QueryFormData & { url_parameter_value: string; parameter_name: string; url: string; + parameter_prefix: string; }; From 3ac453be23991469e5f8f2cb0540752eea923138 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 1 Nov 2022 08:59:40 -0400 Subject: [PATCH 09/31] [CLDN-1749] adding error message and prefix parameter --- .../src/IFrameVisualization.tsx | 10 +++++++--- .../src/plugin/transformProps.ts | 19 ++++++++++++++++++- .../plugins/plugin-chart-iframe/src/types.ts | 1 + 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx index 659db9fa3ced8..fa1cec2d74420 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/IFrameVisualization.tsx @@ -3,11 +3,15 @@ import { IFrameVisualizationProps } from './types'; export default function IFrameVisualization(props: IFrameVisualizationProps) { - const { url, url_parameter_value, parameter_name } = props + const { url, url_parameter_value, parameter_name, parameter_prefix, errorMessage} = props - const parserdUrlParameterName = parameter_name.includes('=') ? parameter_name : `${parameter_name}=` + const parserdUrlParameterName = parameter_name.includes('=') ? parameter_name : `${parameter_name}=${parameter_prefix}` return ( - + <> + { errorMessage ? + <>{errorMessage} : + } + ); } diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index 6a654ab8b50c8..5590634e664e9 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -75,14 +75,31 @@ export default function transformProps(chartProps: ChartProps) { const allFilters = extractFiltersFromFormData(formData); - const url_parameter_value = String(allFilters.find( e => { + const url_parameter_raw_value = String(allFilters.find( e => { return e.columnName == parameterColumnName })?.value); + + let errorMessage = ""; + + if(Array.isArray(url_parameter_raw_value) && url_parameter_raw_value.length > 1) { + errorMessage = "More than one value received, please emit a single value." + } + + if(Array.isArray(url_parameter_raw_value) && url_parameter_raw_value.length === 0) { + errorMessage = "No value received, please emit a single value." + } + + if(url_parameter_raw_value === undefined || url_parameter_raw_value === "undefined") { + errorMessage = "No value received, please emit a single value." + } + + const url_parameter_value = url_parameter_raw_value.toString() return { url_parameter_value, parameter_name: parameterName, url, parameter_prefix: parameterPrefix, + errorMessage, }; } diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts index bdf1feecb2fc0..bd24375ee377c 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/types.ts @@ -5,4 +5,5 @@ export type IFrameVisualizationProps = QueryFormData & { parameter_name: string; url: string; parameter_prefix: string; + errorMessage: string; }; From 0e5d05be81d7d924e5cdd6aa0922dbda4521a1a7 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 1 Nov 2022 09:04:55 -0400 Subject: [PATCH 10/31] [CLDN-1749] removing unused files --- .../AnnotationLayer.jsx | 847 ------------------ .../AnnotationLayer.test.tsx | 240 ----- .../DrillToDashboardLinkConfig/index.jsx | 258 ------ 3 files changed, 1345 deletions(-) delete mode 100644 superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.jsx delete mode 100644 superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.test.tsx delete mode 100644 superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/index.jsx diff --git a/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.jsx b/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.jsx deleted file mode 100644 index 8df36c1291ac8..0000000000000 --- a/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.jsx +++ /dev/null @@ -1,847 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { CompactPicker } from 'react-color'; -import Button from 'src/components/Button'; -import { - t, - SupersetClient, - getCategoricalSchemeRegistry, - getChartMetadataRegistry, - validateNonEmpty, - isValidExpression, - styled, - withTheme, -} from '@superset-ui/core'; - -import SelectControl from 'src/explore/components/controls/SelectControl'; -import TextControl from 'src/explore/components/controls/TextControl'; -import CheckboxControl from 'src/explore/components/controls/CheckboxControl'; -import { - ANNOTATION_SOURCE_TYPES, - ANNOTATION_TYPES, - ANNOTATION_TYPES_METADATA, - DEFAULT_ANNOTATION_TYPE, - requiresQuery, - ANNOTATION_SOURCE_TYPES_METADATA, -} from 'src/modules/AnnotationTypes'; -import PopoverSection from 'src/components/PopoverSection'; -import ControlHeader from 'src/explore/components/ControlHeader'; -import { EmptyStateSmall } from 'src/components/EmptyState'; - -const AUTOMATIC_COLOR = ''; - -const propTypes = { - name: PropTypes.string, - annotationType: PropTypes.string, - sourceType: PropTypes.string, - color: PropTypes.string, - opacity: PropTypes.string, - style: PropTypes.string, - width: PropTypes.number, - showMarkers: PropTypes.bool, - hideLine: PropTypes.bool, - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - overrides: PropTypes.object, - show: PropTypes.bool, - showLabel: PropTypes.bool, - titleColumn: PropTypes.string, - descriptionColumns: PropTypes.arrayOf(PropTypes.string), - timeColumn: PropTypes.string, - intervalEndColumn: PropTypes.string, - vizType: PropTypes.string, - - error: PropTypes.string, - colorScheme: PropTypes.string, - - addAnnotationLayer: PropTypes.func, - removeAnnotationLayer: PropTypes.func, - close: PropTypes.func, -}; - -const defaultProps = { - name: '', - annotationType: DEFAULT_ANNOTATION_TYPE, - sourceType: '', - color: AUTOMATIC_COLOR, - opacity: '', - style: 'solid', - width: 1, - showMarkers: false, - hideLine: false, - overrides: {}, - colorScheme: 'd3Category10', - show: true, - showLabel: false, - titleColumn: '', - descriptionColumns: [], - timeColumn: '', - intervalEndColumn: '', - - addAnnotationLayer: () => {}, - removeAnnotationLayer: () => {}, - close: () => {}, -}; - -const NotFoundContentWrapper = styled.div` - && > div:first-child { - padding-left: 0; - padding-right: 0; - } -`; - -const NotFoundContent = () => ( - - - {t('Add an annotation layer')}{' '} - - {t('here')} - - . - - } - image="empty.svg" - /> - -); - -class AnnotationLayer extends React.PureComponent { - constructor(props) { - super(props); - const { - name, - annotationType, - sourceType, - color, - opacity, - style, - width, - showMarkers, - hideLine, - value, - overrides, - show, - showLabel, - titleColumn, - descriptionColumns, - timeColumn, - intervalEndColumn, - vizType, - } = props; - - // Only allow override whole time_range - if ('since' in overrides || 'until' in overrides) { - overrides.time_range = null; - delete overrides.since; - delete overrides.until; - } - - // Check if annotationType is supported by this chart - const metadata = getChartMetadataRegistry().get(vizType); - const supportedAnnotationTypes = metadata?.supportedAnnotationTypes || []; - const validAnnotationType = supportedAnnotationTypes.includes( - annotationType, - ) - ? annotationType - : supportedAnnotationTypes[0]; - - this.state = { - // base - name, - annotationType: validAnnotationType, - sourceType, - value, - overrides, - show, - showLabel, - // slice - titleColumn, - descriptionColumns, - timeColumn, - intervalEndColumn, - // display - color: color || AUTOMATIC_COLOR, - opacity, - style, - width, - showMarkers, - hideLine, - // refData - isNew: !name, - isLoadingOptions: true, - valueOptions: [], - }; - this.submitAnnotation = this.submitAnnotation.bind(this); - this.deleteAnnotation = this.deleteAnnotation.bind(this); - this.applyAnnotation = this.applyAnnotation.bind(this); - this.fetchOptions = this.fetchOptions.bind(this); - this.handleAnnotationType = this.handleAnnotationType.bind(this); - this.handleAnnotationSourceType = - this.handleAnnotationSourceType.bind(this); - this.handleValue = this.handleValue.bind(this); - this.isValidForm = this.isValidForm.bind(this); - } - - componentDidMount() { - const { annotationType, sourceType, isLoadingOptions } = this.state; - this.fetchOptions(annotationType, sourceType, isLoadingOptions); - } - - componentDidUpdate(prevProps, prevState) { - if (prevState.sourceType !== this.state.sourceType) { - this.fetchOptions(this.state.annotationType, this.state.sourceType, true); - } - } - - getSupportedSourceTypes(annotationType) { - // Get vis types that can be source. - const sources = getChartMetadataRegistry() - .entries() - .filter(({ value: chartMetadata }) => - chartMetadata.canBeAnnotationType(annotationType), - ) - .map(({ key, value: chartMetadata }) => ({ - value: key, - label: chartMetadata.name, - })); - // Prepend native source if applicable - if (ANNOTATION_TYPES_METADATA[annotationType]?.supportNativeSource) { - sources.unshift(ANNOTATION_SOURCE_TYPES_METADATA.NATIVE); - } - return sources; - } - - isValidFormulaAnnotation(expression, annotationType) { - if (annotationType === ANNOTATION_TYPES.FORMULA) { - return isValidExpression(expression); - } - return true; - } - - isValidForm() { - const { - name, - annotationType, - sourceType, - value, - timeColumn, - intervalEndColumn, - } = this.state; - const errors = [ - validateNonEmpty(name), - validateNonEmpty(annotationType), - validateNonEmpty(value), - ]; - if (sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE) { - if (annotationType === ANNOTATION_TYPES.EVENT) { - errors.push(validateNonEmpty(timeColumn)); - } - if (annotationType === ANNOTATION_TYPES.INTERVAL) { - errors.push(validateNonEmpty(timeColumn)); - errors.push(validateNonEmpty(intervalEndColumn)); - } - } - errors.push(!this.isValidFormulaAnnotation(value, annotationType)); - return !errors.filter(x => x).length; - } - - handleAnnotationType(annotationType) { - this.setState({ - annotationType, - sourceType: null, - value: null, - }); - } - - handleAnnotationSourceType(sourceType) { - const { sourceType: prevSourceType } = this.state; - - if (prevSourceType !== sourceType) { - this.setState({ sourceType, value: null, isLoadingOptions: true }); - } - } - - handleValue(value) { - this.setState({ - value, - descriptionColumns: [], - intervalEndColumn: null, - timeColumn: null, - titleColumn: null, - overrides: { time_range: null }, - }); - } - - fetchOptions(annotationType, sourceType, isLoadingOptions) { - if (isLoadingOptions) { - if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) { - SupersetClient.get({ - endpoint: '/annotationlayermodelview/api/read?', - }).then(({ json }) => { - const layers = json - ? json.result.map(layer => ({ - value: layer.id, - label: layer.name, - })) - : []; - this.setState({ - isLoadingOptions: false, - valueOptions: layers, - }); - }); - } else if (requiresQuery(sourceType)) { - SupersetClient.get({ endpoint: '/superset/user_slices' }).then( - ({ json }) => { - const registry = getChartMetadataRegistry(); - this.setState({ - isLoadingOptions: false, - valueOptions: json - .filter(x => { - const metadata = registry.get(x.viz_type); - return ( - metadata && metadata.canBeAnnotationType(annotationType) - ); - }) - .map(x => ({ value: x.id, label: x.title, slice: x })), - }); - }, - ); - } else { - this.setState({ - isLoadingOptions: false, - valueOptions: [], - }); - } - } - } - - deleteAnnotation() { - this.props.removeAnnotationLayer(); - this.props.close(); - } - - applyAnnotation() { - if (this.isValidForm()) { - const annotationFields = [ - 'name', - 'annotationType', - 'sourceType', - 'color', - 'opacity', - 'style', - 'width', - 'showMarkers', - 'hideLine', - 'value', - 'overrides', - 'show', - 'showLabel', - 'titleColumn', - 'descriptionColumns', - 'timeColumn', - 'intervalEndColumn', - ]; - const newAnnotation = {}; - annotationFields.forEach(field => { - if (this.state[field] !== null) { - newAnnotation[field] = this.state[field]; - } - }); - - if (newAnnotation.color === AUTOMATIC_COLOR) { - newAnnotation.color = null; - } - - this.props.addAnnotationLayer(newAnnotation); - this.setState({ isNew: false }); - } - } - - submitAnnotation() { - this.applyAnnotation(); - this.props.close(); - } - - renderOption(option) { - return ( - - {option.label} - - ); - } - - renderValueConfiguration() { - const { - annotationType, - sourceType, - value, - valueOptions, - isLoadingOptions, - } = this.state; - let label = ''; - let description = ''; - if (requiresQuery(sourceType)) { - if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) { - label = 'Annotation layer'; - description = 'Select the Annotation Layer you would like to use.'; - } else { - label = t('Chart'); - description = t( - `Use another existing chart as a source for annotations and overlays. - Your chart must be one of these visualization types: [%s]`, - this.getSupportedSourceTypes(annotationType) - .map(x => x.label) - .join(', '), - ); - } - } else if (annotationType === ANNOTATION_TYPES.FORMULA) { - label = 'Formula'; - description = `Expects a formula with depending time parameter 'x' - in milliseconds since epoch. mathjs is used to evaluate the formulas. - Example: '2x+5'`; - } - if (requiresQuery(sourceType)) { - return ( - } - /> - ); - } - if (annotationType === ANNOTATION_TYPES.FORMULA) { - return ( - - ); - } - return ''; - } - - renderSliceConfiguration() { - const { - annotationType, - sourceType, - value, - valueOptions, - overrides, - titleColumn, - timeColumn, - intervalEndColumn, - descriptionColumns, - } = this.state; - const { slice } = valueOptions.find(x => x.value === value) || {}; - if (sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE && slice) { - const columns = (slice.data.groupby || []) - .concat(slice.data.all_columns || []) - .map(x => ({ value: x, label: x })); - const timeColumnOptions = slice.data.include_time - ? [{ value: '__timestamp', label: '__timestamp' }].concat(columns) - : columns; - return ( -
- - {(annotationType === ANNOTATION_TYPES.EVENT || - annotationType === ANNOTATION_TYPES.INTERVAL) && ( - this.setState({ timeColumn: v })} - /> - )} - {annotationType === ANNOTATION_TYPES.INTERVAL && ( - this.setState({ intervalEndColumn: value })} - /> - )} - this.setState({ titleColumn: value })} - /> - {annotationType !== ANNOTATION_TYPES.TIME_SERIES && ( - this.setState({ descriptionColumns: value })} - /> - )} -
- { - delete overrides.time_range; - if (v) { - this.setState({ - overrides: { ...overrides, time_range: null }, - }); - } else { - this.setState({ overrides: { ...overrides } }); - } - }} - /> - { - delete overrides.time_grain_sqla; - delete overrides.granularity; - if (v) { - this.setState({ - overrides: { - ...overrides, - time_grain_sqla: null, - granularity: null, - }, - }); - } else { - this.setState({ overrides: { ...overrides } }); - } - }} - /> - - this.setState({ overrides: { ...overrides, time_shift: v } }) - } - /> -
-
-
- ); - } - return ''; - } - - renderDisplayConfiguration() { - const { - color, - opacity, - style, - width, - showMarkers, - hideLine, - annotationType, - } = this.state; - const colorScheme = getCategoricalSchemeRegistry() - .get(this.props.colorScheme) - .colors.concat(); - if ( - color && - color !== AUTOMATIC_COLOR && - !colorScheme.find(x => x.toLowerCase() === color.toLowerCase()) - ) { - colorScheme.push(color); - } - return ( - - this.setState({ style: v })} - /> - this.setState({ opacity: value })} - /> -
- -
- this.setState({ color: v.hex })} - /> - -
-
- this.setState({ width: v })} - /> - {annotationType === ANNOTATION_TYPES.TIME_SERIES && ( - this.setState({ showMarkers: v })} - /> - )} - {annotationType === ANNOTATION_TYPES.TIME_SERIES && ( - this.setState({ hideLine: v })} - /> - )} -
- ); - } - - render() { - const { isNew, name, annotationType, sourceType, show, showLabel } = - this.state; - const isValid = this.isValidForm(); - const metadata = getChartMetadataRegistry().get(this.props.vizType); - const supportedAnnotationTypes = metadata - ? metadata.supportedAnnotationTypes.map( - type => ANNOTATION_TYPES_METADATA[type], - ) - : []; - const supportedSourceTypes = this.getSupportedSourceTypes(annotationType); - - return ( - <> - {this.props.error && ( - - ERROR: {this.props.error} - - )} -
-
- - this.setState({ name: v })} - validationErrors={!name ? [t('Mandatory')] : []} - /> - this.setState({ show: !v })} - /> - this.setState({ showLabel: v })} - /> - - {supportedSourceTypes.length > 0 && ( - } - value={sourceType} - onChange={this.handleAnnotationSourceType} - validationErrors={!sourceType ? [t('Mandatory')] : []} - /> - )} - {this.renderValueConfiguration()} - -
- {this.renderSliceConfiguration()} - {this.renderDisplayConfiguration()} -
-
- {isNew ? ( - - ) : ( - - )} -
- - - -
-
- - ); - } -} - -AnnotationLayer.propTypes = propTypes; -AnnotationLayer.defaultProps = defaultProps; - -export default withTheme(AnnotationLayer); diff --git a/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.test.tsx b/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.test.tsx deleted file mode 100644 index ed8ae44339b2e..0000000000000 --- a/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/AnnotationLayer.test.tsx +++ /dev/null @@ -1,240 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; -import { render, screen, waitFor } from 'spec/helpers/testing-library'; -import userEvent from '@testing-library/user-event'; -import { getChartMetadataRegistry, ChartMetadata } from '@superset-ui/core'; -import fetchMock from 'fetch-mock'; -import setupColors from 'src/setup/setupColors'; -import { ANNOTATION_TYPES_METADATA } from 'src/modules/AnnotationTypes'; -import AnnotationLayer from './AnnotationLayer'; - -const defaultProps = { - value: '', - vizType: 'table', - annotationType: ANNOTATION_TYPES_METADATA.FORMULA.value, -}; - -beforeAll(() => { - const supportedAnnotationTypes = Object.values(ANNOTATION_TYPES_METADATA).map( - value => value.value, - ); - - fetchMock.get('glob:*/annotationlayermodelview/api/read?*', { - result: [{ label: 'Chart A', value: 'a' }], - }); - - fetchMock.get('glob:*/superset/user_slices*', [ - { id: 'a', title: 'Chart A', viz_type: 'table', data: {} }, - ]); - - setupColors(); - - getChartMetadataRegistry().registerValue( - 'table', - new ChartMetadata({ - name: 'Table', - thumbnail: '', - supportedAnnotationTypes, - canBeAnnotationTypes: ['EVENT'], - }), - ); -}); - -const waitForRender = (props?: any) => - waitFor(() => render()); - -test('renders with default props', async () => { - await waitForRender(); - expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'OK' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'Cancel' })).toBeEnabled(); -}); - -test('renders extra checkboxes when type is time series', async () => { - await waitForRender(); - expect( - screen.queryByRole('button', { name: 'Show Markers' }), - ).not.toBeInTheDocument(); - expect( - screen.queryByRole('button', { name: 'Hide Line' }), - ).not.toBeInTheDocument(); - userEvent.click(screen.getAllByText('Formula')[0]); - userEvent.click(screen.getByText('Time series')); - expect( - screen.getByRole('button', { name: 'Show Markers' }), - ).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Hide Line' })).toBeInTheDocument(); -}); - -test('enables apply and ok buttons', async () => { - const { container } = render(); - - waitFor(() => { - expect(container).toBeInTheDocument(); - }); - - const nameInput = screen.getByRole('textbox', { name: 'Name' }); - const formulaInput = screen.getByRole('textbox', { name: 'Formula' }); - - expect(nameInput).toBeInTheDocument(); - expect(formulaInput).toBeInTheDocument(); - - userEvent.type(nameInput, 'Name'); - userEvent.type(formulaInput, '2x'); - - waitFor(() => { - expect(screen.getByRole('button', { name: 'Apply' })).toBeEnabled(); - expect(screen.getByRole('button', { name: 'OK' })).toBeEnabled(); - }); -}); - -test('triggers addAnnotationLayer when apply button is clicked', async () => { - const addAnnotationLayer = jest.fn(); - await waitForRender({ name: 'Test', value: '2x', addAnnotationLayer }); - userEvent.click(screen.getByRole('button', { name: 'Apply' })); - expect(addAnnotationLayer).toHaveBeenCalled(); -}); - -test('triggers addAnnotationLayer and close when ok button is clicked', async () => { - const addAnnotationLayer = jest.fn(); - const close = jest.fn(); - await waitForRender({ name: 'Test', value: '2x', addAnnotationLayer, close }); - userEvent.click(screen.getByRole('button', { name: 'OK' })); - expect(addAnnotationLayer).toHaveBeenCalled(); - expect(close).toHaveBeenCalled(); -}); - -test('triggers close when cancel button is clicked', async () => { - const close = jest.fn(); - await waitForRender({ close }); - userEvent.click(screen.getByRole('button', { name: 'Cancel' })); - expect(close).toHaveBeenCalled(); -}); - -test('triggers removeAnnotationLayer and close when remove button is clicked', async () => { - const removeAnnotationLayer = jest.fn(); - const close = jest.fn(); - await waitForRender({ - name: 'Test', - value: '2x', - removeAnnotationLayer, - close, - }); - userEvent.click(screen.getByRole('button', { name: 'Remove' })); - expect(removeAnnotationLayer).toHaveBeenCalled(); - expect(close).toHaveBeenCalled(); -}); - -test('renders chart options', async () => { - await waitForRender({ - annotationType: ANNOTATION_TYPES_METADATA.EVENT.value, - }); - userEvent.click( - screen.getByRole('combobox', { name: 'Annotation source type' }), - ); - userEvent.click(screen.getByText('Superset annotation')); - expect(screen.getByText('Annotation layer')).toBeInTheDocument(); - - userEvent.click( - screen.getByRole('combobox', { name: 'Annotation source type' }), - ); - userEvent.click(screen.getByText('Table')); - expect(screen.getByText('Chart')).toBeInTheDocument(); -}); - -test('keeps apply disabled when missing required fields', async () => { - await waitForRender({ - annotationType: ANNOTATION_TYPES_METADATA.EVENT.value, - sourceType: 'Table', - }); - userEvent.click( - screen.getByRole('combobox', { name: 'Annotation layer value' }), - ); - userEvent.click(await screen.findByText('Chart A')); - expect( - screen.getByText('Annotation Slice Configuration'), - ).toBeInTheDocument(); - - userEvent.click(screen.getByRole('button', { name: 'Automatic Color' })); - userEvent.click( - screen.getByRole('combobox', { name: 'Annotation layer title column' }), - ); - userEvent.click(screen.getByText('None')); - userEvent.click(screen.getByText('Style')); - userEvent.click( - screen.getByRole('combobox', { name: 'Annotation layer stroke' }), - ); - userEvent.click(screen.getByText('Dashed')); - userEvent.click(screen.getByText('Opacity')); - userEvent.click( - screen.getByRole('combobox', { name: 'Annotation layer opacity' }), - ); - userEvent.click(screen.getByText('0.5')); - - const checkboxes = screen.getAllByRole('checkbox'); - checkboxes.forEach(checkbox => userEvent.click(checkbox)); - - expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled(); -}); - -test.skip('Disable apply button if formula is incorrect', async () => { - // TODO: fix flaky test that passes locally but fails on CI - await waitForRender({ name: 'test' }); - - userEvent.clear(screen.getByLabelText('Formula')); - userEvent.type(screen.getByLabelText('Formula'), 'x+1'); - await waitFor(() => { - expect(screen.getByLabelText('Formula')).toHaveValue('x+1'); - expect(screen.getByRole('button', { name: 'OK' })).toBeEnabled(); - expect(screen.getByRole('button', { name: 'Apply' })).toBeEnabled(); - }); - - userEvent.clear(screen.getByLabelText('Formula')); - userEvent.type(screen.getByLabelText('Formula'), 'y = x*2+1'); - await waitFor(() => { - expect(screen.getByLabelText('Formula')).toHaveValue('y = x*2+1'); - expect(screen.getByRole('button', { name: 'OK' })).toBeEnabled(); - expect(screen.getByRole('button', { name: 'Apply' })).toBeEnabled(); - }); - - userEvent.clear(screen.getByLabelText('Formula')); - userEvent.type(screen.getByLabelText('Formula'), 'y+1'); - await waitFor(() => { - expect(screen.getByLabelText('Formula')).toHaveValue('y+1'); - expect(screen.getByRole('button', { name: 'OK' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled(); - }); - - userEvent.clear(screen.getByLabelText('Formula')); - userEvent.type(screen.getByLabelText('Formula'), 'x+'); - await waitFor(() => { - expect(screen.getByLabelText('Formula')).toHaveValue('x+'); - expect(screen.getByRole('button', { name: 'OK' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled(); - }); - - userEvent.clear(screen.getByLabelText('Formula')); - userEvent.type(screen.getByLabelText('Formula'), 'y = z+1'); - await waitFor(() => { - expect(screen.getByLabelText('Formula')).toHaveValue('y = z+1'); - expect(screen.getByRole('button', { name: 'OK' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled(); - }); -}); diff --git a/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/index.jsx b/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/index.jsx deleted file mode 100644 index f1381abee1aa4..0000000000000 --- a/superset-frontend/src/explore/components/controls/DrillToDashboardLinkConfig/index.jsx +++ /dev/null @@ -1,258 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { List } from 'src/components'; -import { connect } from 'react-redux'; -import { t, withTheme } from '@superset-ui/core'; -import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls'; -import AsyncEsmComponent from 'src/components/AsyncEsmComponent'; -import { getChartKey } from 'src/explore/exploreUtils'; -import { runAnnotationQuery } from 'src/components/Chart/chartAction'; -import CustomListItem from 'src/explore/components/controls/CustomListItem'; -import ControlPopover from '../ControlPopover/ControlPopover'; - -const AnnotationLayer = AsyncEsmComponent( - () => import('./AnnotationLayer'), - // size of overlay inner content - () =>
, -); - -const propTypes = { - colorScheme: PropTypes.string.isRequired, - annotationError: PropTypes.object, - annotationQuery: PropTypes.object, - vizType: PropTypes.string, - - validationErrors: PropTypes.array, - name: PropTypes.string.isRequired, - actions: PropTypes.object, - value: PropTypes.arrayOf(PropTypes.object), - onChange: PropTypes.func, - refreshAnnotationData: PropTypes.func, -}; - -const defaultProps = { - vizType: '', - value: [], - annotationError: {}, - annotationQuery: {}, - onChange: () => {}, -}; - -class AnnotationLayerControl extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - popoverVisible: {}, - addedAnnotationIndex: null, - }; - this.addAnnotationLayer = this.addAnnotationLayer.bind(this); - this.removeAnnotationLayer = this.removeAnnotationLayer.bind(this); - this.handleVisibleChange = this.handleVisibleChange.bind(this); - } - - componentDidMount() { - // preload the AnotationLayer component and dependent libraries i.e. mathjs - AnnotationLayer.preload(); - } - - UNSAFE_componentWillReceiveProps(nextProps) { - const { name, annotationError, validationErrors, value } = nextProps; - if (Object.keys(annotationError).length && !validationErrors.length) { - this.props.actions.setControlValue( - name, - value, - Object.keys(annotationError), - ); - } - if (!Object.keys(annotationError).length && validationErrors.length) { - this.props.actions.setControlValue(name, value, []); - } - } - - addAnnotationLayer(originalAnnotation, newAnnotation) { - let annotations = this.props.value; - if (annotations.includes(originalAnnotation)) { - annotations = annotations.map(anno => - anno === originalAnnotation ? newAnnotation : anno, - ); - } else { - annotations = [...annotations, newAnnotation]; - this.setState({ addedAnnotationIndex: annotations.length - 1 }); - } - - this.props.refreshAnnotationData({ - annotation: newAnnotation, - force: true, - }); - - this.props.onChange(annotations); - } - - handleVisibleChange(visible, popoverKey) { - this.setState(prevState => ({ - popoverVisible: { ...prevState.popoverVisible, [popoverKey]: visible }, - })); - } - - removeAnnotationLayer(annotation) { - const annotations = this.props.value.filter(anno => anno !== annotation); - this.props.onChange(annotations); - } - - renderPopover(popoverKey, annotation, error) { - const id = annotation?.name || '_new'; - - return ( -
- - this.addAnnotationLayer(annotation, newAnnotation) - } - removeAnnotationLayer={() => this.removeAnnotationLayer(annotation)} - close={() => { - this.handleVisibleChange(false, popoverKey); - this.setState({ addedAnnotationIndex: null }); - }} - /> -
- ); - } - - renderInfo(anno) { - const { annotationError, annotationQuery, theme } = this.props; - if (annotationQuery[anno.name]) { - return ( - - ); - } - if (annotationError[anno.name]) { - return ( - - ); - } - if (!anno.show) { - return Hidden ; - } - return ''; - } - - render() { - const { addedAnnotationIndex } = this.state; - const addedAnnotation = this.props.value[addedAnnotationIndex]; - - const annotations = this.props.value.map((anno, i) => ( - ({ - '&:hover': { - cursor: 'pointer', - backgroundColor: theme.colors.grayscale.light4, - }, - })} - content={this.renderPopover( - i, - anno, - this.props.annotationError[anno.name], - )} - visible={this.state.popoverVisible[i]} - onVisibleChange={visible => this.handleVisibleChange(visible, i)} - > - - {anno.name} - {this.renderInfo(anno)} - - - )); - - const addLayerPopoverKey = 'add'; - return ( -
- ({ borderRadius: theme.gridUnit })}> - {annotations} - - this.handleVisibleChange(visible, addLayerPopoverKey) - } - > - - {' '} -   {t('Add annotation layer')} - - - -
- ); - } -} - -AnnotationLayerControl.propTypes = propTypes; -AnnotationLayerControl.defaultProps = defaultProps; - -// 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 }) { - const chartKey = getChartKey(explore); - const chart = charts[chartKey] || charts[0] || {}; - - return { - // eslint-disable-next-line camelcase - colorScheme: explore.controls?.color_scheme?.value, - annotationError: chart.annotationError, - annotationQuery: chart.annotationQuery, - vizType: explore.controls.viz_type.value, - }; -} - -function mapDispatchToProps(dispatch) { - return { - refreshAnnotationData: annotationObj => - dispatch(runAnnotationQuery(annotationObj)), - }; -} - -const themedAnnotationLayerControl = withTheme(AnnotationLayerControl); - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(themedAnnotationLayerControl); From 8cafe2d75b00655cacb82d38a5f23912a1187c91 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 1 Nov 2022 09:38:59 -0400 Subject: [PATCH 11/31] [CLDN-1749] adding temp base image for docker deployment --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 336d2e43b2c87..6ed31bef6f981 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:cccs-2.0_20221005132212_b5040 +FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221101132159_b5252 USER root From 72157a39bf38c1235345b306376347935f2b43cb Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 1 Nov 2022 10:56:00 -0400 Subject: [PATCH 12/31] [CLDN-1749] Better error handling and new icon --- .../src/images/thumbnail.png | Bin 5658 -> 4545 bytes .../src/plugin/controlPanel.ts | 58 ++++++++++-------- .../src/plugin/transformProps.ts | 6 +- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png index 7afef30bd4e6e4f85723208bf9f429647d03d3e5..92214739762aa56018c2eca5bccc87133ba811fc 100644 GIT binary patch literal 4545 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 literal 5658 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 diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts index dda69f9c1ec07..dc5c9e198483d 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts @@ -115,31 +115,6 @@ const config: ControlPanelConfig = { label: t('Query'), expanded: true, controlSetRows: [ - ['adhoc_filters'], - [ - { - name: 'metrics', - override: { - // visibility: () => true, - validators: [], - mapStateToProps: ( - state: ControlPanelState, - controlState: ControlState, - ) => { - const { controls } = state; - const originalMapStateToProps = - sharedControls?.metrics?.mapStateToProps; - const newState = - originalMapStateToProps?.(state, controlState) ?? {}; - newState.externalValidationErrors = validateAggControlValues( - controls, - [controlState.value], - ); - return newState; - }, - }, - }, - ], [ { name: 'row_limit', @@ -154,6 +129,17 @@ const config: ControlPanelConfig = { config: { type: 'TextControl', label: t('URL'), + mapStateToProps: ( + state: ControlPanelState, + controlState: ControlState, + ) => { + const originalMapStateToProps = + sharedControls?.groupby?.mapStateToProps; + const newState = + originalMapStateToProps?.(state, controlState) ?? {}; + newState.externalValidationErrors = controlState.value ? [] : ["Please add a value for URL."] + return newState; + }, renderTrigger: true, default: '', description: t('The Base URL for the Iframe.'), @@ -166,6 +152,17 @@ const config: ControlPanelConfig = { config: { type: 'TextControl', label: t('Parameter Column Name'), + mapStateToProps: ( + state: ControlPanelState, + controlState: ControlState, + ) => { + const originalMapStateToProps = + sharedControls?.groupby?.mapStateToProps; + const newState = + originalMapStateToProps?.(state, controlState) ?? {}; + newState.externalValidationErrors = controlState.value ? [] : ["Please add a value for Parameter Column Name."] + return newState; + }, renderTrigger: true, default: '', description: t('The Column name for the value that will populate the url parameter.'), @@ -178,6 +175,17 @@ const config: ControlPanelConfig = { config: { type: 'TextControl', label: t('Parameter Name'), + mapStateToProps: ( + state: ControlPanelState, + controlState: ControlState, + ) => { + const originalMapStateToProps = + sharedControls?.groupby?.mapStateToProps; + const newState = + originalMapStateToProps?.(state, controlState) ?? {}; + newState.externalValidationErrors = controlState.value ? [] : ["Please add a value for Parameter Name."] + return newState; + }, renderTrigger: true, default: '', description: t('The name for the URL parameter.'), diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index 5590634e664e9..15aa7fefeb9e8 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -75,9 +75,9 @@ export default function transformProps(chartProps: ChartProps) { const allFilters = extractFiltersFromFormData(formData); - const url_parameter_raw_value = String(allFilters.find( e => { + const url_parameter_raw_value = allFilters.find( e => { return e.columnName == parameterColumnName - })?.value); + })?.value; let errorMessage = ""; @@ -93,7 +93,7 @@ export default function transformProps(chartProps: ChartProps) { errorMessage = "No value received, please emit a single value." } - const url_parameter_value = url_parameter_raw_value.toString() + const url_parameter_value = String(url_parameter_raw_value) return { url_parameter_value, From ff47239c01e40dae68340bfc237e3d0dbb619cec Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 1 Nov 2022 11:44:25 -0400 Subject: [PATCH 13/31] [CLDN-1749] remove unused imports --- .../plugin-chart-iframe/src/plugin/controlPanel.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts index dc5c9e198483d..b9d9324a81573 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts @@ -16,23 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { ensureIsArray, t, validateNonEmpty } from '@superset-ui/core'; +import { t, validateNonEmpty } from '@superset-ui/core'; import { ControlPanelConfig, ControlPanelState, ControlState, - ControlStateMapping, sharedControls, } from '@superset-ui/chart-controls'; -const validateAggControlValues = ( - controls: ControlStateMapping, - values: any[], -) => { - const areControlsEmpty = values.every(val => ensureIsArray(val).length === 0); - // @ts-ignore - return areControlsEmpty ? [t('Metrics must have a value')] : []; -}; const config: ControlPanelConfig = { /** From 734a521c3a73b7a9adaa392b3c419f8f15fd3ed3 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 1 Nov 2022 12:34:28 -0400 Subject: [PATCH 14/31] temp dockerfile change --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 94fe074fd06af..ee3218e704870 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221101132159_b5252 +FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221101154707_b5260 From 12371dabe4f6b16af9caa173075c331ad5690c76 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 08:04:33 -0500 Subject: [PATCH 15/31] [CLDN-1749] Adding the adhoc filters back in --- .../src/plugin/controlPanel.ts | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts index b9d9324a81573..dd8513f57633d 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts @@ -16,16 +16,27 @@ * specific language governing permissions and limitations * under the License. */ -import { t, validateNonEmpty } from '@superset-ui/core'; +import { ensureIsArray, t, validateNonEmpty } from '@superset-ui/core'; import { ControlPanelConfig, ControlPanelState, ControlState, + ControlStateMapping, sharedControls, } from '@superset-ui/chart-controls'; +const validateAggControlValues = ( + controls: ControlStateMapping, + values: any[], +) => { + const areControlsEmpty = values.every(val => ensureIsArray(val).length === 0); + // @ts-ignore + return areControlsEmpty ? [t('Metrics must have a value')] : []; +}; + const config: ControlPanelConfig = { + /** * The control panel is split into two tabs: "Query" and * "Chart Options". The controls that define the inputs to @@ -99,13 +110,38 @@ const config: ControlPanelConfig = { * - validateInteger: must be an integer value * - validateNumber: must be an intger or decimal value */ - + // For control input types, see: superset-frontend/src/explore/components/controls/index.js controlPanelSections: [ { label: t('Query'), expanded: true, controlSetRows: [ + ['adhoc_filters'], + [ + { + name: 'metrics', + override: { + // visibility: () => true, + validators: [], + mapStateToProps: ( + state: ControlPanelState, + controlState: ControlState, + ) => { + const { controls } = state; + const originalMapStateToProps = + sharedControls?.metrics?.mapStateToProps; + const newState = + originalMapStateToProps?.(state, controlState) ?? {}; + newState.externalValidationErrors = validateAggControlValues( + controls, + [controlState.value], + ); + return newState; + }, + }, + }, + ], [ { name: 'row_limit', From 2f136526b40d596255924f409a3ad6b25fa5b6c5 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 08:59:12 -0500 Subject: [PATCH 16/31] [CLDN-1749] Updating image --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index ee3218e704870..04b412bed79db 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221101154707_b5260 +FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221108131049_b5344 From 265268af171c388a4ab4b2c291025b1ef80b3ac6 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 09:32:42 -0500 Subject: [PATCH 17/31] [CLDN-1749] Adding new thumbnail --- .../src/images/thumbnail.png | Bin 4545 -> 1547 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/images/thumbnail.png index 92214739762aa56018c2eca5bccc87133ba811fc..b3c8eb2228ba700df9fdf3ddeab66af86d572d0f 100644 GIT binary patch literal 1547 zcmZvcc~BAv6vjzSQo$=MMZ>EM%q_2gOfymw4-7m;H-!>4TNCpbk94dS98zRe60OXG zP&XFS%rztN#;{8<^CViuEN`W?LmfAko!S0t=6mmZ^UeGB`{m&SyusQ~Z2$lO_Q84v zs$Blx>`+&2Es7OZW$}3bU@xUosX8SRNpf=Xr%#_sOG{-kSy@?GMMXtpW8=ogMn*

93pM>{$?P$-n6 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 From d186cdac8a84e3c8aec49a91dfa38126ee18a0d4 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 10:02:04 -0500 Subject: [PATCH 18/31] Updating dockerfile --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 04b412bed79db..1fb75991fd708 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221108131049_b5344 +FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221108143342_b5350 From 0d40c5e6d26697c1863f2e5e3ebc849ee77d370a Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 12:19:41 -0500 Subject: [PATCH 19/31] [CLDN-1749] Removing uneeded control panel elements --- .../src/plugin/buildQuery.ts | 58 +++++++++++++++++++ .../src/plugin/controlPanel.ts | 32 ---------- .../plugin-chart-iframe/src/plugin/index.ts | 2 + 3 files changed, 60 insertions(+), 32 deletions(-) create mode 100644 superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/buildQuery.ts diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/buildQuery.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/buildQuery.ts new file mode 100644 index 0000000000000..f0eade2b4e34e --- /dev/null +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/buildQuery.ts @@ -0,0 +1,58 @@ +/** + * 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, QueryFormData } from '@superset-ui/core'; + + /** + * 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 default function buildQuery(formData: QueryFormData) { + /* + We receive an ip as a filter, our job is to find everthing there is to know about that ip + We fire multiple queries to multiple data sets and collect the results here. + */ + + const formDataCopy = { + ...formData, + result_type: 'post_processed', + }; + + return buildQueryContext(formDataCopy, baseQueryObject => { + // RAW mode (not aggregated) + // eslint-disable-next-line no-param-reassign + baseQueryObject.metrics = ["count"]; + + return [ + { + ...baseQueryObject, + }, + ]; + }); + } + \ No newline at end of file diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts index dd8513f57633d..0e48a381141fb 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts @@ -118,38 +118,6 @@ const config: ControlPanelConfig = { expanded: true, controlSetRows: [ ['adhoc_filters'], - [ - { - name: 'metrics', - override: { - // visibility: () => true, - validators: [], - mapStateToProps: ( - state: ControlPanelState, - controlState: ControlState, - ) => { - const { controls } = state; - const originalMapStateToProps = - sharedControls?.metrics?.mapStateToProps; - const newState = - originalMapStateToProps?.(state, controlState) ?? {}; - newState.externalValidationErrors = validateAggControlValues( - controls, - [controlState.value], - ); - return newState; - }, - }, - }, - ], - [ - { - name: 'row_limit', - override: { - default: 1, - }, - }, - ], [ { name: 'url', diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/index.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/index.ts index 42d621626fac2..0efe549552fcb 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/index.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/index.ts @@ -19,6 +19,7 @@ import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core'; import controlPanel from './controlPanel'; import transformProps from './transformProps'; +import buildQuery from './buildQuery'; import thumbnail from '../images/thumbnail.png'; export default class IFrameVisualizationChartPlugin extends ChartPlugin { @@ -40,6 +41,7 @@ export default class IFrameVisualizationChartPlugin extends ChartPlugin { }); super({ + buildQuery, controlPanel, loadChart: () => import('../IFrameVisualization'), metadata, From 29753ce51b9a45c30d6f29f723f01517d4760dae Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 12:38:00 -0500 Subject: [PATCH 20/31] [CLDN-1749] Fixing build errors --- .../plugin-chart-iframe/src/plugin/controlPanel.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts index 0e48a381141fb..a7f86d64d41e7 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts @@ -16,25 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { ensureIsArray, t, validateNonEmpty } from '@superset-ui/core'; +import { t, validateNonEmpty } from '@superset-ui/core'; import { ControlPanelConfig, ControlPanelState, ControlState, - ControlStateMapping, sharedControls, } from '@superset-ui/chart-controls'; - -const validateAggControlValues = ( - controls: ControlStateMapping, - values: any[], -) => { - const areControlsEmpty = values.every(val => ensureIsArray(val).length === 0); - // @ts-ignore - return areControlsEmpty ? [t('Metrics must have a value')] : []; -}; - const config: ControlPanelConfig = { /** From 7c212c9d6ae69e5658d317b225b10c34460118ec Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 12:51:53 -0500 Subject: [PATCH 21/31] [CLDN-1749] New image --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 1fb75991fd708..dfd70644e528a 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221108143342_b5350 +FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221108173907_b5357 From 747b8f4a2668dbc78656ea28570e0d3b4284efc1 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 14:34:09 -0500 Subject: [PATCH 22/31] Change to use data from dataset --- .../src/plugin/buildQuery.ts | 3 +- .../src/plugin/controlPanel.ts | 19 ++++++++----- .../src/plugin/transformProps.ts | 28 +++++++++---------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/buildQuery.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/buildQuery.ts index f0eade2b4e34e..22405e6d1202f 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/buildQuery.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/buildQuery.ts @@ -46,11 +46,10 @@ return buildQueryContext(formDataCopy, baseQueryObject => { // RAW mode (not aggregated) // eslint-disable-next-line no-param-reassign - baseQueryObject.metrics = ["count"]; - return [ { ...baseQueryObject, + row_limit: 10, }, ]; }); diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts index a7f86d64d41e7..8aacb844ea08c 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { t, validateNonEmpty } from '@superset-ui/core'; +import { ensureIsArray, t, validateNonEmpty } from '@superset-ui/core'; import { ControlPanelConfig, ControlPanelState, @@ -24,6 +24,8 @@ import { sharedControls, } from '@superset-ui/chart-controls'; +import { StyledColumnOption } from 'src/explore/components/optionRenderers'; + const config: ControlPanelConfig = { /** @@ -132,10 +134,16 @@ const config: ControlPanelConfig = { ], [ { - name: 'parameter_column_name', + name: 'groupby', config: { - type: 'TextControl', + type: 'SelectControl', label: t('Parameter Column Name'), + description: sharedControls.groupby.description, + multi: false, + allowAll: false, + default: [], + valueKey: 'column_name', + includeTime: false, mapStateToProps: ( state: ControlPanelState, controlState: ControlState, @@ -144,12 +152,9 @@ const config: ControlPanelConfig = { sharedControls?.groupby?.mapStateToProps; const newState = originalMapStateToProps?.(state, controlState) ?? {}; - newState.externalValidationErrors = controlState.value ? [] : ["Please add a value for Parameter Column Name."] + newState.externalValidationErrors = ensureIsArray(controlState.value).length > 0 ? [] : ["Please add a value for Parameter Column Name."] return newState; }, - renderTrigger: true, - default: '', - description: t('The Column name for the value that will populate the url parameter.'), }, }, ], diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index 15aa7fefeb9e8..71bfddc20e814 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { ChartProps, PlainObject, } from '@superset-ui/core'; +import { ChartProps, PlainObject, TimeseriesDataRecord, } from '@superset-ui/core'; const extractFiltersFromFormData = (formData: PlainObject): ({columnName: string, value: string | number | boolean | (string | number | boolean)[] })[] => { @@ -70,33 +70,33 @@ export default function transformProps(chartProps: ChartProps) { * be seen until restarting the development server. */ const formData = chartProps.formData; + const queriesData = chartProps.queriesData; + - const { url, parameterColumnName, parameterName, parameterPrefix } = formData - const allFilters = extractFiltersFromFormData(formData); - - const url_parameter_raw_value = allFilters.find( e => { - return e.columnName == parameterColumnName - })?.value; - + const { url, parameterName, parameterPrefix, groupby } = formData + + const data = queriesData[0]?.data as TimeseriesDataRecord[]; + + let value: string | number | true | Date = "" let errorMessage = ""; - if(Array.isArray(url_parameter_raw_value) && url_parameter_raw_value.length > 1) { + if(Array.isArray(data) && data.length > 1) { errorMessage = "More than one value received, please emit a single value." } - if(Array.isArray(url_parameter_raw_value) && url_parameter_raw_value.length === 0) { + if(Array.isArray(data) && data.length === 0) { errorMessage = "No value received, please emit a single value." } - if(url_parameter_raw_value === undefined || url_parameter_raw_value === "undefined") { - errorMessage = "No value received, please emit a single value." + if(Array.isArray(data) && data.length === 1) { + value = data[0][groupby] || "" } - const url_parameter_value = String(url_parameter_raw_value) + return { - url_parameter_value, + url_parameter_value: value, parameter_name: parameterName, url, parameter_prefix: parameterPrefix, From 5fea9975dceed9c35e2e56cd53beb534a0e7e8ed Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 14:35:23 -0500 Subject: [PATCH 23/31] Removing unsed function --- .../src/plugin/transformProps.ts | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index 71bfddc20e814..f2f8c955f0b55 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -16,28 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { ChartProps, PlainObject, TimeseriesDataRecord, } from '@superset-ui/core'; - - -const extractFiltersFromFormData = (formData: PlainObject): ({columnName: string, value: string | number | boolean | (string | number | boolean)[] })[] => { - - const filters = [...(formData?.adhocFilters || []), ...(formData?.extraFormData?.adhocFilters || []), ...(formData?.extraFormData?.filters || [])] - - const simpleAdhocFilters = filters.reduce( - (acc, filter) => { - - if ( ("subject" in filter) && ("comparator" in filter) ) { - acc.push( {columnName: filter.subject, value: filter.comparator} ) - } - else if ( ("col" in filter) && ("val" in filter) ) { - acc.push( {columnName: filter.col.toString(), value: filter.val} ) - } - return acc - }, <({columnName: string, value: string | number | boolean | (string | number | boolean)[]})[]> [] - ) - - return simpleAdhocFilters -} +import { ChartProps, TimeseriesDataRecord, } from '@superset-ui/core'; export default function transformProps(chartProps: ChartProps) { /** From dc6f914a56bfc6ce06327f89216f8cabe558513a Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 14:37:34 -0500 Subject: [PATCH 24/31] Fixing typos --- .../plugins/plugin-chart-iframe/src/plugin/controlPanel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts index 8aacb844ea08c..176fdfeb9cb1a 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts @@ -138,7 +138,7 @@ const config: ControlPanelConfig = { config: { type: 'SelectControl', label: t('Parameter Column Name'), - description: sharedControls.groupby.description, + description: "The name of the column that will populate the url parameter value.", multi: false, allowAll: false, default: [], @@ -189,7 +189,7 @@ const config: ControlPanelConfig = { label: t('Parameter Prefix'), renderTrigger: true, default: '', - description: t('A value that will be predened the parameter value.'), + description: t('A value that will be prefix the parameter value.'), }, }, ] From 833e0da6019466023601f76b7b8ac16f7924a6b1 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 14:49:16 -0500 Subject: [PATCH 25/31] renove unused import --- .../plugins/plugin-chart-iframe/src/plugin/controlPanel.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts index 176fdfeb9cb1a..6298ba8b24170 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts @@ -24,8 +24,6 @@ import { sharedControls, } from '@superset-ui/chart-controls'; -import { StyledColumnOption } from 'src/explore/components/optionRenderers'; - const config: ControlPanelConfig = { /** From cfc151f32efb78efa94fd7b766551aaeba8de340 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 8 Nov 2022 15:02:46 -0500 Subject: [PATCH 26/31] updating image --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index dfd70644e528a..896f0802edc32 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221108173907_b5357 +FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221108195038_b5370 From 6418431572b0338e51c7bc8bedbc4ca1470b37c7 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Mon, 14 Nov 2022 14:08:08 -0500 Subject: [PATCH 27/31] error message and label fixes --- .../plugins/plugin-chart-iframe/src/plugin/controlPanel.ts | 6 +----- .../plugin-chart-iframe/src/plugin/transformProps.ts | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts index 6298ba8b24170..5c7a9df66fbce 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/controlPanel.ts @@ -133,14 +133,12 @@ const config: ControlPanelConfig = { [ { name: 'groupby', - config: { - type: 'SelectControl', + override: { label: t('Parameter Column Name'), description: "The name of the column that will populate the url parameter value.", multi: false, allowAll: false, default: [], - valueKey: 'column_name', includeTime: false, mapStateToProps: ( state: ControlPanelState, @@ -173,7 +171,6 @@ const config: ControlPanelConfig = { newState.externalValidationErrors = controlState.value ? [] : ["Please add a value for Parameter Name."] return newState; }, - renderTrigger: true, default: '', description: t('The name for the URL parameter.'), }, @@ -185,7 +182,6 @@ const config: ControlPanelConfig = { config: { type: 'TextControl', label: t('Parameter Prefix'), - renderTrigger: true, default: '', description: t('A value that will be prefix the parameter value.'), }, diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index f2f8c955f0b55..e851ad5d7ad0a 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -61,11 +61,11 @@ export default function transformProps(chartProps: ChartProps) { let errorMessage = ""; if(Array.isArray(data) && data.length > 1) { - errorMessage = "More than one value received, please emit a single value." + errorMessage = "The query returned too many rows when only one was expected." } if(Array.isArray(data) && data.length === 0) { - errorMessage = "No value received, please emit a single value." + errorMessage = "The query returned no rows." } if(Array.isArray(data) && data.length === 1) { From 23d1b54116be28694944e79c3959d0d4de13e928 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Mon, 14 Nov 2022 15:42:04 -0500 Subject: [PATCH 28/31] updating image --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 896f0802edc32..ad8ff8deeb9c9 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221108195038_b5370 +FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221114192347_b5421 From 81b4f4177baaca79eb56851e2ca53614a8d01c45 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:46:25 -0500 Subject: [PATCH 29/31] Update cccs-build/superset/Dockerfile Co-authored-by: cccs-Dustin <96579982+cccs-Dustin@users.noreply.github.com> --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index ad8ff8deeb9c9..6abb7e38ea3dd 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:feature_CLDN-1749_20221114192347_b5421 +FROM uchimera.azurecr.io/cccs/superset-base:cccs-2.0_20221014182839_b5135 From 3c433c25798efbf34cbd209de1ec7925978ad93c Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Thu, 1 Dec 2022 08:17:00 -0500 Subject: [PATCH 30/31] Update superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts Co-authored-by: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> --- .../plugins/plugin-chart-iframe/src/plugin/transformProps.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index e851ad5d7ad0a..d655ab5c61d3a 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -50,8 +50,6 @@ export default function transformProps(chartProps: ChartProps) { */ const formData = chartProps.formData; const queriesData = chartProps.queriesData; - - const { url, parameterName, parameterPrefix, groupby } = formData From 31e83ac085adc4a8c453a4a5661f218f3c776693 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Thu, 1 Dec 2022 08:17:10 -0500 Subject: [PATCH 31/31] Update superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts Co-authored-by: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> --- .../plugins/plugin-chart-iframe/src/plugin/transformProps.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts index d655ab5c61d3a..ee67ef9d2aff7 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-iframe/src/plugin/transformProps.ts @@ -69,8 +69,6 @@ export default function transformProps(chartProps: ChartProps) { if(Array.isArray(data) && data.length === 1) { value = data[0][groupby] || "" } - - return { url_parameter_value: value,