Skip to content

Commit 1956c5f

Browse files
Period Over Period Plugin:
- Fix format and lint - Rewrite some functions to help with type checks - Use css when possible in the Plugin component
1 parent 68fe17f commit 1956c5f

File tree

8 files changed

+147
-121
lines changed

8 files changed

+147
-121
lines changed

superset-frontend/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,10 @@
109109
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
110110
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
111111
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
112+
"@superset-ui/plugin-chart-period-over-period-kpi": "file:./plugins/plugin-chart-period-over-period-kpi",
112113
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
113114
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
114115
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
115-
"@superset-ui/plugin-chart-period-over-period-kpi": "file:./plugins/plugin-chart-period-over-period-kpi",
116116
"@superset-ui/switchboard": "file:./packages/superset-ui-switchboard",
117117
"@types/d3-format": "^3.0.1",
118118
"@visx/axis": "^3.0.1",

superset-frontend/plugins/plugin-chart-period-over-period-kpi/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ npm run dev
1818
```
1919

2020
To add the package to Superset, go to the `superset-frontend` subdirectory in your Superset source folder (assuming both the `custom-viz` plugin and `superset` repos are in the same root directory) and run
21+
2122
```
2223
npm i -S ../../custom-viz
2324
```
@@ -54,6 +55,7 @@ import { CustomViz } from 'custom-viz';
5455
```
5556

5657
to import the plugin and later add the following to the array that's passed to the `plugins` property:
58+
5759
```js
5860
new CustomViz().configure({ key: 'custom-viz' }),
5961
```

superset-frontend/plugins/plugin-chart-period-over-period-kpi/src/PopKPI.tsx

+57-66
Original file line numberDiff line numberDiff line change
@@ -16,53 +16,18 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
import React, { useEffect, createRef } from 'react';
20-
import {
21-
styled,
22-
} from '@superset-ui/core';
23-
import { PopKPIProps, PopKPIStylesProps } from './types';
19+
import React, { createRef } from 'react';
20+
import { css, styled, useTheme } from '@superset-ui/core';
21+
import { PopKPIComparisonValueStyleProps, PopKPIProps } from './types';
2422

