Skip to content

Commit

Permalink
refactor: encapsulate superset dashboard mutation logic in a hook
Browse files Browse the repository at this point in the history
  • Loading branch information
HendrikThePendric committed Jan 23, 2025
1 parent a55fb13 commit 3b3b8ed
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 139 deletions.
61 changes: 34 additions & 27 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2025-01-22T12:28:04.654Z\n"
"PO-Revision-Date: 2025-01-22T12:28:04.655Z\n"
"POT-Creation-Date: 2025-01-23T11:31:37.781Z\n"
"PO-Revision-Date: 2025-01-23T11:31:37.781Z\n"

msgid "Untitled dashboard"
msgstr "Untitled dashboard"
Expand Down Expand Up @@ -50,26 +50,36 @@ msgstr "Show chart controls on dashboard items"
msgid "Show filters"
msgstr "Show filters"

msgid "Could not update dashboard {{name}}"
msgstr "Could not update dashboard {{name}}"

msgid "Could not delete dashboard {{name}}"
msgstr "Could not delete dashboard {{name}}"
msgid "Delete dashboard"
msgstr "Delete dashboard"

msgid "Edit external dashboard"
msgstr "Edit external dashboard"
msgid ""
"Deleting dashboard \"{{ dashboardName }}\" will remove it for all users. "
"This action cannot be undone. Are you sure you want to permanently delete "
"this dashboard?"
msgstr ""
"Deleting dashboard \"{{ dashboardName }}\" will remove it for all users. "
"This action cannot be undone. Are you sure you want to permanently delete "
"this dashboard?"

msgid "Could not load dashboard details"
msgstr "Could not load dashboard details"
msgid ""
"Note: the source dashboard embedded by this external dashboard will not be "
"deleted."
msgstr ""
"Note: the source dashboard embedded by this external dashboard will not be "
"deleted."

msgid "Update dashboard"
msgstr "Update dashboard"
msgid "Delete"
msgstr "Delete"

msgid "Cancel"
msgstr "Cancel"

msgid "Delete"
msgstr "Delete"
msgid "Edit external dashboard"
msgstr "Edit external dashboard"

msgid "Update dashboard"
msgstr "Update dashboard"

msgid "New dashboard: choose type"
msgstr "New dashboard: choose type"
Expand Down Expand Up @@ -366,6 +376,15 @@ msgstr "Apps"
msgid "Users"
msgstr "Users"

msgid "Could not update dashboard {{name}}"
msgstr "Could not update dashboard {{name}}"

msgid "Could not delete dashboard {{name}}"
msgstr "Could not delete dashboard {{name}}"

msgid "Could not load dashboard details"
msgstr "Could not load dashboard details"

msgid ""
"Failed to save dashboard. You might be offline or not have access to edit "
"this dashboard."
Expand Down Expand Up @@ -414,18 +433,6 @@ msgstr "Exit without saving"
msgid "Go to dashboards"
msgstr "Go to dashboards"

msgid "Delete dashboard"
msgstr "Delete dashboard"

msgid ""
"Deleting dashboard \"{{ dashboardName }}\" will remove it for all users. "
"This action cannot be undone. Are you sure you want to permanently delete "
"this dashboard?"
msgstr ""
"Deleting dashboard \"{{ dashboardName }}\" will remove it for all users. "
"This action cannot be undone. Are you sure you want to permanently delete "
"this dashboard?"

msgid "Discard changes"
msgstr "Discard changes"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useDataMutation, useDataQuery } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import {
Button,
Expand All @@ -11,112 +10,41 @@ import {
} from '@dhis2/ui'
import cx from 'classnames'
import PropTypes from 'prop-types'
import React, { useCallback, useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useHistory } from 'react-router-dom'
import { tFetchDashboards } from '../../actions/dashboards.js'
import {
acClearSelected,
tSetSelectedDashboardById,
} from '../../actions/selected.js'
import { parseSupersetEmbeddedDashboardFieldValues } from '../../modules/parseSupersetEmbeddedDashboardFieldValues.js'
import React, { useEffect } from 'react'
import { useSupersetEmbeddedDashboardFieldsState } from '../../modules/useSupersetEmbeddedDashboardFieldsState.js'
import { sGetSelectedId } from '../../reducers/selected.js'
import { useSupersetEmbeddedDashboardMutation } from '../../modules/useSupersetEmbeddedDashboardMutation.js'
import styles from './styles/SupersetEmbeddedDashboardModal.module.css'
import { SupersetEmbeddedDashboardFields } from './SupersetEmbeddedDashboardFields.js'

const getDashboardQuery = {
dashboard: {
resource: 'dashboards',
id: ({ id }) => id,
params: {
fields: ['name', 'code', 'description', 'access', 'embedded[*]'],
},
},
}
const updateDashboardQuery = {
resource: 'dashboards',
type: 'update',
id: ({ id }) => id,
data: ({ values }) => parseSupersetEmbeddedDashboardFieldValues(values),
params: {
skipTranslation: true,
skipSharing: true,
},
}
const deleteDashboardQuery = {
resource: 'dashboards',
type: 'delete',
id: ({ id }) => id,
}

const parseErrorText = (error) =>
error?.details?.response?.errorReports[0]?.message ??
i18n.t('An unknown error occurred')

export const UpdateSupersetEmbeddedDashboard = ({ closeModal }) => {
const dispatch = useDispatch()
const history = useHistory()
const id = useSelector(sGetSelectedId)
const {
loading: queryLoading,
error: queryError,
data: queryData,
} = useDataQuery(getDashboardQuery, { variables: { id } })
const dashboard = queryData?.dashboard
const name = dashboard?.name
const [mutationLoading, setMutationLoading] = useState(false)
const handeMutationError = useCallback(() => setMutationLoading(false), [])
const [updateDashboard, { error: updateError }] = useDataMutation(
updateDashboardQuery,
{ variables: { id }, onError: handeMutationError }
)
const [deleteDashboard, { error: deleteError }] = useDataMutation(
deleteDashboardQuery,
{ variables: { id }, onError: handeMutationError }
)
const mutationError = updateError || deleteError
const mutationErrorTitle = updateError
? i18n.t('Could not update dashboard {{name}}', { name })
: i18n.t('Could not delete dashboard {{name}}', { name })
const mutationErrorText = parseErrorText(mutationError)
queryLoading,
queryHasError,
queryErrorTitle,
queryErrorMessage,
mutationLoading,
mutationHasError,
mutationErrorTitle,
mutationErrorText,
dashboard,
showDeleteConfirmDialog,
setShowDeleteConfirmDialog,
handleUpdate,
handleDelete,
} = useSupersetEmbeddedDashboardMutation({ closeModal })
const {
hasFieldChanges,
isSupersetEmbedIdValid,
isSupersetEmbedIdFieldTouched,
values,
onChange,
onSupersetEmbedIdFieldBlur,
resetStateWithNewValues,
resetFieldsStateWithNewValues,
} = useSupersetEmbeddedDashboardFieldsState()
const handleSubmit = useCallback(
async (event) => {
event.preventDefault()
setMutationLoading(true)
await updateDashboard({ values })
await dispatch(tSetSelectedDashboardById(id))
setMutationLoading(false)
closeModal()
},
[values, updateDashboard, closeModal, dispatch, id]
)
const handleDelete = useCallback(
async (_, event) => {
event.preventDefault()
setMutationLoading(true)
await deleteDashboard()
dispatch(acClearSelected())
await dispatch(tFetchDashboards())
setMutationLoading(false)
closeModal()
history.push('/')
},
[deleteDashboard, closeModal, dispatch, history]
)

useEffect(() => {
if (dashboard) {
resetStateWithNewValues({
resetFieldsStateWithNewValues({
title: dashboard.name,
code: dashboard.code,
description: dashboard.description,
Expand All @@ -126,31 +54,74 @@ export const UpdateSupersetEmbeddedDashboard = ({ closeModal }) => {
showFilters: dashboard.embedded.options.filters.visible,
})
}
}, [dashboard, resetStateWithNewValues])
}, [dashboard, resetFieldsStateWithNewValues])

if (showDeleteConfirmDialog) {
return (
<Modal>
<ModalTitle>{i18n.t('Delete dashboard')}</ModalTitle>
<ModalContent>
<p className={styles.deleteConfirmPrimaryMessage}>
{i18n.t(
'Deleting dashboard "{{ dashboardName }}" will remove it for all users. This action cannot be undone. Are you sure you want to permanently delete this dashboard?',
{ dashboardName: dashboard.name }
)}
</p>
<p className={styles.deleteConfirmSecondaryMessage}>
{i18n.t(
'Note: the source dashboard embedded by this external dashboard will not be deleted.',
{ nsSeparator: '###' }
)}
</p>
</ModalContent>
<ModalActions>
<div className={styles.buttonStrip}>
<Button
loading={mutationLoading}
destructive
onClick={(_, event) => {
event.preventDefault()
handleDelete()
}}
>
{i18n.t('Delete')}
</Button>
<Button
secondary
onClick={() => setShowDeleteConfirmDialog(false)}
disabled={mutationLoading}
>
{i18n.t('Cancel')}
</Button>
</div>
</ModalActions>
</Modal>
)
}

return (
<Modal>
<form onSubmit={handleSubmit}>
<form
onSubmit={(event) => {
event.preventDefault()
handleUpdate(values)
}}
>
<ModalTitle>
{i18n.t('Edit external dashboard', { nsSeparator: '###' })}
</ModalTitle>
<ModalContent className={styles.modalContent}>
{(queryLoading || !!queryError) && (
{(queryLoading || queryHasError) && (
<div
className={cx(styles.contentOverlay, {
[styles.loading]: queryLoading,
[styles.error]: queryError,
[styles.error]: queryHasError,
})}
>
{queryLoading && <CircularLoader />}
{queryError && (
<NoticeBox
error
title={i18n.t(
'Could not load dashboard details'
)}
>
{parseErrorText(queryError)}
{queryHasError && (
<NoticeBox error title={queryErrorTitle}>
{queryErrorMessage}
</NoticeBox>
)}
</div>
Expand All @@ -165,15 +136,15 @@ export const UpdateSupersetEmbeddedDashboard = ({ closeModal }) => {
onSupersetEmbedIdFieldBlur={onSupersetEmbedIdFieldBlur}
submitting={mutationLoading || queryLoading}
/>
{mutationError && (
{mutationHasError && (
<NoticeBox error title={mutationErrorTitle}>
{mutationErrorText}
</NoticeBox>
)}
</ModalContent>
<ModalActions>
<div className={styles.buttonStrip}>
{!queryError && (
{!queryHasError && (
<Button
loading={mutationLoading}
type="submit"
Expand All @@ -189,19 +160,19 @@ export const UpdateSupersetEmbeddedDashboard = ({ closeModal }) => {
)}
<Button
disabled={mutationLoading}
secondary={!queryError}
primary={!!queryError}
secondary={!queryHasError}
primary={queryHasError}
onClick={closeModal}
type={queryError ? 'submit' : undefined}
type={queryHasError ? 'submit' : undefined}
>
{i18n.t('Cancel')}
</Button>
{!queryError && dashboard?.access?.delete && (
{!queryHasError && dashboard?.access?.delete && (
<Button
destructive
disabled={mutationLoading || queryLoading}
disabled={queryLoading || mutationLoading}
secondary
onClick={handleDelete}
onClick={() => setShowDeleteConfirmDialog(true)}
className={styles.deleteButton}
>
{i18n.t('Delete')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@
.deleteButton {
margin-inline-end: auto;
}
.deleteConfirmPrimaryMessage,
.deleteConfirmSecondaryMessage {
margin-block-start: 0;
}
.deleteConfirmSecondaryMessage {
margin-block-end: 0;
color: var(--colors-grey600);
}
4 changes: 2 additions & 2 deletions src/modules/useSupersetEmbeddedDashboardFieldsState.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const useSupersetEmbeddedDashboardFieldsState = (
const onSupersetEmbedIdFieldBlur = useCallback((payload) => {
dispatch({ type: SUPERSET_FIELD_BLUR, payload })
}, [])
const resetStateWithNewValues = useCallback((payload) => {
const resetFieldsStateWithNewValues = useCallback((payload) => {
dispatch({ type: RESET_FIELD_STATE, payload })
}, [])

Expand All @@ -89,6 +89,6 @@ export const useSupersetEmbeddedDashboardFieldsState = (
onSupersetEmbedIdFieldBlur: state.isSupersetEmbedIdFieldTouched
? undefined
: onSupersetEmbedIdFieldBlur,
resetStateWithNewValues,
resetFieldsStateWithNewValues,
}
}
Loading

0 comments on commit 3b3b8ed

Please sign in to comment.