Skip to content

Commit

Permalink
Merge pull request #1886 from oliver-sanders/metadata-view-template
Browse files Browse the repository at this point in the history
metadata view: introduce a minimal task metadata view
  • Loading branch information
wxtim authored Dec 16, 2024
2 parents 8a07978 + 7a451e6 commit 0db3d62
Show file tree
Hide file tree
Showing 13 changed files with 1,123 additions and 14 deletions.
1 change: 1 addition & 0 deletions changes.d/1886.feat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added an info view to display task information including metadata, prerequisites and outputs.
282 changes: 282 additions & 0 deletions cypress/component/info.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
/**
* 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 InfoComponent from '@/components/cylc/Info.vue'
import { Tokens } from '@/utils/uid'

const DESCRIPTION = `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi.
`
const TOKENS = new Tokens('~user/workflow//1234/foo')
const TASK = {
id: TOKENS.id,
name: TOKENS.task,
tokens: TOKENS,
node: {
state: 'running',
task: {
meta: {
title: 'My Foo',
description: DESCRIPTION,
URL: 'https://cylc.org',
customMeta: {
answer: '42',
question: 'mutually exclusive',
}
}
},
prerequisites: [
{
satisfied: false,
expression: '(c0 & c1) | c2',
conditions: [
{
taskId: 'a',
message: 'succeeded',
reqState: 'succeeded',
exprAlias: 'c0',
satisfied: true,
},
{
taskId: 'b',
message: 'custom message',
reqState: 'custom_output',
exprAlias: 'c1',
satisfied: false,
},
{
taskId: 'a',
message: 'expired',
reqState: 'expired',
exprAlias: 'c2',
satisfied: false,
},
],
},
{
satisfied: true,
expression: 'c0',
conditions: [
{
taskId: 'x',
message: 'succeeded',
reqState: 'succeeded',
exprAlias: 'c0',
satisfied: true,
},
],
},
],
outputs: [
{
label: 'started',
message: 'started',
satisfied: true,
},
{
label: 'succeeded',
message: 'succeeded',
satisfied: false,
},
{
label: 'failed',
message: 'failed',
satisfied: false,
},
{
label: 'x',
message: 'xxx',
satisfied: true,
}
],
runtime: {
completion: '(succeeded and x) or failed'
}
},
children: [
{
id: TOKENS.clone({ job: '01' }).id,
tokens: TOKENS.clone({ job: '01' }),
name: '01',
node: {
state: 'failed'
}
},
{
id: TOKENS.clone({ job: '02' }).id,
tokens: TOKENS.clone({ job: '02' }),
name: '02',
node: {
state: 'succeeded'
}
},
],
}

describe('Info component', () => {
it('displays task information', () => {
cy.vmount(InfoComponent, {
props: {
task: TASK,
class: 'job_theme--default',
// NOTE: expand all sections by default
panelExpansion: [0, 1, 2, 3],
}
})

// there should be a task icon (running)
cy.get('.c-graph-node .c8-task.running').should('be.visible')

// and two job icons (succeeded & failed)
cy.get('.c-graph-node .c-job').should('have.length', 2)
.get('.c-graph-node .c-job .failed').should('be.visible')
.get('.c-graph-node .c-job .succeeded').should('be.visible')

// the metadata panel
cy.get('.metadata-panel.v-expansion-panel--active').should('be.visible')
.contains('My Foo')
.get('.metadata-panel') // the description should be displayed
.contains(/Lorem ipsum dolor sit amet.*/)
.get('.metadata-panel a:first') // the URL should be an anchor
.should('have.attr', 'href', 'https://cylc.org')
.contains(/^https:\/\/cylc.org$/)

// the prerequisites panel
cy.get('.prerequisites-panel.v-expansion-panel--active').should('be.visible')
.find('.prerequisite-alias.condition')
.should('have.length', 6)
.then((selector) => {
expect(selector[0].innerText).to.equal('(0 & 1) | 2')
expect(selector[0]).to.not.have.class('satisfied')

expect(selector[1].innerText).to.equal('0 a:succeeded')
expect(selector[1]).to.have.class('satisfied')

expect(selector[2].innerText).to.equal('1 b:custom_output')
expect(selector[2]).to.not.have.class('satisfied')

expect(selector[3].innerText).to.equal('2 a:expired')
expect(selector[3]).to.not.have.class('satisfied')

expect(selector[4].innerText).to.equal('0')
expect(selector[4]).to.have.class('satisfied')

expect(selector[5].innerText).to.equal('0 x:succeeded')
expect(selector[5]).to.have.class('satisfied')
})

// the outputs panel
cy.get('.outputs-panel.v-expansion-panel--active').should('be.visible')
.find('.condition')
.should('have.length', 4)
.then((selector) => {
expect(selector[0]).to.contain('started')
expect(selector[0].classList.toString()).to.equal('condition satisfied')

expect(selector[1]).to.contain('succeeded')
expect(selector[1].classList.toString()).to.equal('condition')

expect(selector[2]).to.contain('failed')
expect(selector[2].classList.toString()).to.equal('condition')

expect(selector[3]).to.contain('x')
expect(selector[3].classList.toString()).to.equal('condition satisfied')
})

// the completion panel
cy.get('.completion-panel.v-expansion-panel--active').should('be.visible')
.find('.condition')
.should('have.length', 5)
.then((selector) => {
expect(selector[0]).to.contain('(')
expect(selector[0].classList.toString()).to.equal('condition blank')

expect(selector[1]).to.contain('succeeded')
expect(selector[1].classList.toString()).to.equal('condition')

expect(selector[2]).to.contain('and x')
expect(selector[2].classList.toString()).to.equal('condition satisfied')

expect(selector[3]).to.contain(')')
expect(selector[3].classList.toString()).to.equal('condition blank')

expect(selector[4]).to.contain('or failed')
expect(selector[4].classList.toString()).to.equal('condition')
})
})

it('should expand sections as intended', () => {
const spy = cy.spy()
cy.vmount(InfoComponent, {
props: {
task: TASK,
class: 'job_theme--default'
},
listeners: {
'update:panelExpansion': spy,
}
}).as('wrapper')

// ONLY the metadata panel should be expanded by default
cy.get('.v-expansion-panel--active')
.should('have.length', 1)
.should('have.class', 'metadata-panel')

// the update:panelExpansion event should be emitted when a panel is
// expanded/collapsed
cy.get('.prerequisites-panel')
.find('button')
.should('be.visible')
.click({ force: true })
.get('@wrapper').then(({ wrapper }) => {
expect(
wrapper.emitted('update:panelExpansion')[0][0]
).to.deep.equal([0, 1])
})
})

it('should work for a task with no data', () => {
// ensure the component can be mounted without errors for empty states
// i.e. no metadata, prerequisites, outputs or jobs
const tokens = new Tokens('~user/workflow//1234/foo')
const task = {
id: tokens.id,
name: tokens.task,
tokens,
node: {
task: {
meta: {
customMeta: {}
}
},
prerequisites: [],
outputs: [],
},
children: [],
}

cy.vmount(InfoComponent, {
props: {
task,
class: 'job_theme--default',
// NOTE: expand all sections by default
panelExpansion: [0, 1, 2],
}
})
})
})
Loading

0 comments on commit 0db3d62

Please sign in to comment.