25-
// The following Styles component is a <div> element, which has been styled using Emotion
26-
// For docs, visit https://emotion.sh/docs/styled
27-
28-
// Theming variables are provided for your use via a ThemeProvider
29-
// imported from @superset-ui/core. For variables available, please visit
30-
// https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-core/src/style/index.ts
31-
32-
const Styles = styled.div<PopKPIStylesProps>`
33-
34-
font-family: ${({ theme }) => theme.typography.families.sansSerif};
35-
position: relative;
36-
display: flex;
37-
flex-direction: column;
38-
justify-content: center;
39-
padding: ${({ theme }) => theme.tdUnit * 4}px;
40-
border-radius: ${({ theme }) => theme.tdUnit * 2}px;
41-
height: ${({ height }) => height}px;
42-
width: ${({ width }) => width}px;
43-
`;
44-
45-
const BigValueContainer = styled.div`
46-
font-size: ${props=> props.headerFontSize ? props.headerFontSize : 60}px;
47-
font-weight: ${({ theme }) => theme.typography.weights.normal};
48-
text-align: center;
49-
`;
50-
51-
const TableContainer = styled.div`
52-
width: 100%;
53-
display: table;
54-
`
55-
56-
const ComparisonContainer = styled.div`
57-
display: table-row;
58-
`;
59-
60-
const ComparisonValue = styled.div`
61-
font-weight: ${({ theme }) => theme.typography.weights.light};
62-
width: 33%;
63-
display: table-cell;
64-
font-size: ${props=> props.subheaderFontSize ? props.subheaderFontSize : 20}px;
65-
text-align: center;
23+
const ComparisonValue = styled.div<PopKPIComparisonValueStyleProps>`
24+
${({ theme, subheaderFontSize }) => `
25+
font-weight: ${theme.typography.weights.light};
26+
width: 33%;
27+
display: table-cell;
28+
font-size: ${subheaderFontSize || 20}px;
29+
text-align: center;
30+
`}
6631
`;
6732

6833
export default function PopKPI(props: PopKPIProps) {
@@ -78,28 +43,54 @@ export default function PopKPI(props: PopKPIProps) {
7843
} = props;
7944

8045
const rootElem = createRef<HTMLDivElement>();
46+
const theme = useTheme();
8147

82-
useEffect(() => {
83-
const root = rootElem.current as HTMLElement;
84-
});
48+
const wrapperDivStyles = css`
49+
font-family: ${theme.typography.families.sansSerif};
50+
position: relative;
51+
display: flex;
52+
flex-direction: column;
53+
justify-content: center;
54+
padding: ${theme.gridUnit * 4}px;
55+
border-radius: ${theme.gridUnit * 2}px;
56+
height: ${height}px;
57+
width: ${width}px;
58+
`;
8559

86-
return (
87-
<Styles
88-
ref={rootElem}
89-
boldText={props.boldText}
90-
headerFontSize={props.headerFontSize}
91-
height={height}
92-
width={width}
93-
>
60+
const bigValueContainerStyles = css`
61+
font-size: ${headerFontSize || 60}px;
62+
font-weight: ${theme.typography.weights.normal};
63+
text-align: center;
64+
`;
9465

95-
<BigValueContainer headerFontSize={headerFontSize}>{bigNumber}</BigValueContainer>
96-
<TableContainer>
97-
<ComparisonContainer>
98-
<ComparisonValue subheaderFontSize={subheaderFontSize}> #: {prevNumber}</ComparisonValue>
99-
<ComparisonValue subheaderFontSize={subheaderFontSize}> Δ: {valueDifference}</ComparisonValue>
100-
<ComparisonValue subheaderFontSize={subheaderFontSize}> %: {percentDifference}</ComparisonValue>
101-
</ComparisonContainer>
102-
</TableContainer>
103-
</Styles>
66+
return (
67+
<div ref={rootElem} css={wrapperDivStyles}>
68+
<div css={bigValueContainerStyles}>{bigNumber}</div>
69+
<div
70+
css={css`
71+
width: 100%;
72+
display: table;
73+
`}
74+
>
75+
<div
76+
css={css`
77+
display: table-row;
78+
`}
79+
>
80+
<ComparisonValue subheaderFontSize={subheaderFontSize}>
81+
{' '}
82+
#: {prevNumber}
83+
</ComparisonValue>
84+
<ComparisonValue subheaderFontSize={subheaderFontSize}>
85+
{' '}
86+
Δ: {valueDifference}
87+
</ComparisonValue>
88+
<ComparisonValue subheaderFontSize={subheaderFontSize}>
89+
{' '}
90+
%: {percentDifference}
91+
</ComparisonValue>
92+
</div>
93+
</div>
94+
</div>
10495
);
10596
}

superset-frontend/plugins/plugin-chart-period-over-period-kpi/src/plugin/buildQuery.ts

+60-44
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
import { buildQueryContext, QueryFormData } from '@superset-ui/core';
19+
import {
20+
AdhocFilter,
21+
buildQueryContext,
22+
QueryFormData,
23+
} from '@superset-ui/core';
2024
import moment, { Moment } from 'moment';
2125

2226
/**
@@ -42,25 +46,25 @@ function getSinceUntil(
4246
relativeEnd: string | null = null,
4347
): MomentTuple {
4448
const separator = ' : ';
45-
const _relativeStart = relativeStart || "today";
46-
const _relativeEnd = relativeEnd || "today";
49+
const effectiveRelativeStart = relativeStart || 'today';
50+
const effectiveRelativeEnd = relativeEnd || 'today';
4751

4852
if (!timeRange) {
4953
return [null, null];
5054
}
5155

5256
let modTimeRange: string | null = timeRange;
5357

54-
if (timeRange === 'NO_TIME_RANGE' || timeRange === '_(NO_TIME_RANGE)'){
58+
if (timeRange === 'NO_TIME_RANGE' || timeRange === '_(NO_TIME_RANGE)') {
5559
return [null, null];
5660
}
5761

5862
if (timeRange?.startsWith('last') && !timeRange.includes(separator)) {
59-
modTimeRange = timeRange + separator + _relativeEnd;
63+
modTimeRange = timeRange + separator + effectiveRelativeEnd;
6064
}
6165

6266
if (timeRange?.startsWith('next') && !timeRange.includes(separator)) {
63-
modTimeRange = _relativeStart + separator + timeRange;
67+
modTimeRange = effectiveRelativeStart + separator + timeRange;
6468
}
6569

6670
if (
@@ -179,12 +183,15 @@ function getSinceUntil(
179183
return [_since, _until];
180184
}
181185

182-
function calculatePrev(startDate: Moment | null, endDate: Moment | null, calcType: String) {
183-
184-
if (!startDate || !endDate){
185-
return [null, null]
186+
function calculatePrev(
187+
startDate: Moment | null,
188+
endDate: Moment | null,
189+
calcType: String,
190+
) {
191+
if (!startDate || !endDate) {
192+
return [null, null];
186193
}
187-
194+
188195
const daysBetween = endDate.diff(startDate, 'days');
189196

190197
let startDatePrev = moment();
@@ -219,53 +226,63 @@ export default function buildQuery(formData: QueryFormData) {
219226
},
220227
]);
221228

229+
const timeFilterIndex: number =
230+
formData.adhoc_filters?.findIndex(
231+
filter => 'operator' in filter && filter.operator === 'TEMPORAL_RANGE',
232+
) ?? -1;
222233

223-
const timeFilter: any = formData.adhoc_filters?.find(
224-
({ operator }: { operator: string }) => operator === 'TEMPORAL_RANGE',
225-
);
234+
const timeFilter: AdhocFilter | null =
235+
timeFilterIndex !== -1 && formData.adhoc_filters
236+
? formData.adhoc_filters[timeFilterIndex]
237+
: null;
226238

227-
const timeFilterIndex: any = formData.adhoc_filters?.findIndex(
228-
({ operator }: { operator: string }) => operator === 'TEMPORAL_RANGE',
229-
);
239+
let testSince = null;
240+
let testUntil = null;
230241

231-
const [testSince, testUntil] = getSinceUntil(
232-
timeFilter.comparator.toLowerCase(),
233-
);
234-
235-
let formDataB: QueryFormData;
242+
if (
243+
timeFilter &&
244+
'comparator' in timeFilter &&
245+
typeof timeFilter.comparator === 'string'
246+
) {
247+
[testSince, testUntil] = getSinceUntil(
248+
timeFilter.comparator.toLocaleLowerCase(),
249+
);
250+
}
236251

237-
if (timeComparison!='c'){
252+
let formDataB: QueryFormData;
238253

239-
const [prevStartDateMoment, prevEndDateMoment] = calculatePrev(
240-
testSince,
241-
testUntil,
242-
timeComparison,
243-
);
254+
if (timeComparison !== 'c') {
255+
const [prevStartDateMoment, prevEndDateMoment] = calculatePrev(
256+
testSince,
257+
testUntil,
258+
timeComparison,
259+
);
244260

245261
const queryBComparator = `${prevStartDateMoment?.format(
246-
'YYYY-MM-DDTHH:mm:ss',
247-
)} : ${prevEndDateMoment?.format('YYYY-MM-DDTHH:mm:ss')}`;
262+
'YYYY-MM-DDTHH:mm:ss',
263+
)} : ${prevEndDateMoment?.format('YYYY-MM-DDTHH:mm:ss')}`;
248264

