Skip to content

Commit

Permalink
build: release v6.91.0 (#6918)
Browse files Browse the repository at this point in the history
* feat: charts (#6790)

* chore: import react google charts

* feat: create skeleton insights page

* fix: regex for route matching for insight page

* fix: layout of insights page

* feat: dummy endpoint for all encrypted data

* feat: queries to get all encrypted submission

* fix: encrypted model find params

* feat: get and decrypted all submission data

* feat: get formfields

* feat: generate charts

* fix: typing of spacing

* feat: create mapping for field to charts

* feat: some upgrades to format charts

* feat:wordcloud

* fix: remove excess divider

* fix: prefill rating values in bar chart

* feat: add question number to chart title

* feat: skeleton for changing chart to table

* chore: add gstatic charts to csp policy

* fix: rating average counting

* feat: table toggle mode

* feat: use icon button instead of toggle

* feat: dummy date picker

* fix: better date range picker styling

* feat: date range filtering

* fix: do not show rating if no values

* fix: filtering by date and styling

* fix: increase size of charts

* fix: do not display wordcloud if no words

* fix: alignment of wordcloud title

* fix: typing for submission insights dto

* refactor: make code more readable

* fix: typing of render array

* fix: do not randomize color for rating

* feat: fix bar graph colours changing on refocus

* feat: fix random word cloud movements on re-render

* feat: address MR comments

* feat: corrected types and fixed lint comments

* feat: update average rating to account for division by 0

* feat: refactored filter function to remove redundant date parsing

* feat: set max words for word cloud

* fix: fe lint issue

* refactor: cleanup constants

* feat: add growthbook toggle

* fix: flickering pie chart tooltip

* fix: ordering of frontend/package.json

* chore: add utils

* feat: add empty insights field

* fix: typeerror on admin submission

* refactor: rename insights to charts

* feat: add beta badge

* refactor: secretkeyverification to common component for results and charts tab

* fix: table charts ui

* fix: charts secretkeyvewrification component

* fix: remove stray space between charts and badge on tab title

* fix: remove testing flag

* chore: update copy for no charts generted

* fix: endday not calculated correctly

* feat(be): add limit and reverse chrono sort for submissions query

* feat(fe): add forced redirect for email charts

* chore: update charts supported field for better visual alignment with secret key section

* feat: add marketing prompts for charts

* chore: add copy for 1000 chart limit

* chore: shorten copy

* refactor: create daterangepicker helpers

* refactor: use helpers from daterangepicker

* feat: add no charts prompt

* chore: update language to omit implication of uncertainty

* fix: number typo

* feat: correctly retrieve based on date range

* fix: remove incorrect generic

* fix: remove unnecessary comment

---------

Co-authored-by: Timothee Groleau <timothee.groleau@gmail.com>
Co-authored-by: sebastianwzq <sebastian@open.gov.sg>
Co-authored-by: Ken <ken@open.gov.sg>
Co-authored-by: tshuli <shuli@open.gov.sg>

* fix: omit isVisible property from webhook response (#6907)

fix: omit isVisible property

* fix(markdown): refine regex to handle newlines after indentation groups (#6917)

fix: refine regex to handle newlines after indentation groups

* chore: bump version to v6.91.0

---------

Co-authored-by: Foo Chi Fa <59867455+foochifa@users.noreply.github.com>
Co-authored-by: Timothee Groleau <timothee.groleau@gmail.com>
Co-authored-by: sebastianwzq <sebastian@open.gov.sg>
Co-authored-by: tshuli <shuli@open.gov.sg>
Co-authored-by: wanlingt <56983748+wanlingt@users.noreply.github.com>
  • Loading branch information
6 people committed Nov 21, 2023
1 parent bfefc6d commit d98f05c
Show file tree
Hide file tree
Showing 42 changed files with 1,599 additions and 72 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,24 @@ All notable changes to this project will be documented in this file. Dates are d

Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).

#### [v6.91.0](https://github.com/opengovsg/FormSG/compare/v6.90.0...v6.91.0)

- fix(markdown): refine regex to handle newlines after indentation groups [`#6917`](https://github.com/opengovsg/FormSG/pull/6917)
- fix: omit isVisible property from webhook response [`#6907`](https://github.com/opengovsg/FormSG/pull/6907)
- feat: charts [`#6790`](https://github.com/opengovsg/FormSG/pull/6790)
- build: merge release 6.90.0 to develop [`#6914`](https://github.com/opengovsg/FormSG/pull/6914)
- build: release v6.90.0 [`#6913`](https://github.com/opengovsg/FormSG/pull/6913)

#### [v6.90.0](https://github.com/opengovsg/FormSG/compare/v6.89.2...v6.90.0)

> 20 November 2023

- chore: drop fallback to encrypt mode entirely [`#6912`](https://github.com/opengovsg/FormSG/pull/6912)
- fix(deps): bump type-fest from 4.7.1 to 4.8.1 in /shared [`#6911`](https://github.com/opengovsg/FormSG/pull/6911)
- build: merge Release 6.89.2 into develop [`#6910`](https://github.com/opengovsg/FormSG/pull/6910)
- chore: Revert remove eb shift frontend feature flags (#6869) [`#6909`](https://github.com/opengovsg/FormSG/pull/6909)
- build: merge release v6.89.1 into develop [`#6905`](https://github.com/opengovsg/FormSG/pull/6905)
- chore: bump version to v6.90.0 [`c03692e`](https://github.com/opengovsg/FormSG/commit/c03692e3d9aa64afa8007dffecfd9871542f4759)

#### [v6.89.2](https://github.com/opengovsg/FormSG/compare/v6.89.0...v6.89.2)

Expand Down
393 changes: 391 additions & 2 deletions frontend/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "form-frontend",
"version": "6.90.0",
"version": "6.91.0",
"homepage": ".",
"private": true,
"dependencies": {
Expand All @@ -15,6 +15,7 @@
"@stablelib/base64": "^1.0.1",
"@stripe/react-stripe-js": "^1.15.0",
"@stripe/stripe-js": "^1.44.1",
"@types/stopword": "^2.0.1",
"axios": "^1.6.2",
"broadcast-channel": "^4.13.0",
"browser-image-compression": "^2.0.2",
Expand Down Expand Up @@ -50,6 +51,7 @@
"react-dom": "^17.0.2",
"react-dropzone": "^11.4.2",
"react-focus-lock": "^2.7.1",
"react-google-charts": "^4.0.1",
"react-helmet-async": "^1.2.3",
"react-hook-form": "^7.28.0",
"react-i18next": "^11.16.7",
Expand All @@ -67,11 +69,13 @@
"react-use-scrollspy": "^3.0.2",
"react-virtuoso": "^2.14.0",
"react-waypoint": "^10.1.0",
"react-wordcloud": "^1.2.7",
"remark-breaks": "^3.0.2",
"remark-gfm": "^3.0.1",
"rooks": "^5.11.0",
"simplur": "^3.0.1",
"spark-md5": "^3.0.2",
"stopword": "^2.0.8",
"stripe": "^11.1.0",
"timezone-mock": "^1.3.6",
"type-fest": "^2.8.0",
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/app/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
PAYMENT_PAGE_SUBROUTE,
PRIVACY_POLICY_ROUTE,
PUBLICFORM_ROUTE,
RESULTS_CHARTS_SUBROUTE,
RESULTS_FEEDBACK_SUBROUTE,
TOU_ROUTE,
USE_TEMPLATE_REDIRECT_SUBROUTE,
Expand All @@ -35,6 +36,7 @@ import {
ResponsesLayout,
ResponsesPage,
} from '~features/admin-form/responses'
import { ChartsPage } from '~features/admin-form/responses/ChartsPage/ChartsPage'
import { SettingsPage } from '~features/admin-form/settings/SettingsPage'
import { SelectProfilePage } from '~features/login'
import { FormPaymentPage } from '~features/public-form/components/FormPaymentPage/FormPaymentPage'
Expand Down Expand Up @@ -171,6 +173,9 @@ export const AppRouter = (): JSX.Element => {
path={RESULTS_FEEDBACK_SUBROUTE}
element={<FeedbackPage />}
/>
<Route path={RESULTS_CHARTS_SUBROUTE} element={<ResponsesLayout />}>
<Route index element={<ChartsPage />} />
</Route>
</Route>
</Route>
<Route
Expand Down
33 changes: 33 additions & 0 deletions frontend/src/components/DateRangePicker/helpers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { format, isValid } from 'date-fns'

import { DateString } from '~shared/types'

import { DateRangeValue } from '~components/Calendar'

export const dateStringToDatePickerValue = (range: DateString[]) => {
const [start, end] = range
// Convert to Date objects
const startDate = new Date(start)
const endDate = new Date(end)
const result: (Date | null)[] = [null, null]
// Check if dates are valid
if (isValid(startDate)) {
result[0] = startDate
}
if (isValid(endDate)) {
result[1] = endDate
}
return result as DateRangeValue
}

export const datePickerValueToDateString = (range: DateRangeValue) => {
const [start, end] = range
const result: DateString[] = []
if (start) {
result.push(format(start, 'yyyy-MM-dd') as DateString)
}
if (end) {
result.push(format(end, 'yyyy-MM-dd') as DateString)
}
return result
}
1 change: 1 addition & 0 deletions frontend/src/components/DateRangePicker/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './DateRangePicker'
export * as dateRangePickerHelper from './helpers'
20 changes: 19 additions & 1 deletion frontend/src/components/MarkdownText/MarkdownText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,25 @@ export const MarkdownText = ({
const processedRawString = useMemo(() => {
// Create new line nodes for every new line in raw string so new lines gets rendered.
if (multilineBreaks) {
return children.replace(/\n/gi, '&nbsp; \n')
/**
* Matching new lines that are not preceded by a token that indents.
*
* (?<!{regex}): negative lookbehind to ensure that the following regex does not match
*
* (-|\d+\.|\*): matching character tokens that indents
* -: "-"
* *: "*",
* \d+ : "1.", "2.", etc.
*
* \s: whitespace following the token, indentation groups must start with token followed by a whitespace character
*
* .*: any character, any number of times, this is the actual text content of the line
*
* \n: new line character
*
* \n: the new line character that we will want markdown to render as a new line
*/
return children.replace(/(?<!(-|\d+\.|\*)\s.*\n)\n/gi, '&nbsp; \n')
}
return children
}, [children, multilineBreaks])
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/constants/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const LOCAL_STORAGE_EVENT = 'local-storage'
* Key to store whether a user has seen the rollout announcements before.
*/
export const ROLLOUT_ANNOUNCEMENT_KEY_PREFIX =
'has-seen-rollout-announcement-20231116-'
'has-seen-rollout-announcement-20231121-'

/**
* Key to store whether the admin has seen the feature tour in localStorage.
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ export const ACTIVE_ADMINFORM_BUILDER_ROUTE_REGEX = new RegExp(
/** Responses tab has no subroute, its the index results route. */
export const RESULTS_RESPONSES_SUBROUTE = ''
export const RESULTS_FEEDBACK_SUBROUTE = 'feedback'
export const RESULTS_CHARTS_SUBROUTE = 'charts'

export const ACTIVE_ADMINFORM_RESULTS_ROUTE_REGEX = new RegExp(
`${ADMINFORM_ROUTE}/([a-fA-F0-9]{24})/${ADMINFORM_RESULTS_SUBROUTE}(/${RESULTS_FEEDBACK_SUBROUTE})?/?`,
`${ADMINFORM_ROUTE}/([a-fA-F0-9]{24})/${ADMINFORM_RESULTS_SUBROUTE}(/${RESULTS_FEEDBACK_SUBROUTE}|/${RESULTS_CHARTS_SUBROUTE})?/?`,
'i',
)
export const PAYMENT_PAGE_SUBROUTE = 'payment/:paymentId'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { DateString } from '~shared/types'
import {
FormSubmissionMetadataQueryDto,
StorageModeChartsDto,
StorageModeSubmissionDto,
StorageModeSubmissionMetadataList,
SubmissionCountQueryDto,
Expand Down Expand Up @@ -100,3 +102,61 @@ export const getDecryptedSubmissionById = async ({
responses: processedContent,
}
}

const getAllEncryptedSubmission = async ({
formId,
startDate,
endDate,
}: {
formId: string
startDate?: DateString
endDate?: DateString
}): Promise<StorageModeChartsDto[]> => {
const queryUrl = `${ADMIN_FORM_ENDPOINT}/${formId}/submissions`
if (startDate && endDate) {
return ApiService.get(queryUrl, {
params: {
startDate,
endDate,
},
}).then(({ data }) => data)
}
return ApiService.get(queryUrl).then(({ data }) => data)
}

type DecryptedContent = NonNullable<ReturnType<typeof formsgSdk.crypto.decrypt>>
export type DecryptedSubmission = DecryptedContent & {
submissionTime: string
}

export const getAllDecryptedSubmission = async ({
formId,
secretKey,
startDate,
endDate,
}: {
formId: string
secretKey?: string
startDate?: DateString
endDate?: DateString
}): Promise<DecryptedSubmission[]> => {
if (!secretKey) return []

const allEncryptedData = await getAllEncryptedSubmission({
formId,
startDate,
endDate,
})

return allEncryptedData.map((encryptedData) => {
const decryptedContent = formsgSdk.crypto.decrypt(secretKey, {
encryptedContent: encryptedData.encryptedContent,
verifiedContent: encryptedData.verifiedContent,
version: encryptedData.version,
})

if (!decryptedContent) throw new Error('Could not decrypt the response')

return { ...decryptedContent, submissionTime: encryptedData.created }
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useLocation } from 'react-router-dom'
import { Box, Container, Divider, Stack } from '@chakra-ui/react'

import { FormResponseMode } from '~shared/types/form'

import { ACTIVE_ADMINFORM_RESULTS_ROUTE_REGEX } from '~constants/routes'
import { useToast } from '~hooks/useToast'

import { useAdminForm } from '~features/admin-form/common/queries'

import { SecretKeyVerification } from '../components/SecretKeyVerification'
import { ResponsesPageSkeleton } from '../ResponsesPage/ResponsesPageSkeleton'
import { useStorageResponsesContext } from '../ResponsesPage/storage'

import { ChartsSvgr } from './UnlockedCharts/assets/svgr/ChartsSvgr'
import { ChartsSupportedFieldsInfoBox } from './UnlockedCharts/components/ChartsSupportedFieldsInfoBox'
import { EmptyChartsContainer } from './UnlockedCharts/components/EmptyChartsContainer'
import UnlockedCharts from './UnlockedCharts'

export const ChartsPage = (): JSX.Element => {
const { data: form, isLoading } = useAdminForm()
const { totalResponsesCount, secretKey } = useStorageResponsesContext()
const { pathname } = useLocation()

const toast = useToast({ status: 'danger' })

if (isLoading) return <ResponsesPageSkeleton />

if (!form) {
toast({
description:
'There was an error retrieving your form. Please try again later.',
})
return <ResponsesPageSkeleton />
}

// Charts is not available for Email response
// Since there's no entry to the charts page for Email mode we should
// forcefully redirect the user to the responses page
// we need to redirect to one level up, i.e., '../'
if (form.responseMode === FormResponseMode.Email) {
/**
* 0: "/admin/form/<form_id>/results/charts"
* 1: "<form_id>"
* 2: "/charts"
*/
const match = pathname.match(ACTIVE_ADMINFORM_RESULTS_ROUTE_REGEX)
const subroute = match?.[2]
if (subroute) {
const pathnameWithoutSubroute = pathname.replace(subroute, '')
window.location.replace(pathnameWithoutSubroute)
}
return <></>
}

if (totalResponsesCount === 0) {
return (
<EmptyChartsContainer
title="No charts generated yet."
subtitle="Charts will be generated when you receive responses on your form."
/>
)
}

return secretKey ? (
<UnlockedCharts />
) : (
<>
<SecretKeyVerification
hideResponseCount
heroSvg={<ChartsSvgr />}
ctaText="View charts"
label="Enter or upload Secret Key to view charts"
/>
<Container p={0} maxW="42.5rem">
<Box mt="2rem" mb="0.5rem">
<Divider />
</Box>
<Stack>
<ChartsSupportedFieldsInfoBox />
</Stack>
</Container>
</>
)
}
Loading

0 comments on commit d98f05c

Please sign in to comment.