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

Survey Answer Data Chart #249

Merged
merged 45 commits into from
Jun 13, 2024
Merged

Survey Answer Data Chart #249

merged 45 commits into from
Jun 13, 2024

Conversation

ajmenca
Copy link
Collaborator

@ajmenca ajmenca commented Apr 25, 2024

Overview

This PR adds a SurveyDataChart Container component. This chart will graph responses to a Survey over time (configured by specifying the Survey Name, Step Identifier, and optionally the Result Identifier). The presentation here was driven by a component I originally created for a customer where they have multiple surveys that calculate multiple scores per survey, and they want to see those scores side by side over time. As a specific example, the project was giving repeated instances of the Five Factor Wellness Inventory; this survey generates a total score and five second order scores, and the aim was to show all of those scores over time. The storybook entry I created is a simplified version of this:

image
image

The bigger swing I took here was to attempt to leverage the rendering code already established in the DailyDataChart by refactoring it into a more generic DataChart component that is used by both the DailyDataChart and the SurveyDataChart. This is a pattern that I think we should really be following more often; separating the responsibilities of retrieving data and presenting data. I've run into several cases in using the MDHUI library where I couldn't re-use one of the components because I needed to make the slightest tweak to the set of data being presented and couldn't because we're not separating these concerns (i.e. the SurveyTaskList gets a specific set of Tasks, rather than being a presentational component which renders a list of Tasks it's given, and we also provide an OpenSurveyTaskList which gets the data). So I took the plunge in this PR to try and start introducing and encouraging this pattern.

Behaviorally, the only change I ended up making to the rendering code was the addition of being able to display multiple sets of data on one graph (something we'd had in the DeviceDataMonthChart but then did not support in the DailyDataChart). I did this by having an optional dataKeys parameter for the DataChart. If you don't pass any, it assumes a basic default key of value which is how the DailyDataChart formats its basic data. So the Survey Chart can support lines, as shown above, as well as area and bar presentations (though you can get some truly awful color combinations of areas):
image
image

I also made it so that the "parent" container is the one who specifies how the hover detail works. This was mostly for handling rather different formatting if there's only one line versus multiple.

Backwards compatibility for the DailyDataChart should be unchanged; it's external interface was unmodified. All stories for it should still work, as verification.

Security

REMINDER: All file contents are public.

  • I have ensured no secure credentials or sensitive information remain in code, metadata, comments, etc.
    • Please verify that you double checked that .storybook/preview.js does not contain your participant access key details.
    • There are no temporary testing changes committed such as API base URLs, access tokens, print/log statements, etc.
  • These changes do not introduce any security risks, or any such risks have been properly mitigated.

Describe briefly what security risks you considered, why they don't apply, or how they've been mitigated.

Checklist

Testing

Documentation

  • I have added relevant Storybook updates to this PR as well.
  • If this feature requires a developer doc update, tag @CareEvolution/api-docs.

Consider "Squash and merge" as needed to keep the commit history reasonable on main.

Reviewers

Assign to the appropriate reviewer(s). Minimally, a second set of eyes is needed ensure no non-public information is published. Consider also including:

  • Subject-matter experts
  • Style/editing reviewers
  • Others requested by the content owner

Summary by CodeRabbit

  • New Features

    • Introduced new chart types (Line, Bar, Area) for visualizing survey and daily data.
    • Added support for a new "6Month" interval type in date range components.
  • Enhancements

    • Improved data handling and chart rendering with the new TimeSeriesChart component.
    • Updated chart options with additional properties for customization (colors, thresholds).
  • Bug Fixes

    • Enhanced title generation for date ranges to include the new "6Month" interval.
  • Refactor

    • Streamlined data processing and interval handling in chart components.
    • Replaced custom random number generator with a predictable one for consistent data previews.

- Break the rendering portion of the DailyDataChart out from the Data Querying part
- Reuse the rendering to grab data from surveys instead.
- Support multiple lines on the line graph.
- Simplify presentation by always assuming an array of dataKeys but defaulting to a single entry array.
- Support multiple colors for all chart types.
@basyl-durnan
Copy link
Contributor

Very cool!

  • Looks like you can graph multiple survey results on the same graph. Can those be results from the same and different surveys?
  • I noticed you said "survey step". While I imagine it will almost always be a survey step's result, would they be able to handle form item results as well?
  • Is there any validation to making sure we get numeric values? I of course hope study teams put validation on the result, but... you never know. How would it handle a non-numeric value?
  • What happens if a survey is completed multiple times in one day? I'm not sure I have an answer for what to do... some people may want to visually see all of them, others may want an average.
  • Forgive me for not trusting myself to parse the code behind this, but I assume there is some check in case surveys share similar field names, right? We probably specify the survey?
  • You can click on the bars to also see a legend, right?

@basyl-durnan
Copy link
Contributor

Adding @Kbalon in case she has some thoughts and/or feedback.

@ajmenca
Copy link
Collaborator Author

ajmenca commented Apr 25, 2024

  • Looks like you can graph multiple survey results on the same graph. Can those be results from the same and different surveys?

Yes, each line is configured individually and the data is queried for it individually, so you can combine things.

  • I noticed you said "survey step". While I imagine it will almost always be a survey step's result, would they be able to handle form item results as well?

Yes, the configuration actually takes both a step identifier and an (optional) result identifier.

  • Is there any validation to making sure we get numeric values? I of course hope study teams put validation on the result, but... you never know. How would it handle a non-numeric value?

Not at the moment. A non numerical value will cause a failure in the graph; I can update it to just throw those values away if they come up though (which would really be the only option to proceed and graph what is valid).

  • What happens if a survey is completed multiple times in one day? I'm not sure I have an answer for what to do... some people may want to visually see all of them, others may want an average.

I can verify with a test, but I think the way I'm parsing the data, the later answer would be the one that would show up. I agree there's not exactly a predictable expectation for this sort of case. Seeing all the points wouldn't really be reasonable for this type of graph though; i.e. when you're making a line, which point do you run the line through?

  • Forgive me for not trusting myself to parse the code behind this, but I assume there is some check in case surveys share similar field names, right? We probably specify the survey?

Yup, survey name, step identifier, result identifier. I'll update the PR description for clarity.

  • You can click on the bars to also see a legend, right?

Hover, but yes - same behavior.

@basyl-durnan
Copy link
Contributor

I think an error is probably best for non-numerical values. If someone is trying to graph results, we probably want to make it obvious there is an error in the survey values. The study team should enforce validation or a picklist.

Great point about showing multiple results on the same day. Let's revisit averages and such if that comes up.

Thanks!

@chrisnowak
Copy link
Contributor

I would definitely just throw the values away if it's non-numeric rather than have the graph error; makes it robust if you ever change anything in the survey definition etc...one string could crash the whole graph etc.

@basyl-durnan
Copy link
Contributor

@chrisnowak I worry about a team that made an error and not realizing it for some time since it wouldn't stand out. However, I could imagine there being a "skip" or "prefer not to answer" option and they wouldn't want an error because of that; so probably best to dump.

Copy link
Collaborator

@reneefromhold reneefromhold left a comment

Choose a reason for hiding this comment

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

Overall, lg and worthwhile to have.
I had a few nits and comments on unused items.
I added a NoData story to the SurveyData component, but it was erroring (code below). Could be I am misunderstanding how to set it up. Can we have Defaults, NoData, and Live versions for the SurveyData.

/*
export const NoData = {
args: {
title: "No Data",
options: {
domainMin: 0,
lineColor: ["#e41a1c", "#377eb8", "#4daf4a"],
areaColor: ["#d41a1c", "#277eb8", "#3daf4a"]
},
intervalType: "Week",
weekStartsOn: "6DaysAgo",
lines: [{ label: "Creative Self", surveyName: "FFWEL", stepIdentifier: "CreativeSelf", resultIdentifier: "CreativeSelf", stroke: "#e41a1c" },
{ label: "Coping Self", surveyName: "FFWEL", stepIdentifier: "CopingSelf", resultIdentifier: "CopingSelf", stroke: "#377eb8" },
{ label: "Social Self", surveyName: "FFWEL", stepIdentifier: "SocialSelf", resultIdentifier: "SocialSelf", stroke: "#4daf4a" }],
valueFormatter: (value: number) => Number(value.toFixed(0)).toLocaleString(),
chartType: "Area",
previewDataProvider: (start: Date, end: Date) => {
const data: SurveyAnswersPage[] = [];
return Promise.resolve(data);
}
},
render: render
}; */

Copy link
Contributor

@chrisnowak chrisnowak left a comment

Choose a reason for hiding this comment

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

I got pulled away from my review due to some other issues, so gonna post my comments now after my initial first pass - but I still want to dig in a bit deeper into TimeSeriesChart

src/components/container/DailyDataChart/DailyDataChart.tsx Outdated Show resolved Hide resolved
src/components/container/DailyDataChart/DailyDataChart.tsx Outdated Show resolved Hide resolved
src/components/container/DailyDataChart/DailyDataChart.tsx Outdated Show resolved Hide resolved
}

export interface BarChartOptions {
barColor?: ColorDefinition | ColorDefinition[],
Copy link
Contributor

Choose a reason for hiding this comment

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

Would definitely NOT do parallel arrays here...we should definitely figure out another way to structure this such that colors are attached to the actual series that is being represented here; it took me a while just to figure out how coloring each survey answer would work.

Realistically if we think about how you'd set this up in view builder; you'd want to add a survey answer to the chart and specify a color specific to that answer...I would suggest that each dataKey here is an object that has a color associated with it, and your SurveyAnswerChartParameters (or whatever we name it) has a color on it

Copy link
Contributor

Choose a reason for hiding this comment

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

So basically to figure out the color of the bar it would:

  • Check the thresholds; if over a threshold, use the threshold color
  • If the dataKey has a color specified, use the dataKey color
  • If the options has a color specified, use that color
  • Otherwise use var(--mdhui-color-primary)

Copy link
Collaborator Author

@ajmenca ajmenca May 21, 2024

Choose a reason for hiding this comment

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

I wrestled with this a bit because the parallel arrays didn't make me very happy either. I was trying to walk a balance of not having 3 different interfaces for doing things between 3 different related but different widgets (the DailyDataChart, the SurveyAnswerChart, and the TimeSeriesChart). I was trying to avoid having the DailyData and SurveyAnswer have "custom" interfaces that they each "adapted" into something for the TimeSeriesChart because that seemed more confusing. In particular I really wanted them to be able to share these base FooChartOptions classes. And in doing so, I was trying to not break the current interface of the DailyDataChart, so that any current users could keep on working, and I wouldn't have to change View Builder.

Honestly, I feel strongly that changing the dataKeys for the TimeSeriesChart into objects instead of the current string array doesn't make it clearer. I think that having two separate places where you can define the colors is harder to discover and understand as an end user, and the fact that we'd have to convey the hierarchy of where we look for and prioritize those colors is a situation I'd prefer to avoid.

Here's two suggestions:

  1. Move the dataKeys property off of the TimeSeriesChartProps and into each FooChartOptions.
export interface LineChartOptions {
    lineColor?: ColorDefinition | ColorDefinition[],
    dataKeys?: undefined | string[],
    connectNulls?: boolean,
    domainMin?: number | "Auto"
}

export interface BarChartOptions {
    barColor?: ColorDefinition | ColorDefinition[],
    dataKeys?: undefined | string[],
    thresholds?: BarChartThreshold[]
}

This at least colocates the parallel arrays so they're next to each other.

  1. Similar solution, but more like your suggestion, again removing the dataKeys property from TimeSeriesChart:
export interface LineChartOptions {
    lineColor?: ColorDefinition,
    seriesDefinition? : undefined | { dataKey: string, lineColor: ColorDefinition }[]
    connectNulls?: boolean,
    domainMin?: number | "Auto"
}

export interface BarChartOptions {
    barColor?: ColorDefinition,
    seriesDefinition? : undefined | { dataKey: string, barColor: ColorDefinition }[]
    thresholds?: BarChartThreshold[]
}

This has the issue of having to "prioritize" things, but at least in this case the properties are much closer together and maybe its more obvious?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm...yeah neither option here feels right to me.

1 as you point out is still parallel arrays

2 is really burying a key required top-level property into sub-sub object so our chart would be like <TimeSeriesChart options={{seriesDefinition:{dataKey:"Steps", barColor:"blue"}}}/>
which feels rough.

I would certainly not break the DailyDataChart interface/props, but I think it'd be ok to have a distinct DailyDataChartLineOptions, DailyDataChartBarChartOptions, etc. interfaces so we have freedom here...just changing types is not enough of a breaking change that I would feel like we'd need to cut a new major. Or we can name these new options "TimeSeriesChartOptions"

So I think we do that^ and keep those types the same. And then we can split our "options" from "series" such that we have:

export interface TimeSeriesChartProps {
    title?: string
    intervalType?: "Week" | "Month" | "6Month" | "Day",
    intervalStart: Date,
    data: any[] | undefined,
    expectedDataInterval?: Duration,
    series: ChartSeries[]
    chartHasData: boolean,
    tooltip: ({ active, payload, label }: any) => React.JSX.Element | null,
    chartType: "Line" | "Bar" | "Area"
    options?: TimeSeriesLineChartOptions | TimeSeriesBarChartOptions
    innerRef?: React.Ref<HTMLDivElement>
}

export interface ChartSeries {
    dataKey: string
    color: ColorDefinition
} 

export interface AreaChartSeries extends ChartSeries {
    areaColor: ColorDefinition // not sure how many use cases for multi-series area charts we have (or area charts in general), so could go either way on this
} 

export interface TimeSeriesLineChartOptions {
    connectNulls?: boolean,
    domainMin?: number | "Auto"
}

export interface TimeSeriesBarChartOptions {
    thresholds?: BarChartThreshold[]
}

export interface TimeSeriesBarChartThreshold {
    value: number
    referenceLineColor?: ColorDefinition
    overThresholdBarColor?: ColorDefinition
}

Something like that? And now that I look at that ^ we could consider dropping "options" entirely. domainMin probably makes sense for all charts I think...not sure why it was just added to lines. And sounds like we're adding thresholds to line charts as well...not sure if that is coming here or in @reneefromhold 's PR. The only kinda oddball one feels like "connectNulls" which i'd be fine just putting on TimeSeriesChartProps even if it doesn't make sense for bar/area charts right now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've implemented this with the slight adjustment that instead of calling it TimeSeriesLineChartOptions I called them MultipleSeriesLineChartOptions because then the SurveyAnswerChart and TimeSeriesChart both share a similar interface because they both support showing multiple series of data. The DailyDataChart is the only one that converts from its own property styles to the interface that TimeSeriesChart wants. I've adjusted all the stories as necessary.

I'd talked to Renee and asked if she would add thresholds to line charts in her PR as this PR was large enough as it was, and adding it in her PR where it would be used the first time seemed appropriate.


export interface LineChartOptions {
lineColor?: ColorDefinition | ColorDefinition[],
connectNulls?: boolean,
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm yeah, for survey answer data I can see this make sense since you might have large gaps in between

coderabbitai[bot]

This comment was marked as off-topic.

- Model Month behavior; by default go 6 months back from the current month, all the way back to the 1st.
- Fix tick mark generation on TimeSeriesChart for multiple possibilities of how the 6 month span starts, to always get the ticks in predictable spots.
- Add stories to demonstrate this behavior.
…eriesChart

- This was preventing the Loading indicator from ever being presented.
Can re-use because DailyDataChart is prevented from calling with 6Month parameter by other type checking.
coderabbitai[bot]

This comment was marked as off-topic.

@CareEvolution CareEvolution deleted a comment from coderabbitai bot May 21, 2024
@CareEvolution CareEvolution deleted a comment from ClaysGit May 21, 2024
@CareEvolution CareEvolution deleted a comment from coderabbitai bot May 21, 2024
@CareEvolution CareEvolution deleted a comment from coderabbitai bot May 21, 2024
@CareEvolution CareEvolution deleted a comment from ClaysGit May 21, 2024
Avoids parallel arrays for keys and colors.  Maintain backwards interface to DailyDataChart for compatibility purposes by mapping single chart series properties to multiple series properties.
Copy link

coderabbitai bot commented May 30, 2024

Walkthrough

The updates primarily enhance charting capabilities and interval handling within various components. Significant changes include the introduction of new chart configurations, support for a "6Month" interval type, and the addition of new helper functions for interval calculations and random data generation. The refactoring of data processing and chart rendering functions, along with the introduction of the TimeSeriesChart component, streamline the code and improve functionality.

Changes

File/Path Change Summary
src/components/container/DailyDataChart/DailyDataChart.stories.tsx Added imports and a new story stepsWithGapLineChart with custom data preview provider.
src/components/container/DailyDataChart/DailyDataChart.tsx Refactored imports, interval handling logic, data processing, and chart rendering; introduced TimeSeriesChart.
src/components/container/SurveyAnswerChart/SurveyAnswerChart.stories.tsx Introduced components and configurations for rendering various chart types based on survey responses.
src/components/container/SurveyAnswerChart/SurveyAnswerChart.tsx Added functionality for rendering survey answer data in various charts.
src/components/container/index.ts Modified exported entities in the DailyDataChart module, retaining only the default export.
src/components/presentational/DateRangeCoordinator/DateRangeCoordinator.stories.tsx Updated to use SurveyAnswerChart and added new exports for different interval types.
src/components/presentational/DateRangeCoordinator/DateRangeCoordinator.tsx Extended intervalType enum to include 6Month.
src/components/presentational/DateRangeNavigator/DateRangeNavigator.tsx Added support for a new interval type "6Month".
src/components/presentational/TimeSeriesChart/TimeSeriesChart.stories.tsx Introduced test scenarios for various time series charts with different configurations.
src/components/presentational/TimeSeriesChart/TimeSeriesChart.tsx Added TimeSeriesChart component for rendering time series charts.
src/components/presentational/index.ts Added export for DataChart and related chart options.
src/helpers/chartOptions.ts Introduced interfaces for defining options and configurations for various chart types.
src/helpers/date-helpers.ts Updated titleForDateRange function to support the "6Month" interval type.
src/helpers/get-interval-start.ts Added functions for calculating interval starts based on different interval types.
src/helpers/index.ts Reordered and added exports of modules related to daily data types and chart options.
src/helpers/predictableRandomNumber.ts Introduced predictableRandomNumber function for generating large random integers from a string seed.
src/helpers/query-daily-data.tsx Replaced custom random number generation with predictableRandomNumber.

Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between e1875e4 and 04f6bba.

Files selected for processing (6)
  • src/components/container/SurveyAnswerChart/SurveyAnswerChart.stories.tsx (1 hunks)
  • src/components/container/SurveyAnswerChart/SurveyAnswerChart.tsx (1 hunks)
  • src/components/presentational/DateRangeNavigator/DateRangeNavigator.tsx (2 hunks)
  • src/components/presentational/TimeSeriesChart/TimeSeriesChart.stories.tsx (1 hunks)
  • src/components/presentational/TimeSeriesChart/TimeSeriesChart.tsx (1 hunks)
  • src/helpers/chartOptions.ts (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • src/helpers/chartOptions.ts
Additional context used
Biome
src/components/presentational/DateRangeNavigator/DateRangeNavigator.tsx

[error] 49-49: Use === instead of ==.
== is only allowed when comparing against null (lint/suspicious/noDoubleEquals)

== is only allowed when comparing against null

Using == may be unsafe if you are relying on type coercion
Unsafe fix: Use ===

src/components/container/SurveyAnswerChart/SurveyAnswerChart.tsx

[error] 83-83: This var should be declared at the root of the enclosing function. (lint/correctness/noInnerDeclarations)

The var is accessible in the whole body of the enclosing function.
To avoid confusion, it should be declared at the root of the enclosing function.


[error] 94-94: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.


[error] 98-113: Prefer for...of instead of forEach. (lint/complexity/noForEach)

forEach may lead to performance issues when working with large arrays. When combined with functions like filter or map, this causes multiple iterations over the same type.


[error] 100-112: Prefer for...of instead of forEach. (lint/complexity/noForEach)

forEach may lead to performance issues when working with large arrays. When combined with functions like filter or map, this causes multiple iterations over the same type.


[error] 117-117: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.


[error] 118-118: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.


[error] 120-120: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.


[error] 121-121: Avoid redundant double-negation. (lint/complexity/noExtraBooleanCast)

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation


[error] 149-149: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.


[error] 172-172: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.

src/components/presentational/TimeSeriesChart/TimeSeriesChart.stories.tsx

[error] 81-81: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.


[error] 84-84: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.

src/components/container/SurveyAnswerChart/SurveyAnswerChart.stories.tsx

[error] 15-15: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 16-16: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 17-17: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 18-18: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 19-19: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 20-20: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 21-21: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 22-22: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 23-23: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 24-24: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 27-27: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.

src/components/presentational/TimeSeriesChart/TimeSeriesChart.tsx

[error] 18-18: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.


[error] 22-22: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.


[error] 32-32: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.


[error] 109-109: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.


[error] 173-173: This var should be declared at the root of the enclosing function. (lint/correctness/noInnerDeclarations)

The var is accessible in the whole body of the enclosing function.
To avoid confusion, it should be declared at the root of the enclosing function.


[error] 185-185: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.


[error] 188-188: This var should be declared at the root of the enclosing function. (lint/correctness/noInnerDeclarations)

The var is accessible in the whole body of the enclosing function.
To avoid confusion, it should be declared at the root of the enclosing function.


[error] 191-191: This var should be declared at the root of the enclosing function. (lint/correctness/noInnerDeclarations)

The var is accessible in the whole body of the enclosing function.
To avoid confusion, it should be declared at the root of the enclosing function.


[error] 192-192: This var should be declared at the root of the enclosing function. (lint/correctness/noInnerDeclarations)

The var is accessible in the whole body of the enclosing function.
To avoid confusion, it should be declared at the root of the enclosing function.


[error] 193-193: This var should be declared at the root of the enclosing function. (lint/correctness/noInnerDeclarations)

The var is accessible in the whole body of the enclosing function.
To avoid confusion, it should be declared at the root of the enclosing function.


[error] 195-197: This var should be declared at the root of the enclosing function. (lint/correctness/noInnerDeclarations)

The var is accessible in the whole body of the enclosing function.
To avoid confusion, it should be declared at the root of the enclosing function.


[error] 255-255: Missing key property for this element in iterable. (lint/correctness/useJsxKeyInIterable)

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.


[error] 261-261: Avoid using the index of an array as key property in an element. (lint/suspicious/noArrayIndexKey)

This is the source of the key value.

The order of the items may change, and this also affects performances and component state.
Check the React documentation.

Additional comments not posted (4)
src/components/presentational/DateRangeNavigator/DateRangeNavigator.tsx (2)

16-16: Addition of "6Month" interval type enhances flexibility in date range navigation.


26-29: Refactoring to use const and strict equality checks improves code reliability and readability.

src/components/container/SurveyAnswerChart/SurveyAnswerChart.tsx (1)

40-49: Proper handling of default interval types and integration with context for dynamic interval start calculation.

src/components/container/SurveyAnswerChart/SurveyAnswerChart.stories.tsx (1)

59-76: Ensure the weekStartsOn property is correctly set.

Verification successful

The weekStartsOn property is used in multiple places within the codebase, including the SurveyAnswerChart story. The value "6DaysAgo" is consistently used in the SurveyAnswerChart stories, which aligns with the context provided.

  • src/components/container/SurveyAnswerChart/SurveyAnswerChart.stories.tsx: The weekStartsOn property is set to "6DaysAgo" in multiple instances.

Given this consistency, it appears that the weekStartsOn property is correctly set.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the `weekStartsOn` property is correctly set in the `ffwelLineChart` story.

# Test: Search for the property usage. Expect: Correct usage of the property.
rg --type typescript $'weekStartsOn'

Length of output: 70



Script:

#!/bin/bash
# Description: Verify the `weekStartsOn` property is correctly set in the `ffwelLineChart` story.

# Test: Search for the property usage. Expect: Correct usage of the property.
rg --type ts 'weekStartsOn'

Length of output: 5892


Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 25

Outside diff range and nitpick comments (6)
src/components/presentational/DateRangeNavigator/DateRangeNavigator.stories.tsx (2)

Line range hint 2-3: Mark imports used only as types with type.

- import React from "react";
- import { ComponentStory, ComponentMeta } from "@storybook/react";
+ import type React from "react";
+ import type { ComponentStory, ComponentMeta } from "@storybook/react";

Also applies to: 4-5


Line range hint 19-19: Use let or const instead of var for variable declarations.

- var currentDate = new Date();
+ const currentDate = new Date();
src/helpers/get-interval-start.ts (1)

Line range hint 5-11: Simplify the computed expressions and refactor unnecessary else clauses.

- let dayOfWeekLookup = {
+ const dayOfWeekLookup = {
    "Sunday": 0,
    "Monday": 1,
    "Tuesday": 2,
    "Wednesday": 3,
    "Thursday": 4,
    "Friday": 5,
    "Saturday": 6
}
- if (weekStartsOn === "6DaysAgo" || weekStartsOn === "7DaysAgo") {
+ if (["6DaysAgo", "7DaysAgo"].includes(weekStartsOn)) {
    let startDate = add(today, { days: weekStartsOn === "6DaysAgo" ? -6 : -7 });
    return startDate;
- } else {
+ }
let dayOfWeek = dayOfWeekLookup[weekStartsOn];
let dayOfWeekToday = today.getDay();
let daysToSubtract = dayOfWeekToday - dayOfWeek;
if (daysToSubtract < 0) {
    daysToSubtract = 7 + daysToSubtract;
}
let startDate = add(today, { days: -daysToSubtract });
return startDate;
- }

Also applies to: 24-33

src/components/presentational/DateRangeCoordinator/DateRangeCoordinator.tsx (2)

Line range hint 1-1: Mark imports used only as types with type.

- import React, { useEffect, useState } from "react";
- import { createContext } from "react";
+ import type React, { useEffect, useState } from "react";
+ import type { createContext } from "react";

Also applies to: 3-4


Line range hint 38-38: Correct the useEffect dependencies to include initialIntervalStart.

-    }, [props.intervalType, props.weekStartsOn]);
+    }, [props.intervalType, props.weekStartsOn, initialIntervalStart]);
src/components/presentational/DateRangeNavigator/DateRangeNavigator.tsx (1)

Line range hint 31-39: Convert function expressions to arrow functions for consistency and improved readability.

- var nextInterval = function () {
+ const nextInterval = () => {
- var previousInterval = function () {
+ const previousInterval = () => {

Arrow functions provide a cleaner and more concise syntax, especially when the function does not require its own this context.

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between cdd6ad2 and 300a1e8.

Files selected for processing (22)
  • src/components/container/DailyDataChart/DailyDataChart.stories.tsx (2 hunks)
  • src/components/container/DailyDataChart/DailyDataChart.tsx (3 hunks)
  • src/components/container/DailyDataChart/index.ts (1 hunks)
  • src/components/container/SurveyAnswerChart/SurveyAnswerChart.stories.tsx (1 hunks)
  • src/components/container/SurveyAnswerChart/SurveyAnswerChart.tsx (1 hunks)
  • src/components/container/SurveyAnswerChart/index.ts (1 hunks)
  • src/components/container/index.ts (2 hunks)
  • src/components/presentational/DateRangeCoordinator/DateRangeCoordinator.stories.tsx (3 hunks)
  • src/components/presentational/DateRangeCoordinator/DateRangeCoordinator.tsx (2 hunks)
  • src/components/presentational/DateRangeNavigator/DateRangeNavigator.stories.tsx (1 hunks)
  • src/components/presentational/DateRangeNavigator/DateRangeNavigator.tsx (2 hunks)
  • src/components/presentational/TimeSeriesChart/TimeSeriesChart.css (3 hunks)
  • src/components/presentational/TimeSeriesChart/TimeSeriesChart.stories.tsx (1 hunks)
  • src/components/presentational/TimeSeriesChart/TimeSeriesChart.tsx (1 hunks)
  • src/components/presentational/TimeSeriesChart/index.ts (1 hunks)
  • src/components/presentational/index.ts (1 hunks)
  • src/helpers/chartOptions.ts (1 hunks)
  • src/helpers/date-helpers.ts (1 hunks)
  • src/helpers/get-interval-start.ts (2 hunks)
  • src/helpers/index.ts (1 hunks)
  • src/helpers/predictableRandomNumber.ts (1 hunks)
  • src/helpers/query-daily-data.tsx (2 hunks)
Files not reviewed due to errors (1)
  • src/components/container/SurveyAnswerChart/SurveyAnswerChart.stories.tsx (no review received)
Files skipped from review due to trivial changes (4)
  • src/components/container/DailyDataChart/index.ts
  • src/components/container/SurveyAnswerChart/index.ts
  • src/components/presentational/TimeSeriesChart/index.ts
  • src/helpers/index.ts
Additional context used
Biome
src/helpers/predictableRandomNumber.ts

[error] 8-8: Use Number.parseInt instead of the equivalent global.

src/helpers/chartOptions.ts

[error] 1-1: All these imports are only used as types.

src/components/presentational/DateRangeNavigator/DateRangeNavigator.stories.tsx

[error] 2-3: All these imports are only used as types.


[error] 4-5: Some named imports are only used as types.


[error] 19-19: Use let or const instead of var.

src/helpers/get-interval-start.ts

[error] 5-5: The computed expression can be simplified without the use of a string literal.


[error] 6-6: The computed expression can be simplified without the use of a string literal.


[error] 7-7: The computed expression can be simplified without the use of a string literal.


[error] 8-8: The computed expression can be simplified without the use of a string literal.


[error] 9-9: The computed expression can be simplified without the use of a string literal.


[error] 10-10: The computed expression can be simplified without the use of a string literal.


[error] 11-11: The computed expression can be simplified without the use of a string literal.


[error] 24-33: This else clause can be omitted because previous branches break early.


[error] 3-4: This let declares a variable that is only assigned once.


[error] 18-18: Reassigning a function parameter is confusing.


[error] 20-20: This let declares a variable that is only assigned once.


[error] 22-22: This let declares a variable that is only assigned once.


[error] 25-25: This let declares a variable that is only assigned once.


[error] 26-26: This let declares a variable that is only assigned once.


[error] 31-31: This let declares a variable that is only assigned once.


[error] 37-37: This let declares a variable that is only assigned once.


[error] 38-38: This let declares a variable that is only assigned once.

src/components/presentational/DateRangeCoordinator/DateRangeCoordinator.tsx

[error] 1-1: The default import is only used as a type.


[error] 3-4: Some named imports are only used as types.


[error] 38-38: This hook does not specify all of its dependencies: initialIntervalStart


[error] 38-38: This hook specifies more dependencies than necessary: props.weekStartsOn

src/helpers/date-helpers.ts

[error] 12-12: This var should be declared at the root of the enclosing function.


[error] 13-13: This var should be declared at the root of the enclosing function.


[error] 50-50: Use === instead of ==.
== is only allowed when comparing against null


[error] 50-50: Use === instead of ==.
== is only allowed when comparing against null


[error] 53-53: Use === instead of ==.
== is only allowed when comparing against null


[error] 53-53: Use === instead of ==.
== is only allowed when comparing against null


[error] 56-67: This else clause can be omitted because previous branches break early.


[error] 56-56: Use === instead of ==.
== is only allowed when comparing against null


[error] 56-56: Use === instead of ==.
== is only allowed when comparing against null


[error] 59-67: This else clause can be omitted because previous branches break early.


[error] 59-59: Use === instead of ==.
== is only allowed when comparing against null


[error] 59-59: Use === instead of ==.
== is only allowed when comparing against null


[error] 59-59: Use === instead of ==.
== is only allowed when comparing against null


[error] 62-67: This else clause can be omitted because previous branches break early.


[error] 62-62: Use === instead of ==.
== is only allowed when comparing against null


[error] 11-11: Use let or const instead of var.


[error] 12-12: Use let or const instead of var.


[error] 13-13: Use let or const instead of var.


[error] 20-20: Use let or const instead of var.


[error] 21-21: Use let or const instead of var.

src/components/presentational/DateRangeNavigator/DateRangeNavigator.tsx

[error] 26-26: Use === instead of ==.
== is only allowed when comparing against null


[error] 27-27: Use === instead of ==.
== is only allowed when comparing against null


[error] 28-28: Use === instead of ==.
== is only allowed when comparing against null


[error] 31-34: This function expression can be turned into an arrow function.


[error] 36-39: This function expression can be turned into an arrow function.


[error] 49-49: Use === instead of ==.
== is only allowed when comparing against null


[error] 4-5: All these imports are only used as types.


[error] 26-29: Use let or const instead of var.


[error] 31-34: Use let or const instead of var.


[error] 32-32: Use let or const instead of var.


[error] 36-39: Use let or const instead of var.


[error] 37-37: Use let or const instead of var.


[error] 41-41: Use let or const instead of var.


[error] 42-42: Use let or const instead of var.


[error] 43-43: Use let or const instead of var.


[error] 48-48: This let declares a variable that is only assigned once.

src/helpers/query-daily-data.tsx

[error] 37-37: Template literals are preferred over string concatenation.


[error] 46-46: Template literals are preferred over string concatenation.


[error] 50-50: This var should be declared at the root of the enclosing function.


[error] 47-57: This function expression can be turned into an arrow function.


[error] 66-66: This var should be declared at the root of the enclosing function.


[error] 67-67: This var should be declared at the root of the enclosing function.


[error] 67-67: Template literals are preferred over string concatenation.


[error] 82-82: Forbidden non-null assertion.


[error] 85-87: Prefer for...of instead of forEach.


[error] 85-87: This function expression can be turned into an arrow function.


[error] 3-4: All these imports are only used as types.


[error] 15-15: This let declares a variable that is only assigned once.


[error] 36-36: Use let or const instead of var.


[error] 54-54: Reassigning a function parameter is confusing.


[error] 45-45: Use let or const instead of var.


[error] 48-48: Use let or const instead of var.


[error] 50-50: Use let or const instead of var.


[error] 69-69: Reassigning a function parameter is confusing.


[error] 61-61: Use let or const instead of var.


[error] 62-62: This let declares a variable that is only assigned once.

src/components/presentational/DateRangeCoordinator/DateRangeCoordinator.stories.tsx

[error] 30-30: The computed expression can be simplified without the use of a string literal.


[error] 31-31: The computed expression can be simplified without the use of a string literal.


[error] 32-32: The computed expression can be simplified without the use of a string literal.


[error] 33-33: The computed expression can be simplified without the use of a string literal.


[error] 34-34: The computed expression can be simplified without the use of a string literal.


[error] 35-35: The computed expression can be simplified without the use of a string literal.


[error] 36-36: The computed expression can be simplified without the use of a string literal.


[error] 37-37: The computed expression can be simplified without the use of a string literal.


[error] 38-38: The computed expression can be simplified without the use of a string literal.


[error] 39-39: The computed expression can be simplified without the use of a string literal.


[error] 42-42: The computed expression can be simplified without the use of a string literal.


[error] 73-73: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.


[error] 3-4: Some named imports are only used as types.


[error] 9-10: All these imports are only used as types.


[error] 12-13: This let declares a variable that is only assigned once.


[error] 14-15: This let declares a variable that is only assigned once.


[error] 45-45: Use let or const instead of var.


[error] 51-51: This let declares a variable that is only assigned once.

src/components/container/DailyDataChart/DailyDataChart.tsx

[error] 49-49: Use === instead of ==.
== is only allowed when comparing against null


[error] 50-50: Use === instead of ==.
== is only allowed when comparing against null


[error] 73-75: This function expression can be turned into an arrow function.


[error] 97-97: Unexpected any. Specify a different type.


[error] 98-98: This type annotation is trivially inferred from its initialization.


[error] 100-119: Prefer for...of instead of forEach.


[error] 102-102: Unexpected any. Specify a different type.


[error] 105-105: Forbidden non-null assertion.


[error] 107-107: Forbidden non-null assertion.


[error] 124-124: Unexpected any. Specify a different type.


[error] 126-126: This var should be declared at the root of the enclosing function.


[error] 157-179: This else clause can be omitted because previous branches break early.


[error] 168-179: This else clause can be omitted because previous branches break early.


[error] 1-1: The default import is only used as a type.


[error] 2-3: Some named imports are only used as types.


[error] 6-7: Some named imports are only used as types.


[error] 9-10: All these imports are only used as types.


[error] 28-28: This let declares some variables that are only assigned once.


[error] 29-29: This let declares some variables that are only assigned once.


[error] 49-49: This let declares a variable that is only assigned once.

src/components/container/DailyDataChart/DailyDataChart.stories.tsx

[error] 141-141: Template literals are preferred over string concatenation.


[error] 142-142: Template literals are preferred over string concatenation.


[error] 162-162: Template literals are preferred over string concatenation.


[error] 163-163: Template literals are preferred over string concatenation.


[error] 183-183: Template literals are preferred over string concatenation.


[error] 184-184: Template literals are preferred over string concatenation.


[error] 203-203: Template literals are preferred over string concatenation.


[error] 204-204: Template literals are preferred over string concatenation.


[error] 223-223: Template literals are preferred over string concatenation.


[error] 224-224: Template literals are preferred over string concatenation.


[error] 243-243: Template literals are preferred over string concatenation.


[error] 244-244: Template literals are preferred over string concatenation.


[error] 1-2: Some named imports are only used as types.


[error] 3-4: Some named imports are only used as types.


[error] 7-8: This let declares a variable that is only assigned once.


[error] 39-39: This let declares a variable that is only assigned once.


[error] 43-43: This let declares a variable that is only assigned once.


[error] 140-140: Use let or const instead of var.


[error] 141-141: Use let or const instead of var.


[error] 161-161: Use let or const instead of var.

src/components/container/SurveyAnswerChart/SurveyAnswerChart.tsx

[error] 51-51: Use === instead of ==.
== is only allowed when comparing against null


[error] 72-74: This function expression can be turned into an arrow function.


[error] 83-83: This var should be declared at the root of the enclosing function.


[error] 94-94: Unexpected any. Specify a different type.


[error] 95-95: This type annotation is trivially inferred from its initialization.


[error] 98-113: Prefer for...of instead of forEach.


[error] 100-112: Prefer for...of instead of forEach.


[error] 100-100: Forbidden non-null assertion.


[error] 103-103: Forbidden non-null assertion.


[error] 108-108: Forbidden non-null assertion.


[error] 117-117: Unexpected any. Specify a different type.


[error] 118-118: Unexpected any. Specify a different type.


[error] 120-120: Unexpected any. Specify a different type.


[error] 121-121: Avoid redundant double-negation.


[error] 135-135: Use === instead of ==.
== is only allowed when comparing against null


[error] 149-149: Unexpected any. Specify a different type.


[error] 172-172: Unexpected any. Specify a different type.


[error] 1-1: The default import is only used as a type.


[error] 3-4: All these imports are only used as types.


[error] 4-5: Some named imports are only used as types.

src/components/presentational/TimeSeriesChart/TimeSeriesChart.stories.tsx

[error] 13-13: Unexpected any. Specify a different type.


[error] 17-17: Unexpected any. Specify a different type.


[error] 18-18: Unexpected any. Specify a different type.


[error] 57-57: Template literals are preferred over string concatenation.


[error] 58-58: Template literals are preferred over string concatenation.


[error] 59-59: Template literals are preferred over string concatenation.


[error] 67-67: Unexpected any. Specify a different type.


[error] 85-85: Unexpected any. Specify a different type.


[error] 88-88: Unexpected any. Specify a different type.


[error] 85-97: This function expression can be turned into an arrow function.


[error] 1-1: All these imports are only used as types.


[error] 3-4: Some named imports are only used as types.


[error] 12-13: This let declares a variable that is only assigned once.


[error] 24-24: Use let or const instead of var.


[error] 37-37: Use let or const instead of var.


[error] 52-52: Use let or const instead of var.


[error] 67-67: Use let or const instead of var.


[error] 70-70: This let declares a variable that is only assigned once.


[error] 84-97: Use let or const instead of var.

src/components/container/SurveyAnswerChart/SurveyAnswerChart.stories.tsx

[error] 15-15: The computed expression can be simplified without the use of a string literal.


[error] 16-16: The computed expression can be simplified without the use of a string literal.


[error] 17-17: The computed expression can be simplified without the use of a string literal.


[error] 18-18: The computed expression can be simplified without the use of a string literal.


[error] 19-19: The computed expression can be simplified without the use of a string literal.


[error] 20-20: The computed expression can be simplified without the use of a string literal.


[error] 21-21: The computed expression can be simplified without the use of a string literal.


[error] 22-22: The computed expression can be simplified without the use of a string literal.


[error] 23-23: The computed expression can be simplified without the use of a string literal.


[error] 24-24: The computed expression can be simplified without the use of a string literal.


[error] 27-27: The computed expression can be simplified without the use of a string literal.


[error] 43-43: Unexpected any. Specify a different type.


[error] 43-43: Unexpected any. Specify a different type.


[error] 2-3: Some named imports are only used as types.


[error] 3-4: All these imports are only used as types.


[error] 9-10: This let declares a variable that is only assigned once.


[error] 13-13: Use let or const instead of var.


[error] 32-32: This let declares a variable that is only assigned once.


[error] 33-33: This let declares a variable that is only assigned once.


[error] 34-34: This let declares a variable that is only assigned once.

src/components/presentational/TimeSeriesChart/TimeSeriesChart.tsx

[error] 18-18: Unexpected any. Specify a different type.


[error] 22-22: Unexpected any. Specify a different type.


[error] 32-32: Unexpected any. Specify a different type.


[error] 35-35: Use === instead of ==.
== is only allowed when comparing against null


[error] 37-63: This else clause can be omitted because previous branches break early.


[error] 37-37: Use === instead of ==.
== is only allowed when comparing against null


[error] 44-63: This else clause can be omitted because previous branches break early.


[error] 44-44: Use === instead of ==.
== is only allowed when comparing against null


[error] 45-45: This type annotation is trivially inferred from its initialization.


[error] 47-47: Use === instead of ==.
== is only allowed when comparing against null


[error] 57-63: This else clause can be omitted because previous branches break early.


[error] 57-57: Use === instead of ==.
== is only allowed when comparing against null


[error] 76-106: This else clause can be omitted because previous branches break early.


[error] 77-77: This var should be declared at the root of the enclosing function.


[error] 78-78: This var should be declared at the root of the enclosing function.


[error] 81-106: This else clause can be omitted because previous branches break early.


[error] 82-82: This var should be declared at the root of the enclosing function.


[error] 83-83: This var should be declared at the root of the enclosing function.


[error] 87-87: This var should be declared at the root of the enclosing function.


[error] 93-93: This var should be declared at the root of the enclosing function.

Additional comments not posted (2)
src/components/presentational/TimeSeriesChart/TimeSeriesChart.css (1)

Line range hint 1-70: CSS changes are consistent and well-structured.

src/components/presentational/TimeSeriesChart/TimeSeriesChart.tsx (1)

77-78: Declare variables at the root of their enclosing function.
[REFACTOR_SUGGESTion]
Variables like monthLength, numberOfTicks, ticks, currentTick, and firstTick should be declared at the top of their enclosing function to improve readability and maintainability.

Also applies to: 82-83, 87-93

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Outside diff range and nitpick comments (1)
src/components/presentational/DateRangeNavigator/DateRangeNavigator.tsx (1)

Line range hint 49-49: Replace == with === for strict equality checks to avoid type coercion issues.

- if (props.variant == "rounded") {
+ if (props.variant === "rounded") {


function processPages(pages: SurveyAnswer[][]) {
var newDailyData: { [key: string]: SurveyAnswer[] } = {};
for (var i = 0; i < props.series.length; i++) {
Copy link

Choose a reason for hiding this comment

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

Replace var with const for block-scoped variable declarations and avoid using any by specifying more precise types.

- var newDailyData: { [key: string]: SurveyAnswer[] } = {};
+ const newDailyData: { [key: string]: SurveyAnswer[] } = {};
- var data: any[] | undefined = undefined;
+ const data: { timestamp: number, [key: string]: number }[] | undefined = undefined;
- var tooltip = ({ active, payload, _label }: any): React.JSX.Element | null => {
+ const tooltip = ({ active, payload, _label }: { active: boolean, payload: any[], _label: string }): React.JSX.Element | null => {

Also applies to: 94-94, 117-117, 118-118, 120-120, 121-121, 149-149, 172-172

Committable suggestion was skipped due to low confidence.

Tools
Biome

[error] 83-83: This var should be declared at the root of the enclosing function. (lint/correctness/noInnerDeclarations)

The var is accessible in the whole body of the enclosing function.
To avoid confusion, it should be declared at the root of the enclosing function.

Comment on lines +51 to +74
let intervalEnd = intervalType === "Week" ? add(intervalStart, { days: 7 })
: intervalType === "Month" ? add(intervalStart, { months: 1 })
: intervalType === "6Month" ? add(intervalStart, { months: 6 }) :
intervalStart;
const loadCurrentInterval = () => {
setCurrentData(null);
if (props.previewDataProvider) {
props.previewDataProvider(intervalStart, intervalEnd)
.then((data) => {
setCurrentData(processPages(data));
});
return;
}

var dataRequests = props.series.map(l => queryAllSurveyAnswers({
surveyName: l.surveyName,
stepIdentifier: l.stepIdentifier,
resultIdentifier: l.resultIdentifier,
after: intervalStart.toISOString(),
before: intervalEnd.toISOString()
}));
Promise.all(dataRequests).then((data) => {
setCurrentData(processPages(data));
})
Copy link

Choose a reason for hiding this comment

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

Ensure robust error handling in asynchronous data fetching to prevent unhandled promise rejections.

+ .catch(error => {
+     console.error("Failed to load data:", error);
+     setCurrentData({});
+ })
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
let intervalEnd = intervalType === "Week" ? add(intervalStart, { days: 7 })
: intervalType === "Month" ? add(intervalStart, { months: 1 })
: intervalType === "6Month" ? add(intervalStart, { months: 6 }) :
intervalStart;
const loadCurrentInterval = () => {
setCurrentData(null);
if (props.previewDataProvider) {
props.previewDataProvider(intervalStart, intervalEnd)
.then((data) => {
setCurrentData(processPages(data));
});
return;
}
var dataRequests = props.series.map(l => queryAllSurveyAnswers({
surveyName: l.surveyName,
stepIdentifier: l.stepIdentifier,
resultIdentifier: l.resultIdentifier,
after: intervalStart.toISOString(),
before: intervalEnd.toISOString()
}));
Promise.all(dataRequests).then((data) => {
setCurrentData(processPages(data));
})
let intervalEnd = intervalType === "Week" ? add(intervalStart, { days: 7 })
: intervalType === "Month" ? add(intervalStart, { months: 1 })
: intervalType === "6Month" ? add(intervalStart, { months: 6 }) :
intervalStart;
const loadCurrentInterval = () => {
setCurrentData(null);
if (props.previewDataProvider) {
props.previewDataProvider(intervalStart, intervalEnd)
.then((data) => {
setCurrentData(processPages(data));
})
.catch(error => {
console.error("Failed to load data:", error);
setCurrentData({});
});
return;
}
var dataRequests = props.series.map(l => queryAllSurveyAnswers({
surveyName: l.surveyName,
stepIdentifier: l.stepIdentifier,
resultIdentifier: l.resultIdentifier,
after: intervalStart.toISOString(),
before: intervalEnd.toISOString()
}));
Promise.all(dataRequests).then((data) => {
setCurrentData(processPages(data));
})
.catch(error => {
console.error("Failed to load data:", error);
setCurrentData({});
});

return responses;
}

var tooltip = ({ active, payload, _label }: any): React.JSX.Element | null => {
Copy link

Choose a reason for hiding this comment

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

Replace any with more specific types to enhance type safety in the tooltip function.

- var tooltip = ({ active, payload, _label }: any): React.JSX.Element | null => {
+ const tooltip = ({ active, payload, _label }: { active: boolean, payload: { dataKey: string, value: number }[], _label: string }): React.JSX.Element | null => {

Also applies to: 84-84

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
var tooltip = ({ active, payload, _label }: any): React.JSX.Element | null => {
var tooltip = ({ active, payload, _label }: { active: boolean, payload: { dataKey: string, value: number }[], _label: string }): React.JSX.Element | null => {
Tools
Biome

[error] 81-81: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.

Comment on lines +94 to +100
previewDataProvider: async (start: Date, end: Date) => {
var data = await getRandomFFWELData(start,end);
data[0].splice(data[0].length/2,1);
data[1].splice(data[1].length/2,1);
data[2].splice(data[2].length/2,1);
return Promise.resolve(data);
}
Copy link

Choose a reason for hiding this comment

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

Handle data gaps more efficiently in ffwelLineChartWithDataGap.

-            data[0].splice(data[0].length/2,1);
-            data[1].splice(data[1].length/2,1);
-            data[2].splice(data[2].length/2,1);
+            const midIndex = Math.floor(data[0].length / 2);
+            data.forEach(subArray => subArray.splice(midIndex, 1));
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
previewDataProvider: async (start: Date, end: Date) => {
var data = await getRandomFFWELData(start,end);
data[0].splice(data[0].length/2,1);
data[1].splice(data[1].length/2,1);
data[2].splice(data[2].length/2,1);
return Promise.resolve(data);
}
previewDataProvider: async (start: Date, end: Date) => {
var data = await getRandomFFWELData(start,end);
const midIndex = Math.floor(data[0].length / 2);
data.forEach(subArray => subArray.splice(midIndex, 1));
return Promise.resolve(data);
}

Comment on lines +12 to +29
async function generateSurveyResponse(date: Date, resultIdentifier: string, surveyName: string, rangeStart: number, rangeEnd: number): Promise<SurveyAnswer> {
var answer = await predictableRandomNumber(getDayKey(date)+resultIdentifier);
return {
"id": "00000000-0000-0000-0000-000000000000",
"surveyID": "00000000-0000-0000-0000-000000000000",
"surveyResultID": "00000000-0000-0000-0000-000000000000",
"surveyVersion": 0,
"surveyName": surveyName,
"surveyDisplayName": surveyName,
"date": date.toISOString(),
"stepIdentifier": resultIdentifier,
"resultIdentifier": resultIdentifier,
"answers": [
(answer % (rangeEnd - rangeStart) + rangeStart).toString()
],
"insertedDate": date.toISOString()
};
}
Copy link

Choose a reason for hiding this comment

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

Refactor generateSurveyResponse to avoid using string literals for keys.

-    return {
-        "id": "00000000-0000-0000-0000-000000000000",
-        "surveyID": "00000000-0000-0000-0000-000000000000",
-        "surveyResultID": "00000000-0000-0000-0000-000000000000",
-        "surveyVersion": 0,
-        "surveyName": surveyName,
-        "surveyDisplayName": surveyName,
-        "date": date.toISOString(),
-        "stepIdentifier": resultIdentifier,
-        "resultIdentifier": resultIdentifier,
-        "answers": [
-            (answer % (rangeEnd - rangeStart) + rangeStart).toString()
-        ],
-        "insertedDate": date.toISOString()
-    };
+    return {
+        id: "00000000-0000-0000-0000-000000000000",
+        surveyID: "00000000-0000-0000-0000-000000000000",
+        surveyResultID: "00000000-0000-0000-0000-000000000000",
+        surveyVersion: 0,
+        surveyName: surveyName,
+        surveyDisplayName: surveyName,
+        date: date.toISOString(),
+        stepIdentifier: resultIdentifier,
+        resultIdentifier: resultIdentifier,
+        answers: [
+            (answer % (rangeEnd - rangeStart) + rangeStart).toString()
+        ],
+        insertedDate: date.toISOString()
+    };
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
async function generateSurveyResponse(date: Date, resultIdentifier: string, surveyName: string, rangeStart: number, rangeEnd: number): Promise<SurveyAnswer> {
var answer = await predictableRandomNumber(getDayKey(date)+resultIdentifier);
return {
"id": "00000000-0000-0000-0000-000000000000",
"surveyID": "00000000-0000-0000-0000-000000000000",
"surveyResultID": "00000000-0000-0000-0000-000000000000",
"surveyVersion": 0,
"surveyName": surveyName,
"surveyDisplayName": surveyName,
"date": date.toISOString(),
"stepIdentifier": resultIdentifier,
"resultIdentifier": resultIdentifier,
"answers": [
(answer % (rangeEnd - rangeStart) + rangeStart).toString()
],
"insertedDate": date.toISOString()
};
}
async function generateSurveyResponse(date: Date, resultIdentifier: string, surveyName: string, rangeStart: number, rangeEnd: number): Promise<SurveyAnswer> {
var answer = await predictableRandomNumber(getDayKey(date)+resultIdentifier);
return {
id: "00000000-0000-0000-0000-000000000000",
surveyID: "00000000-0000-0000-0000-000000000000",
surveyResultID: "00000000-0000-0000-0000-000000000000",
surveyVersion: 0,
surveyName: surveyName,
surveyDisplayName: surveyName,
date: date.toISOString(),
stepIdentifier: resultIdentifier,
resultIdentifier: resultIdentifier,
answers: [
(answer % (rangeEnd - rangeStart) + rangeStart).toString()
],
insertedDate: date.toISOString()
};
}
Tools
Biome

[error] 15-15: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 16-16: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 17-17: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 18-18: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 19-19: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 20-20: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 21-21: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 22-22: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 23-23: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 24-24: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.


[error] 27-27: The computed expression can be simplified without the use of a string literal. (lint/complexity/useLiteralKeys)

Unsafe fix: Use a literal key instead.

)}
</defs>
{(props.options as MultiSeriesBarChartOptions)?.thresholds?.filter(t=>t.referenceLineColor)?.map((threshold, index) =>
<ReferenceLine y={threshold.value} stroke={resolveColor(layoutContext.colorScheme, threshold.referenceLineColor)} />
Copy link

Choose a reason for hiding this comment

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

Add a key property to ReferenceLine to ensure stable React re-renders.

-                            <ReferenceLine y={threshold.value} stroke={resolveColor(layoutContext.colorScheme, threshold.referenceLineColor)} />
+                            <ReferenceLine key={`ref-line-${index}`} y={threshold.value} stroke={resolveColor(layoutContext.colorScheme, threshold.referenceLineColor)} />
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
<ReferenceLine y={threshold.value} stroke={resolveColor(layoutContext.colorScheme, threshold.referenceLineColor)} />
<ReferenceLine key={`ref-line-${index}`} y={threshold.value} stroke={resolveColor(layoutContext.colorScheme, threshold.referenceLineColor)} />
Tools
Biome

[error] 255-255: Missing key property for this element in iterable. (lint/correctness/useJsxKeyInIterable)

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

expectedDataInterval?: Duration,
series: ChartSeries[] | AreaChartSeries[],
chartHasData: boolean,
tooltip: ({ active, payload, label }: any) => React.JSX.Element | null,
Copy link

Choose a reason for hiding this comment

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

Specify a more precise type for the tooltip prop in TimeSeriesChartProps.

-    tooltip: ({ active, payload, label }: any) => React.JSX.Element | null,
+    tooltip: ({ active, payload, label }: TooltipProps) => React.JSX.Element | null,

Committable suggestion was skipped due to low confidence.

Tools
Biome

[error] 22-22: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.

title?: string
intervalType?: "Week" | "Month" | "6Month" | "Day",
intervalStart: Date,
data: Record<string,any>[] | undefined,
Copy link

Choose a reason for hiding this comment

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

Specify a more precise type than any for data in TimeSeriesChartProps.

-    data: Record<string,any>[] | undefined,
+    data: Record<string, number | string | Date>[] | undefined,
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
data: Record<string,any>[] | undefined,
data: Record<string, number | string | Date>[] | undefined,
Tools
Biome

[error] 18-18: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.

Comment on lines +32 to +67
const DayTick = ({ x, y, stroke, payload }: any) => {
var value = payload.value;
let currentDate = new Date(value);
if (intervalType === "Month") {
return <text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 15} textAnchor="middle" fontSize="12">{currentDate.getDate()}</text>;
} else if (intervalType === "6Month" ){
let monthLabel = currentDate.getDate() === 1 ? format(currentDate, "LLL") : "";
let dayLabel = currentDate.getDate().toString();
return <>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 8} textAnchor="middle" fontSize="11">{monthLabel}</text>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 24} textAnchor="middle" fontSize="12">{dayLabel}</text>
</>;
} else if (intervalType === "Week" ){
let dayOfWeek: string = "";
for (let i = 0; i < 7; i++) {
if (currentDate.getTime() === value) {
dayOfWeek = format(currentDate, "EEEEEE");
break;
}
currentDate = add(currentDate, { days: 1 });
}
return <>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 8} textAnchor="middle" fontSize="11">{dayOfWeek}</text>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 24} textAnchor="middle" fontSize="12">{currentDate.getDate()}</text>
</>;
} else if ( intervalType === "Day"){
const startTime = new Date(props.intervalStart);
startTime.setHours(0, 0, 0, 0);
return <>
<text fill="var(--mdhui-text-color-2)" x={x} y={y + 24} textAnchor="middle" fontSize="12">{format(currentDate, "hh:mm aaa")}</text>
</>;
}
return <>
<text fill="var(--mdhui-text-color-2)" x={x} y={y + 15} textAnchor="middle" fontSize="12">{value}</text>;
</>
}
Copy link

Choose a reason for hiding this comment

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

Refactor DayTick to avoid using any and improve type safety.

-    const DayTick = ({ x, y, stroke, payload }: any) => {
+    interface DayTickProps {
+        x: number;
+        y: number;
+        stroke: string;
+        payload: { value: string };
+    }
+    const DayTick = ({ x, y, stroke, payload }: DayTickProps) => {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const DayTick = ({ x, y, stroke, payload }: any) => {
var value = payload.value;
let currentDate = new Date(value);
if (intervalType === "Month") {
return <text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 15} textAnchor="middle" fontSize="12">{currentDate.getDate()}</text>;
} else if (intervalType === "6Month" ){
let monthLabel = currentDate.getDate() === 1 ? format(currentDate, "LLL") : "";
let dayLabel = currentDate.getDate().toString();
return <>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 8} textAnchor="middle" fontSize="11">{monthLabel}</text>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 24} textAnchor="middle" fontSize="12">{dayLabel}</text>
</>;
} else if (intervalType === "Week" ){
let dayOfWeek: string = "";
for (let i = 0; i < 7; i++) {
if (currentDate.getTime() === value) {
dayOfWeek = format(currentDate, "EEEEEE");
break;
}
currentDate = add(currentDate, { days: 1 });
}
return <>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 8} textAnchor="middle" fontSize="11">{dayOfWeek}</text>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 24} textAnchor="middle" fontSize="12">{currentDate.getDate()}</text>
</>;
} else if ( intervalType === "Day"){
const startTime = new Date(props.intervalStart);
startTime.setHours(0, 0, 0, 0);
return <>
<text fill="var(--mdhui-text-color-2)" x={x} y={y + 24} textAnchor="middle" fontSize="12">{format(currentDate, "hh:mm aaa")}</text>
</>;
}
return <>
<text fill="var(--mdhui-text-color-2)" x={x} y={y + 15} textAnchor="middle" fontSize="12">{value}</text>;
</>
}
interface DayTickProps {
x: number;
y: number;
stroke: string;
payload: { value: string };
}
const DayTick = ({ x, y, stroke, payload }: DayTickProps) => {
var value = payload.value;
let currentDate = new Date(value);
if (intervalType === "Month") {
return <text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 15} textAnchor="middle" fontSize="12">{currentDate.getDate()}</text>;
} else if (intervalType === "6Month" ){
let monthLabel = currentDate.getDate() === 1 ? format(currentDate, "LLL") : "";
let dayLabel = currentDate.getDate().toString();
return <>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 8} textAnchor="middle" fontSize="11">{monthLabel}</text>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 24} textAnchor="middle" fontSize="12">{dayLabel}</text>
</>;
} else if (intervalType === "Week" ){
let dayOfWeek: string = "";
for (let i = 0; i < 7; i++) {
if (currentDate.getTime() === value) {
dayOfWeek = format(currentDate, "EEEEEE");
break;
}
currentDate = add(currentDate, { days: 1 });
}
return <>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 8} textAnchor="middle" fontSize="11">{dayOfWeek}</text>
<text className={isToday(currentDate) ? "today" : ""} fill="var(--mdhui-text-color-2)" x={x} y={y + 24} textAnchor="middle" fontSize="12">{currentDate.getDate()}</text>
</>;
} else if ( intervalType === "Day"){
const startTime = new Date(props.intervalStart);
startTime.setHours(0, 0, 0, 0);
return <>
<text fill="var(--mdhui-text-color-2)" x={x} y={y + 24} textAnchor="middle" fontSize="12">{format(currentDate, "hh:mm aaa")}</text>
</>;
}
return <>
<text fill="var(--mdhui-text-color-2)" x={x} y={y + 15} textAnchor="middle" fontSize="12">{value}</text>;
</>
}
Tools
Biome

[error] 32-32: Unexpected any. Specify a different type. (lint/suspicious/noExplicitAny)

any disables many type checking rules. Its use should be avoided.

@basyl-durnan
Copy link
Contributor

Do we anticipate a date yet for the next MDHDUI release and would this be included?

Copy link
Contributor

@chrisnowak chrisnowak left a comment

Choose a reason for hiding this comment

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

Looks good! I like the latest changes.

@ajmenca ajmenca merged commit a0f2fa3 into main Jun 13, 2024
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.

None yet

7 participants