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

Added Gantt chart #1466

Merged
merged 48 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
abf06c4
Added Gantt chart
ChrisPaulBennett Dec 19, 2023
9968cf5
Update src/components/cylc/gantt/GanttChart.vue
ChrisPaulBennett Dec 20, 2023
fa93e9c
Update src/components/cylc/gantt/GanttChart.vue
ChrisPaulBennett Dec 20, 2023
5fd93de
Update src/components/cylc/gantt/GanttChart.vue
ChrisPaulBennett Dec 20, 2023
2713906
Update src/components/cylc/gantt/GanttChart.vue
ChrisPaulBennett Dec 20, 2023
ecd3e25
Update src/components/cylc/gantt/GanttChart.vue
ChrisPaulBennett Dec 20, 2023
6ca6793
Update src/components/cylc/gantt/GanttChart.vue
ChrisPaulBennett Dec 20, 2023
ccafeab
Updated unit tests and removed unused code
ChrisPaulBennett Jan 4, 2024
2de40c8
altered unit test to use different values for different browsers
ChrisPaulBennett Jan 4, 2024
79a3f36
Changed naming from job name to task name
ChrisPaulBennett Jan 5, 2024
7953017
Added gantt chart to vue router.
ChrisPaulBennett Jan 24, 2024
81098ef
Added unit tests
ChrisPaulBennett Jan 25, 2024
2deb507
Added pagination based to number of tasks per page
ChrisPaulBennett Jan 30, 2024
7720012
Fixed unit tests
ChrisPaulBennett Feb 1, 2024
77c5217
Fixed the other unit test
ChrisPaulBennett Feb 1, 2024
e708002
Added task filter
ChrisPaulBennett Feb 9, 2024
d508c8b
Added unit test for new "matchTasks" function
ChrisPaulBennett Feb 9, 2024
e14148e
Added pagination tests
ChrisPaulBennett Feb 12, 2024
bba29de
Add unit tests for gantt view
ChrisPaulBennett Feb 13, 2024
0fb59e2
Added more unit tests
ChrisPaulBennett Feb 13, 2024
880b5b5
More unit tests
ChrisPaulBennett Feb 13, 2024
00053cf
regen lock file
ChrisPaulBennett Feb 13, 2024
2bcd532
removed repeated code
ChrisPaulBennett Feb 13, 2024
6858b90
Removed redundant code
ChrisPaulBennett Feb 14, 2024
4049b31
Code review changes
ChrisPaulBennett Feb 16, 2024
6b18771
Code review changes
ChrisPaulBennett Feb 16, 2024
237e75a
Skeleton loader for Gantt view
MetRonnie Feb 16, 2024
2416afd
Gantt view: ensure respects reduced animation
MetRonnie Feb 16, 2024
2e67781
Merge pull request #3 from MetRonnie/gantt-skeleton
ChrisPaulBennett Feb 19, 2024
5ea8682
Merge branch 'master' into gantt-chart-view
ChrisPaulBennett Feb 19, 2024
450fe9f
Update tests/unit/views/gantt.vue.spec.js
ChrisPaulBennett Feb 19, 2024
850ddf1
Removed orphaned code
ChrisPaulBennett Feb 19, 2024
8c0842c
Update src/components/cylc/gantt/GanttChart.vue
ChrisPaulBennett Feb 19, 2024
6d6d005
Code review changes
ChrisPaulBennett Feb 20, 2024
9c42b65
Merge branch 'gantt-chart-view' of github.com:ChrisPaulBennett/cylc-u…
ChrisPaulBennett Feb 20, 2024
cf65a3a
Gantt view entry in CHANGES.md
ChrisPaulBennett Feb 20, 2024
81bbd1a
Removed redundant code. Fixed platform option filtering
ChrisPaulBennett Feb 27, 2024
48ff0a2
Merge branch 'master' into gantt-chart-view
ChrisPaulBennett Feb 27, 2024
a41838a
Added gantt view to list
ChrisPaulBennett Feb 27, 2024
182f30a
Updated unit tests
ChrisPaulBennett Feb 27, 2024
f7d3330
commiting terrible tests
ChrisPaulBennett Feb 29, 2024
20a869e
Tidy & fix tests
MetRonnie Feb 29, 2024
199d65e
Update unit tests
ChrisPaulBennett Feb 29, 2024
417ae97
Update src/views/Gantt.vue
ChrisPaulBennett Mar 1, 2024
3f71cd7
Update src/components/cylc/gantt/filter.js
ChrisPaulBennett Mar 1, 2024
4ead7ca
Added unit tests for filtering
ChrisPaulBennett Mar 1, 2024
6acffab
Gantt view: fix filtering (#4)
MetRonnie Mar 5, 2024
0935c6e
Update src/components/cylc/gantt/GanttChart.vue
ChrisPaulBennett Mar 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ Various other efficiency improvements.
Fix bug where the up/down/home/end keys would not work inside the Edit Runtime
form inputs.

[#1466](https://github.com/cylc/cylc-ui/pull/1466) -
Gantt view added.

-------------------------------------------------------------------------------
## __cylc-ui-2.3.0 (<span actions:bind='release-date'>Released 2023-11-28</span>)__

Expand Down
94 changes: 94 additions & 0 deletions cypress/component/ganttchart.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Copyright (C) NIWA & British Crown (Met Office) & Contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { createStore } from 'vuex'
import { createVuetify } from 'vuetify'
import { merge } from 'lodash'
import storeOptions from '@/store/options'
import { vuetifyOptions } from '@/plugins/vuetify'
import GanttChart from '@/components/cylc/gantt/GanttChart.vue'

const jobs = {
test_job: [{
name: 'test_job',
id: '~cbennett/analysis_view_test/run1//1/a/01',
submittedTime: '2023-02-23T11:10:09Z',
startedTime: '2023-02-23T11:10:13Z',
finishedTime: '2023-02-23T11:10:20Z',
platform: 'localhost'
}],
yet_another_test_job: [{
name: 'yet_another_test_job',
id: '~cbennett/analysis_view_test/run1//1/b/01',
submittedTime: '2023-02-23T11:10:21Z',
startedTime: '2023-02-23T11:10:24Z',
finishedTime: '2023-02-23T11:10:26Z',
platform: 'localhost'
}]
}

const mountOpts = {
global: {
plugins: [
createStore(storeOptions),
createVuetify(vuetifyOptions),
],
},
props: {
timingOption: 'total',
animate: false
}
}
describe('GanttChart correctly', () => {
it('renders', () => {
// see: https://on.cypress.io/mounting-vue
cy.mount(GanttChart, merge(mountOpts, {
props: {
jobs
},
}))
cy.get('.vue-apexcharts')
.should('be.visible')
.contains('test_job')
.get('.vue-apexcharts')
.contains('yet_another_test_job')
cy.get('.apexcharts-rangebar-area')
.first()
.click({ force: true })
cy.get('.apexcharts-tooltip-candlestick')
.should('exist')
.should('be.visible')
cy.get('[data-test="v-pagination-item"]')
.should('have.length', 1)
})
it('paginates correctly', () => {
// see: https://on.cypress.io/mounting-vue
cy.mount(GanttChart, merge(mountOpts, {
props: {
tasksPerPage: 1,
jobs
},
}))
cy.get('[data-test="v-pagination-item"]')
.should('have.length', 2)
cy.get('.vue-apexcharts')
.should('be.visible')
.contains('test_job')
cy.contains('yet_another_test_job')
.should('not.exist')
})
})
218 changes: 218 additions & 0 deletions src/components/cylc/gantt/GanttChart.vue
ChrisPaulBennett marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
<!--
Copyright (C) NIWA & British Crown (Met Office) & Contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->

<template>
<VueApexCharts
type="rangeBar"
:options="chartOptions"
:series="series"
width="100%"
height="auto"
class="d-flex justify-center"
/>
<v-pagination
v-model="page"
:length="numPages"
:total-visible="7"
density="comfortable"
/>
</template>

<script>
import VueApexCharts from 'vue3-apexcharts'
import {
mdiDownload,
} from '@mdi/js'
import { useReducedAnimation } from '@/composables/localStorage'

export default {
name: 'GanttChart',

watch: {
tasksPerPage: function () {
this.page = 1

Check warning on line 47 in src/components/cylc/gantt/GanttChart.vue

View check run for this annotation

Codecov / codecov/patch

src/components/cylc/gantt/GanttChart.vue#L47

Added line #L47 was not covered by tests
},
},
components: {
VueApexCharts,
},

props: {
jobs: {
type: Array,
required: true,
},
ChrisPaulBennett marked this conversation as resolved.
Show resolved Hide resolved
timingOption: {
type: String,
required: true,
default: 'total',
},
tasksPerPage: {
type: Number,
default: 10,
},
animate: {
type: Boolean,
default: true,
},
},

setup () {
const reducedAnimation = useReducedAnimation()
return { reducedAnimation }
},

data () {
return {
page: 1,
sortBy: 'name',
sortDesc: false,
}
},

methods: {
/**
* @param {string|number} a
* @param {string|number} b
* @returns {number}
*/
compare (a, b) {
const ret = a[this.sortBy] < b[this.sortBy] ? -1 : 1
return this.sortDesc ? -ret : ret
},
},

computed: {
series () {
const data = []
if (this.jobs.length !== 0) {
const taskNameList = Object.keys(this.jobs)
const startTask = Math.max(0, this.tasksPerPage * (this.page - 1))
const endTask = Math.min(taskNameList.length, startTask + this.tasksPerPage)

const colours = [
'#008FFB',
'#00E396',
'#775DD0',
'#FEB019',
'#FF4560',
]
const taskColours = {}

for (let i = 0; i < taskNameList.length; i++) {
taskColours[taskNameList[i]] = colours[i % colours.length]
}
const timingOptions = {
total: { start: 'submittedTime', end: 'finishedTime' },
run: { start: 'startedTime', end: 'finishedTime' },
queue: { start: 'submittedTime', end: 'startedTime' },
}
const { start, end } = timingOptions[this.timingOption]
for (let i = startTask; i < endTask; i++) {
for (let j = 0; j < this.jobs[taskNameList[i]].length; j++) {
data.push({
x: taskNameList[i],
y: [
new Date(this.jobs[taskNameList[i]][j][start]).getTime(),
new Date(this.jobs[taskNameList[i]][j][end]).getTime()
],
fillColor: taskColours[taskNameList[i]],
})
}
}
}
return [{ data }]
},
numPages () {
if (this.jobs.length !== 0) {
return Math.ceil(Object.keys(this.jobs).length / this.tasksPerPage)
} else {
return 1

Check warning on line 144 in src/components/cylc/gantt/GanttChart.vue

View check run for this annotation

Codecov / codecov/patch

src/components/cylc/gantt/GanttChart.vue#L144

Added line #L144 was not covered by tests
}
},

chartOptions () {
return {
chart: {
animations: {
enabled: this.animate && !this.reducedAnimation,
easing: 'easeinout',
speed: 300,
animateGradually: {
enabled: true,
delay: 150,
},
dynamicAnimation: {
enabled: true,
speed: 350,
},
},
fontFamily: 'inherit',
toolbar: {
tools: {
download: `<svg class="w-100 h-100"><path d="${mdiDownload}"></path></svg>`,
},
},
},
tooltip: {
custom: function ({ seriesIndex, dataPointIndex, w }) {
const startTime = new Date(w.config.series[seriesIndex].data[dataPointIndex].y[0])
const finishTime = new Date(w.config.series[seriesIndex].data[dataPointIndex].y[1])
return (
'<div class="apexcharts-tooltip-candlestick">' +
'<div>Start: <span class="value">' +
startTime +
'</span></div>' +
'<div>Finish: <span class="value">' +
finishTime +
'</span></div>' +
'</div>'
)
},
},
plotOptions: {
bar: {
horizontal: true,
},
},
xaxis: {
labels: {
formatter: function (value, timestamp, opts) {
return new Date(value).toTimeString().slice(0, 9)
}
},
title: {
text: 'Time',
},
},
yaxis: {
labels: {
maxWidth: 280,
offsetX: -10,
},
},
}
},
},
}
</script>

<style scoped>
:deep(.apexcharts-text) {
font-size: 0.9rem;
}
</style>
61 changes: 61 additions & 0 deletions src/components/cylc/gantt/filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright (C) NIWA & British Crown (Met Office) & Contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/**
* Function to determine if a task should be displayed given a certain filter
* Checks the name includes a search string and if the platform is equal to
* that chosen
*
* @export
* @param {Record<string,Object[]>} tasks - Object containing tasks to evaluate
* @param {{ name: string, platformOption: string|-1 }} tasksFilter - The filter to apply to the tasks
* @return {Record<string,Object[]>} An Object with the tasks that made it through the filter
*/
export function matchTasks (tasks, tasksFilter) {
const { name, platformOption } = tasksFilter
return Object.fromEntries(Object.entries(tasks).filter(
([taskName, value]) => (
name.includes(taskName) && (
platformOption === -1 || platformOption === value[0].platform
)
)
))
}

/**
* Function to find the unique platforms in an object of tasks
*
* @export
* @param {object} tasks - The tasks to search for unique platforms
* @return {array} - An array of unique platform objects
*/
export function platformOptions (tasks) {
const platformOptions = [{ value: -1, title: 'All' }]
const platforms = []
for (const jobs of Object.values(tasks)) {
for (let i = 0; i < jobs.length; i++) {
if (!platforms.includes(jobs[i].platform)) {
platforms.push(jobs[i].platform)
platformOptions.push({
value: jobs[i].platform,
title: jobs[i].platform
})
}
}
}
return platformOptions
}
Loading