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

Many fixes and improvements on Jobs/Overview #7034

Merged
merged 8 commits into from
Sep 15, 2023
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

- [Backup/Restore] Fix `Cannot read properties of undefined (reading 'id')` error when restoring via an XO Proxy (PR [#7026](https://github.com/vatesfr/xen-orchestra/pull/7026))
- [Google/GitHub Auth] Fix `Internal Server Error` (xo-server: `Cannot read properties of undefined (reading 'id')`) when logging in with Google or GitHub [Forum#7729](https://xcp-ng.org/forum/topic/7729) (PRs [#7031](https://github.com/vatesfr/xen-orchestra/pull/7031) [#7032](https://github.com/vatesfr/xen-orchestra/pull/7032))
- [Jobs] Fix schedules not being displayed on first load [#6968](https://github.com/vatesfr/xen-orchestra/issues/6968) (PR [#7034](https://github.com/vatesfr/xen-orchestra/pull/7034))

### Packages to release

Expand Down
21 changes: 16 additions & 5 deletions packages/xo-web/src/common/add-subscriptions.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import React from 'react'
import { map, mapValues, noop } from 'lodash'
import { forOwn, map } from 'lodash'

const call = fn => fn()

// `subscriptions` can be a function if we want to ensure that the subscription
// callbacks have been correctly initialized when there are circular dependencies
//
// each subscription can be either a `subscribe` function or a `[subscribe, initialValue]` array
const addSubscriptions = subscriptions => Component =>
class SubscriptionWrapper extends React.PureComponent {
constructor(props) {
super(props)

// provide all props since the beginning (better behavior with Freactal)
this.state = mapValues(
(this._subscribes = typeof subscriptions === 'function' ? subscriptions(props) : subscriptions),
noop
)
const state = {}
const subscribes = {}
forOwn(typeof subscriptions === 'function' ? subscriptions(props) : subscriptions, (subscription, prop) => {
if (typeof subscription === 'function') {
subscribes[prop] = subscription
state[prop] = undefined
} else {
subscribes[prop] = subscription[0]
state[prop] = subscription[1]
}
})
this.state = state
this._subscribes = subscribes
}

_unsubscribes = undefined
Expand Down
69 changes: 20 additions & 49 deletions packages/xo-web/src/xo-app/jobs/overview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { addSubscriptions } from 'utils'
import { Container } from 'grid'
import { createSelector } from 'selectors'
import { Card, CardHeader, CardBlock } from 'card'
import { filter, find, forEach, orderBy } from 'lodash'
import { filter, forEach, keyBy } from 'lodash'
import {
deleteSchedule,
deleteSchedules,
Expand Down Expand Up @@ -100,63 +100,36 @@ const ACTIONS = [
// ===================================================================

@addSubscriptions({
users: subscribeUsers,
jobs: [cb => subscribeJobs(jobs => cb(keyBy(jobs, 'id'))), {}],
schedules: [subscribeSchedules, []],
userIds: [cb => subscribeUsers(users => cb(new Set(users.map(_ => _.id)))), new Set()],
})
export default class Overview extends Component {
static contextTypes = {
router: PropTypes.object,
}

constructor(props) {
super(props)
this.state = {
schedules: [],
}
}

componentWillMount() {
const unsubscribeJobs = subscribeJobs(jobs => {
const obj = {}
forEach(jobs, job => {
obj[job.id] = job
})

this.setState({
jobs: obj,
})
})

const unsubscribeSchedules = subscribeSchedules(schedules => {
// Get only backup jobs.
schedules = filter(schedules, schedule => {
const job = this._getScheduleJob(schedule)
return job && jobKeyToLabel[job.key]
})
_getGenericSchedules = createSelector(
() => this.props.schedules,
() => this.props.jobs,

this.setState({
schedules: orderBy(schedules, schedule => +schedule.id.split(':')[1], ['desc']),
// Get only generic jobs
(schedules, jobs) =>
filter(schedules, schedule => {
const job = jobs[schedule.jobId]
return job !== undefined && job.key in jobKeyToLabel
})
})

this.componentWillUnmount = () => {
unsubscribeJobs()
unsubscribeSchedules()
}
}

_getScheduleJob(schedule) {
const { jobs } = this.state || {}
return jobs[schedule.jobId]
}
)

_getIsScheduleUserMissing = createSelector(
() => this.state.schedules,
() => this.props.users,
(schedules, users) => {
this._getGenericSchedules,
() => this.props.userIds,
() => this.props.jobs,
(schedules, userIds, jobs) => {
const isScheduleUserMissing = {}

forEach(schedules, schedule => {
isScheduleUserMissing[schedule.id] = !find(users, user => user.id === this._getScheduleJob(schedule).userId)
isScheduleUserMissing[schedule.id] = !userIds.has(jobs[schedule.jobId].userId)
})

return isScheduleUserMissing
Expand All @@ -183,8 +156,6 @@ export default class Overview extends Component {
]

render() {
const { schedules } = this.state

return process.env.XOA_PLAN > 3 ? (
<Container>
<Card>
Expand All @@ -194,10 +165,10 @@ export default class Overview extends Component {
<CardBlock>
<SortedTable
actions={ACTIONS}
collection={schedules}
collection={this._getGenericSchedules()}
columns={SCHEDULES_COLUMNS}
data-isScheduleUserMissing={this._getIsScheduleUserMissing()}
data-jobs={this.state.jobs || {}}
data-jobs={this.props.jobs}
individualActions={this._individualActions}
shortcutsTarget='body'
stateUrlParam='s'
Expand Down