249-
const queryBFilter = {
265+
const queryBFilter: any = {
250266
...timeFilter,
251-
comparator: queryBComparator.replace(/Z/g, '')
252-
}
267+
comparator: queryBComparator.replace(/Z/g, ''),
268+
};
253269

254-
const otherFilters = formData.adhoc_filters?.filter((_value: any, index: number) => timeFilterIndex !== index);
255-
const queryBFilters = otherFilters ? [queryBFilter, ...otherFilters] : [queryBFilter];
256-
257-
formDataB= {
270+
const otherFilters = formData.adhoc_filters?.filter(
271+
(_value: any, index: number) => timeFilterIndex !== index,
272+
);
273+
const queryBFilters = otherFilters
274+
? [queryBFilter, ...otherFilters]
275+
: [queryBFilter];
276+
277+
formDataB = {
258278
...formData,
259279
adhoc_filters: queryBFilters,
260-
}
261-
280+
};
262281
} else {
263-
264-
formDataB= {
282+
formDataB = {
265283
...formData,
266284
adhoc_filters: formData.adhoc_custom,
267-
}
268-
285+
};
269286
}
270287

271288
const queryContextB = buildQueryContext(formDataB, baseQueryObject => [
@@ -275,7 +292,6 @@ export default function buildQuery(formData: QueryFormData) {
275292
},
276293
]);
277294

