Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add new plugin to generate period over period KPI charts #847

Merged
merged 1 commit into from
Jan 30, 2024

Conversation

fernando-bytecode
Copy link

SUMMARY

Creating a new chart that allows arbitrary time range comparisons for Key Performance Indicators (KPI). This follows similar multi-value visualizations found in other BI tools.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

TESTING INSTRUCTIONS

  • Create new chart
  • Select Period Over Period KPI as the Chart type under Other
  • Use the cleaned_sales dataset for testing using sales as a measure.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Congrats on making your first PR and thank you for contributing to Superset! 🎉 ❤️

We hope to see you in our Slack community too! Not signed up? Use our Slack App to self-register.

@craig-rueda
Copy link
Member

Please make this PR against https://github.com/apache/superset as this fork is rebased from there :)

@eschutho
Copy link
Member

This is just for testing and cursory review

@eschutho eschutho reopened this Jan 24, 2024
// imported from @superset-ui/core. For variables available, please visit
// https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-core/src/style/index.ts

const Styles = styled.div<PopKPIStylesProps>`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, can we use css here while defining the styles as a variable as detailed in the wiki

display: flex;
flex-direction: column;
justify-content: center;
padding: ${({ theme }) => theme.tdUnit * 4}px;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First time I see this tdUnit, what about gridUnit instead?

width: ${({ width }) => width}px;
`;

const BigValueContainer = styled.div`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, let's use css for styled component that do not need to be re-used, either inline or by assigning them to a var

Comment on lines +25 to +31
// The following Styles component is a <div> element, which has been styled using Emotion
// For docs, visit https://emotion.sh/docs/styled

// Theming variables are provided for your use via a ThemeProvider
// imported from @superset-ui/core. For variables available, please visit
// https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-core/src/style/index.ts

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// The following Styles component is a <div> element, which has been styled using Emotion
// For docs, visit https://emotion.sh/docs/styled
// Theming variables are provided for your use via a ThemeProvider
// imported from @superset-ui/core. For variables available, please visit
// https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-core/src/style/index.ts


const Styles = styled.div<PopKPIStylesProps>`

font-family: ${({ theme }) => theme.typography.families.sansSerif};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +36 to +37
// it's possible to add validators to controls if
// certain selections/types need to be enforced
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// it's possible to add validators to controls if
// certain selections/types need to be enforced

...sharedControls.adhoc_filters,
label: t('FILTERS (Custom)'),
description:
'This only applies when selecting the Range for Comparison Type- Custom',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please localize this

*/
constructor() {
const metadata = new ChartMetadata({
description: 'KPI viz for comparing multiple period',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's localize this

width,
height,
data,
// and now your control data, manipulated as needed, and passed through as props!
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// and now your control data, manipulated as needed, and passed through as props!


let modTimeRange: string | null = timeRange;

if (timeRange === 'NO_TIME_RANGE' || timeRange === '_(NO_TIME_RANGE)'){
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure these are supposed to be strings and not a constant? I am looking at superset/utils/date_parser.py and it looks like these are being referred to a constant NO_TIME_RANGE = "No filter"


type MomentTuple = [moment.Moment | null, moment.Moment | null];

function getSinceUntil(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this function does not belong here but to the utils. Maybe time-format in superset-ui-core?

);

const [testSince, testUntil] = getSinceUntil(
timeFilter.comparator.toLowerCase(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a null check here?

startDatePrev = startDate.subtract(1, 'month');
endDatePrev = endDate.subtract(1, 'month');
} else if (calcType === 'r') {
startDatePrev = startDate.clone().subtract(daysBetween.valueOf(), 'day');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diff should already return a number so valueOf might be redundant here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need clone here?


const rootElem = createRef<HTMLDivElement>();

useEffect(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for? I don't think we use it anywhere?

* either add additional plugin folders (similar in structure to ./plugin)
* OR export multiple instances of `ChartPlugin` extensions in ./plugin/index.ts
* which in turn load exports from CustomViz.tsx
*/
Copy link
Member

@kgabryje kgabryje Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need all those comments (in other files too)

return [null, null];
}

if (timeRange?.startsWith('last') && !timeRange.includes(separator)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is a copy of superset/utils/date_parser.py::get_since_until right? If that's the case, could you add a comment here that refers to date_parser.py, and the other way around - a comment in date_parser.py that refers to this function. The goal is that when someone tries to make a change to 1 of those functions, they should know that they need to sync that change with the corresponding function

]);


const timeFilter: any = formData.adhoc_filters?.find(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wherever possible, please try not to use any


let formDataB: QueryFormData;

if (timeComparison!='c'){
Copy link
Member

@kgabryje kgabryje Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can run your code through prettier (npm run format) to format it to match the style of other files

[
{
name: 'metrics',
config: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessary to override the config, metrics control already has non empty validator set. You can just put ['metrics'] here

label: t('Range for Comparison'),
default: 'y',
choices: [
['y', 'Year'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also localize the names here

const dataA = queriesData[0].data as TimeseriesDataRecord[];
const dataB = queriesData[1].data as TimeseriesDataRecord[];
const data = dataA;
const metricName = getMetricLabel(metrics[0]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we only take the first metric here and ignore the rest? If so, we should use metric control instead of metrics in the control panel

const percentDifference: string = formatPercentChange(percentDifferenceNum);

return {
width,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only return those values that are used in the main plugin file. I see that don't use for example data, metrics or metricName in PopKPI so we should remove those from here

@eschutho
Copy link
Member

@fernando-bytecode I'm going to merge this PR into my branch so that we can add a feature flag on it but go ahead and open a new PR to it when you're ready with the Ci fixes,etc.

@eschutho eschutho merged commit 68fe17f into preset-io:bytecode/time-shift Jan 30, 2024
43 of 55 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants