Skip to content

Commit

Permalink
fix(system-jobs): enforce system job permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
ismay committed Feb 22, 2021
1 parent 0eff8c2 commit 1bae8c7
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 9 deletions.
10 changes: 8 additions & 2 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: 2021-02-11T14:30:21.883Z\n"
"PO-Revision-Date: 2021-02-11T14:30:21.883Z\n"
"POT-Creation-Date: 2021-02-16T11:45:54.474Z\n"
"PO-Revision-Date: 2021-02-16T11:45:54.474Z\n"

msgid "Not authorized"
msgstr ""
Expand Down Expand Up @@ -103,6 +103,9 @@ msgstr ""
msgid "{{ delay }} seconds after last run"
msgstr ""

msgid "View"
msgstr ""

msgid "Every hour"
msgstr ""

Expand Down Expand Up @@ -166,6 +169,9 @@ msgstr ""
msgid "New job"
msgstr ""

msgid "System job: {{ name }}"
msgstr ""

msgid "Data value"
msgstr ""

Expand Down
11 changes: 8 additions & 3 deletions src/components/JobTable/Actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { PropTypes } from '@dhis2/prop-types'
import { FlyoutMenu, DropdownButton } from '@dhis2/ui'
import i18n from '@dhis2/d2-i18n'
import EditJobAction from './EditJobAction'
import ViewJobAction from './ViewJobAction'
import RunJobAction from './RunJobAction'
import DeleteJobAction from './DeleteJobAction'