278-
279295
return {
280296
...queryContextA,
281297
queries: [...queryContextA.queries, ...queryContextB.queries],

superset-frontend/plugins/plugin-chart-period-over-period-kpi/src/plugin/transformProps.ts

+20-7
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,26 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19+
import moment from 'moment';
1920
import {
2021
ChartProps,
21-
TimeseriesDataRecord,
2222
getMetricLabel,
2323
getValueFormatter,
2424
NumberFormats,
2525
getNumberFormatter,
2626
} from '@superset-ui/core';
2727

28+
export const parseMetricValue = (metricValue: number | string | null) => {
29+
if (typeof metricValue === 'string') {
30+
const dateObject = moment.utc(metricValue, moment.ISO_8601, true);
31+
if (dateObject.isValid()) {
32+
return dateObject.valueOf();
33+
}
34+
return 0;
35+
}
36+
return metricValue ?? 0;
37+
};
38+
2839
export default function transformProps(chartProps: ChartProps) {
2940
/**
3041
* This function is called after a successful response has been
@@ -71,12 +82,14 @@ export default function transformProps(chartProps: ChartProps) {
7182
currencyFormat,
7283
subheaderFontSize,
7384
} = formData;
74-
const dataA = queriesData[0].data as TimeseriesDataRecord[];
75-
const dataB = queriesData[1].data as TimeseriesDataRecord[];
85+
const { data: dataA = [] } = queriesData[0];
86+
const { data: dataB = [] } = queriesData[1];
7687
const data = dataA;
7788
const metricName = getMetricLabel(metrics[0]);
78-
let bigNumber = data.length === 0 ? null : data[0][metricName];
79-
let prevNumber = dataB.length === 0 ? null : dataB[0][metricName];
89+
let bigNumber: number | string =
90+
data.length === 0 ? 0 : parseMetricValue(data[0][metricName]);
91+
let prevNumber: number | string =
92+
data.length === 0 ? 0 : parseMetricValue(dataB[0][metricName]);
8093

8194
const numberFormatter = getValueFormatter(
8295
metrics[0],
@@ -97,13 +110,13 @@ export default function transformProps(chartProps: ChartProps) {
97110
NumberFormats.PERCENT_SIGNED_1_POINT,
98111
);
99112

100-
let valueDifference = bigNumber - prevNumber;
113+
let valueDifference: number | string = bigNumber - prevNumber;
101114

102115
const percentDifferenceNum = prevNumber
103116
? (bigNumber - prevNumber) / Math.abs(prevNumber)
104117
: 0;
105118

106-
const compType= compTitles[formData.timeComparison]
119+
const compType = compTitles[formData.timeComparison];
107120
bigNumber = numberFormatter(bigNumber);
108121
prevNumber = numberFormatter(prevNumber);
109122
valueDifference = numberFormatter(valueDifference);

0 commit comments

Comments
 (0)