Expand All @@ -11,9 +12,13 @@ const Actions = ({ id, configurable }) => (
small
component={
<FlyoutMenu>
{configurable && <EditJobAction id={id} />}
<RunJobAction id={id} />
<DeleteJobAction id={id} />
{configurable ? (
<EditJobAction id={id} />
) : (
<ViewJobAction id={id} />
)}
{configurable && <RunJobAction id={id} />}
{configurable && <DeleteJobAction id={id} />}
</FlyoutMenu>
}
>
Expand Down
6 changes: 5 additions & 1 deletion src/components/JobTable/JobTableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ const JobTableRow = ({
<Status status={jobStatus} />
</TableCell>
<TableCell>
<ToggleJobSwitch id={id} checked={enabled} />
<ToggleJobSwitch
id={id}
checked={enabled}
disabled={!configurable}
/>
</TableCell>
<TableCell>
<Actions id={id} configurable={configurable} />
Expand Down
21 changes: 21 additions & 0 deletions src/components/JobTable/ViewJobAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'
import { PropTypes } from '@dhis2/prop-types'
import { MenuItem } from '@dhis2/ui'
import i18n from '@dhis2/d2-i18n'
import history from '../../services/history'

const ViewJobAction = ({ id }) => (
<MenuItem
dense
onClick={() => history.push(`/view/${id}`)}
label={i18n.t('View')}
/>
)

const { string } = PropTypes

ViewJobAction.propTypes = {
id: string.isRequired,
}

export default ViewJobAction
22 changes: 22 additions & 0 deletions src/components/JobTable/ViewJobAction.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react'
import { shallow, mount } from 'enzyme'
import history from '../../services/history'
import ViewJobAction from './ViewJobAction'

jest.mock('../../services/history', () => ({
push: jest.fn(),
}))

describe('<ViewJobAction>', () => {
it('renders without errors', () => {
shallow(<ViewJobAction id="id" />)
})

it('calls history.push correctly when MenuItem is clicked', () => {
const wrapper = mount(<ViewJobAction id="id" />)

wrapper.find('a').simulate('click')

expect(history.push).toHaveBeenCalledWith('/view/id')
})
})
2 changes: 2 additions & 0 deletions src/components/Routes/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { Route } from 'react-router-dom'
import { Router } from 'react-router'
import { JobListContainer } from '../../pages/JobList'
import { JobEditContainer } from '../../pages/JobEdit'
import { JobView } from '../../pages/JobView'
import { JobAddContainer } from '../../pages/JobAdd'
import history from '../../services/history'

const Routes = () => (
<Router history={history}>
<Route exact path="/" component={JobListContainer} />
<Route path="/edit/:id" component={JobEditContainer} />
<Route path="/view/:id" component={JobView} />
<Route path="/add" component={JobAddContainer} />
</Router>
)
Expand Down
5 changes: 3 additions & 2 deletions src/components/Switches/ToggleJobSwitch.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const mutation = {
data: ({ enabled }) => ({ enabled }),
}

const ToggleJobSwitch = ({ id, checked }) => {
const ToggleJobSwitch = ({ id, checked, disabled }) => {
const [toggleJob, { loading }] = useDataMutation(mutation)
const store = useContext(StoreContext)
const refetchJobs = selectors.getRefetchJobs(store)
Expand All @@ -22,7 +22,7 @@ const ToggleJobSwitch = ({ id, checked }) => {
return (
<Switch
name={`toggle-job-${id}`}
disabled={loading}
disabled={disabled || loading}
checked={checked}
onChange={() => {
toggleJob({ id, enabled }).then(() => refetchJobs())
Expand All @@ -35,6 +35,7 @@ const { bool, string } = PropTypes

ToggleJobSwitch.propTypes = {
checked: bool.isRequired,
disabled: bool.isRequired,
id: string.isRequired,
}

Expand Down
3 changes: 2 additions & 1 deletion src/components/Switches/ToggleJobSwitch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('<ToggleJobSwitch>', () => {
it('renders without errors', () => {
useDataMutation.mockImplementation(() => [() => {}, {}])

shallow(<ToggleJobSwitch id="1" checked={true} />)
shallow(<ToggleJobSwitch id="1" checked={true} disabled={false} />)
})

it('calls toggleJob and refetches when toggle is clicked', async () => {
Expand All @@ -27,6 +27,7 @@ describe('<ToggleJobSwitch>', () => {
const props = {
id: 'id',
checked,
disabled: false,
}

useDataMutation.mockImplementation(() => [toggleJobSpy, {}])
Expand Down
104 changes: 104 additions & 0 deletions src/pages/JobView/JobView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { useContext } from 'react'
import { useParams } from 'react-router-dom'
import {
Card,
IconInfo16,
Box,
SingleSelectField,
SingleSelectOption,
InputField,
} from '@dhis2/ui'
import i18n from '@dhis2/d2-i18n'
import { StoreContext, selectors } from '../../components/Store'
import { DiscardFormButton } from '../../components/Buttons'
import { JobDetails } from '../../components/JobDetails'
import translateCron from '../../services/translate-cron'
import { jobTypesMap } from '../../services/server-translations'
import styles from './JobView.module.css'

const infoLink =
'https://docs.dhis2.org/master/en/user/html/dataAdmin_scheduling.html#dataAdmin_scheduling_config'

const JobView = () => {
const store = useContext(StoreContext)
const { id } = useParams()
const {
name,
created,
lastExecutedStatus,
lastExecuted,
jobType,
cronExpression,
} = selectors.getJobById(store, id)

return (
<React.Fragment>
<header className={styles.pageHeader}>
<DiscardFormButton className={styles.pageHeaderButton} small>
{i18n.t('Back to all jobs')}
</DiscardFormButton>
<h2 className={styles.pageHeaderTitle}>
{i18n.t('System job: {{ name }}', {
name,
nsSeparator: '>',
})}
</h2>
</header>
<Card className={styles.card}>
<header className={styles.cardHeader}>
<h3 className={styles.cardHeaderTitle}>
{i18n.t('Configuration')}
</h3>
<a
href={infoLink}
className={styles.cardHeaderLink}
target="_blank"
rel="noopener noreferrer"
>
<span className={styles.cardHeaderInfo}>
<IconInfo16 />
</span>
{i18n.t('About job configuration')}
</a>
</header>
<div className={styles.jobDetails}>
<JobDetails
created={created}
lastExecutedStatus={lastExecutedStatus}
lastExecuted={lastExecuted}
/>
</div>
<Box maxWidth="600px">
<InputField label={i18n.t('Name')} disabled value={name} />
</Box>
<Box marginTop="16px" maxWidth="400px">
<SingleSelectField
label={i18n.t('Job type')}
disabled
selected={jobType}
>
<SingleSelectOption
value={jobType}
label={jobTypesMap[jobType]}
/>
</SingleSelectField>
</Box>
<Box marginTop="16px" maxWidth="400px">
<InputField
label={i18n.t('CRON Expression')}
disabled
value={cronExpression}
helpText={translateCron(cronExpression)}
/>
</Box>
<Box marginTop="24px">
<DiscardFormButton>
{i18n.t('Back to all jobs')}
</DiscardFormButton>
</Box>
</Card>
</React.Fragment>
)
}

export default JobView
46 changes: 46 additions & 0 deletions src/pages/JobView/JobView.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.pageHeader {
margin-bottom: var(--spacers-dp32);
}

.pageHeaderButton {
margin-bottom: var(--spacers-dp16);
}

.pageHeaderTitle {
font-size: 18px;
font-weight: 400;
line-height: 1;
margin: 0;
}

.card {
padding: var(--spacers-dp16);
}

.cardHeader {
align-items: center;
display: inline-flex;
margin-bottom: var(--spacers-dp16);
}

.cardHeaderTitle {
font-size: 16px;
font-weight: 500;
margin: 0 var(--spacers-dp8) 0 0;
}

.cardHeaderInfo {
margin-right: var(--spacers-dp4);
}

.cardHeaderLink {
align-items: center;
display: flex;
font-size: 12px;
color: var(--colors-grey600);
fill: var(--colors-grey600);
}

.jobDetails {
float: right;
}
40 changes: 40 additions & 0 deletions src/pages/JobView/JobView.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react'
import { useParams } from 'react-router-dom'
import { shallow } from 'enzyme'
import { StoreContext } from '../../components/Store'
import JobView from './JobView'

jest.mock('react-router-dom', () => ({
useParams: jest.fn(),
}))

afterEach(() => {
jest.resetAllMocks()
})

describe('<JobView>', () => {
it('renders without errors', () => {
const id = 'one'
const store = {
jobs: [
{
id,
jobType: 'DATA_SET_NOTIFICATION',
cronExpression: '0 0 2 * * ?',
name: 'name',
created: 'now',
lastExecutedStatus: 'COMPLETED',
lastExecuted: 'now',
},
],
}

useParams.mockImplementation(() => id)

shallow(
<StoreContext.Provider value={store}>
<JobView />
</StoreContext.Provider>
)
})
})
3 changes: 3 additions & 0 deletions src/pages/JobView/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import JobView from './JobView'

export { JobView }

0 comments on commit 1bae8c7

Please sign in to comment.