Introduce ResourceList components and implement on Dashboards index (#12166)
* WIP Introduce Resource List component family * Shrink padding of resource cards a bit * Swap positions of meta information and labels in resource cards * Introduce resource name component * Polish resource name editing * Remove child type validation from context component * Styles for context menus inside resource cards * Make resource name + meta line responsive * Polish appearance of responsive resource description * Replace dashboards list with dashboards cards * Update e2e tests and add testID props to a bunch of components Co-Authored-By: Andrew Watkins <121watts@users.noreply.github.com> * Make testID props consistent Make all cypress tests have .test extension Update E2E tests for dashboards index Split off test for dashboards view Co-Authored-By: Andrew Watkins <121watts@users.noreply.github.com> * Move cell test to dashboards view test Co-Authored-By: Andrew Watkins <121watts@users.noreply.github.com> * Remove cells test from dashboards index test Co-Authored-By: Andrew Watkins <121watts@users.noreply.github.com> * Fix dashboard view - cells e2e test * Refactor meta1 and meta2 props into a single metadata prop that returns an array of elements * Cleanup * Fix and refactor e2e test to be less brittlepull/12152/head
parent
70a54e1f4c
commit
6e1ee40f45
|
@ -22,17 +22,17 @@ describe('Buckets', () => {
|
||||||
describe('from the org view', () => {
|
describe('from the org view', () => {
|
||||||
it('can create a bucket', () => {
|
it('can create a bucket', () => {
|
||||||
const newBucket = '🅱️ucket'
|
const newBucket = '🅱️ucket'
|
||||||
cy.getByDataTest('table-row').should('have.length', 1)
|
cy.getByTestID('table-row').should('have.length', 1)
|
||||||
|
|
||||||
cy.contains('Create').click()
|
cy.contains('Create').click()
|
||||||
cy.getByDataTest('overlay--container').within(() => {
|
cy.getByTestID('overlay--container').within(() => {
|
||||||
cy.getByInputName('name').type(newBucket)
|
cy.getByInputName('name').type(newBucket)
|
||||||
cy.get('.button')
|
cy.get('.button')
|
||||||
.contains('Create')
|
.contains('Create')
|
||||||
.click()
|
.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.getByDataTest('table-row')
|
cy.getByTestID('table-row')
|
||||||
.should('have.length', 2)
|
.should('have.length', 2)
|
||||||
.and('contain', newBucket)
|
.and('contain', newBucket)
|
||||||
})
|
})
|
||||||
|
@ -44,14 +44,14 @@ describe('Buckets', () => {
|
||||||
cy.contains(name).click()
|
cy.contains(name).click()
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.getByDataTest('retention-intervals').click()
|
cy.getByTestID('retention-intervals').click()
|
||||||
|
|
||||||
cy.getByInputName('days').type('{uparrow}')
|
cy.getByInputName('days').type('{uparrow}')
|
||||||
cy.getByInputName('hours').type('{uparrow}')
|
cy.getByInputName('hours').type('{uparrow}')
|
||||||
cy.getByInputName('minutes').type('{uparrow}')
|
cy.getByInputName('minutes').type('{uparrow}')
|
||||||
cy.getByInputName('seconds').type('{uparrow}')
|
cy.getByInputName('seconds').type('{uparrow}')
|
||||||
|
|
||||||
cy.getByDataTest('overlay--container').within(() => {
|
cy.getByTestID('overlay--container').within(() => {
|
||||||
cy.getByInputName('name')
|
cy.getByInputName('name')
|
||||||
.clear()
|
.clear()
|
||||||
.type(newName)
|
.type(newName)
|
||||||
|
@ -59,7 +59,7 @@ describe('Buckets', () => {
|
||||||
cy.contains('Save').click()
|
cy.contains('Save').click()
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.getByDataTest('table-row')
|
cy.getByTestID('table-row')
|
||||||
.should('contain', '1 day')
|
.should('contain', '1 day')
|
||||||
.and('contain', newName)
|
.and('contain', newName)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,101 +0,0 @@
|
||||||
import {Organization} from '@influxdata/influx'
|
|
||||||
|
|
||||||
describe('Dashboards', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.flush()
|
|
||||||
|
|
||||||
cy.setupUser().then(({body}) => {
|
|
||||||
cy.wrap(body.org).as('org')
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get<Organization>('@org').then(org => {
|
|
||||||
cy.signin(org.id)
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.fixture('routes').then(({dashboards}) => {
|
|
||||||
cy.visit(dashboards)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can create a dashboard from empty state', () => {
|
|
||||||
cy.get('.empty-state')
|
|
||||||
.contains('Create')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.visit('/dashboards')
|
|
||||||
|
|
||||||
cy.get('.index-list--row')
|
|
||||||
.its('length')
|
|
||||||
.should('be.eq', 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can create a dashboard from the header', () => {
|
|
||||||
cy.get('.page-header--container')
|
|
||||||
.contains('Create')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.getByDataTest('dropdown--item New Dashboard').click()
|
|
||||||
|
|
||||||
cy.visit('/dashboards')
|
|
||||||
|
|
||||||
cy.get('.index-list--row')
|
|
||||||
.its('length')
|
|
||||||
.should('be.eq', 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can delete a dashboard', () => {
|
|
||||||
cy.get<Organization>('@org').then(({id}) => {
|
|
||||||
cy.createDashboard(id)
|
|
||||||
cy.createDashboard(id)
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get('.index-list--row').then(rows => {
|
|
||||||
const numDashboards = rows.length
|
|
||||||
|
|
||||||
cy.get('.button-danger')
|
|
||||||
.first()
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.contains('Confirm')
|
|
||||||
.first()
|
|
||||||
.click({force: true})
|
|
||||||
|
|
||||||
cy.get('.index-list--row')
|
|
||||||
.its('length')
|
|
||||||
.should('eq', numDashboards - 1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can edit a dashboards name', () => {
|
|
||||||
cy.get<Organization>('@org').then(({id}) => {
|
|
||||||
cy.createDashboard(id).then(({body}) => {
|
|
||||||
cy.visit(`/dashboards/${body.id}`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const newName = 'new 🅱️ashboard'
|
|
||||||
|
|
||||||
cy.get('.renamable-page-title--title').click()
|
|
||||||
cy.get('.input-field')
|
|
||||||
.type(newName)
|
|
||||||
.type('{enter}')
|
|
||||||
|
|
||||||
cy.visit('/dashboards')
|
|
||||||
|
|
||||||
cy.get('.index-list--row').should('contain', newName)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Cells', () => {
|
|
||||||
it('can create a cell', () => {
|
|
||||||
cy.get<Organization>('@org').then(({id}) => {
|
|
||||||
cy.createDashboard(id).then(({body}) => {
|
|
||||||
cy.visit(`/dashboards/${body.id}`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.getByDataTest('add-cell--button').click()
|
|
||||||
cy.getByDataTest('save-cell--button').click()
|
|
||||||
cy.getByDataTest('cell--view-empty').should('have.length', 1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import {Organization} from '@influxdata/influx'
|
||||||
|
|
||||||
|
describe('Dashboards', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.flush()
|
||||||
|
|
||||||
|
cy.setupUser().then(({body}) => {
|
||||||
|
cy.wrap(body.org).as('org')
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get<Organization>('@org').then(org => {
|
||||||
|
cy.signin(org.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.fixture('routes').then(({dashboards}) => {
|
||||||
|
cy.visit(dashboards)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can create a dashboard from empty state', () => {
|
||||||
|
cy.getByTestID('empty-state')
|
||||||
|
.contains('Create')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.visit('/dashboards')
|
||||||
|
|
||||||
|
cy.getByTestID('resource-card')
|
||||||
|
.its('length')
|
||||||
|
.should('be.eq', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can create a dashboard from the header', () => {
|
||||||
|
cy.get('.page-header--container')
|
||||||
|
.contains('Create')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.getByTestID('dropdown--item New Dashboard').click()
|
||||||
|
|
||||||
|
cy.visit('/dashboards')
|
||||||
|
|
||||||
|
cy.getByTestID('resource-card')
|
||||||
|
.its('length')
|
||||||
|
.should('be.eq', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can delete a dashboard', () => {
|
||||||
|
cy.get<Organization>('@org').then(({id}) => {
|
||||||
|
cy.createDashboard(id)
|
||||||
|
cy.createDashboard(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.getByTestID('resource-card')
|
||||||
|
.its('length')
|
||||||
|
.should('eq', 2)
|
||||||
|
|
||||||
|
cy.getByTestID('resource-card')
|
||||||
|
.first()
|
||||||
|
.trigger('mouseover')
|
||||||
|
.within(() => {
|
||||||
|
cy.getByTestID('context-delete-menu').click()
|
||||||
|
|
||||||
|
cy.getByTestID('context-delete-dashboard').click()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.getByTestID('resource-card')
|
||||||
|
.its('length')
|
||||||
|
.should('eq', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can edit a dashboards name', () => {
|
||||||
|
cy.get<Organization>('@org').then(({id}) => {
|
||||||
|
cy.createDashboard(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
const newName = 'new 🅱️ashboard'
|
||||||
|
|
||||||
|
cy.getByTestID('resource-card').within(() => {
|
||||||
|
cy.getByTestID('dashboard-card--name').trigger('mouseover')
|
||||||
|
|
||||||
|
cy.getByTestID('dashboard-card--name-button').click()
|
||||||
|
|
||||||
|
cy.get('.input-field')
|
||||||
|
.type(newName)
|
||||||
|
.type('{enter}')
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.visit('/dashboards')
|
||||||
|
|
||||||
|
cy.getByTestID('resource-card').should('contain', newName)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,50 @@
|
||||||
|
import {Organization} from '@influxdata/influx'
|
||||||
|
|
||||||
|
describe('Dashboard', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.flush()
|
||||||
|
|
||||||
|
cy.setupUser().then(({body}) => {
|
||||||
|
cy.wrap(body.org).as('org')
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get<Organization>('@org').then(org => {
|
||||||
|
cy.signin(org.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.fixture('routes').then(({dashboards}) => {
|
||||||
|
cy.visit(dashboards)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can edit a dashboards name', () => {
|
||||||
|
cy.get<Organization>('@org').then(({id}) => {
|
||||||
|
cy.createDashboard(id).then(({body}) => {
|
||||||
|
cy.visit(`/dashboards/${body.id}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const newName = 'new 🅱️ashboard'
|
||||||
|
|
||||||
|
cy.get('.renamable-page-title--title').click()
|
||||||
|
cy.get('.input-field')
|
||||||
|
.type(newName)
|
||||||
|
.type('{enter}')
|
||||||
|
|
||||||
|
cy.visit('/dashboards')
|
||||||
|
|
||||||
|
cy.getByTestID('resource-card').should('contain', newName)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can create a cell', () => {
|
||||||
|
cy.get<Organization>('@org').then(({id}) => {
|
||||||
|
cy.createDashboard(id).then(({body}) => {
|
||||||
|
cy.visit(`/dashboards/${body.id}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.getByTestID('add-cell--button').click()
|
||||||
|
cy.getByTestID('save-cell--button').click()
|
||||||
|
cy.getByTestID('cell--view-empty').should('have.length', 1)
|
||||||
|
})
|
||||||
|
})
|
|
@ -22,7 +22,7 @@ describe('The Login Page', () => {
|
||||||
cy.getByInputName('password').type(user.password)
|
cy.getByInputName('password').type(user.password)
|
||||||
cy.get('button[type=submit]').click()
|
cy.get('button[type=submit]').click()
|
||||||
|
|
||||||
cy.getByDataTest('nav').should('exist')
|
cy.getByTestID('nav').should('exist')
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('login failure', () => {
|
describe('login failure', () => {
|
||||||
|
@ -30,14 +30,14 @@ describe('The Login Page', () => {
|
||||||
cy.getByInputName('password').type(user.password)
|
cy.getByInputName('password').type(user.password)
|
||||||
cy.get('button[type=submit]').click()
|
cy.get('button[type=submit]').click()
|
||||||
|
|
||||||
cy.getByDataTest('notification-error').should('exist')
|
cy.getByTestID('notification-error').should('exist')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('if password is not present', () => {
|
it('if password is not present', () => {
|
||||||
cy.getByInputName('username').type(user.username)
|
cy.getByInputName('username').type(user.username)
|
||||||
cy.get('button[type=submit]').click()
|
cy.get('button[type=submit]').click()
|
||||||
|
|
||||||
cy.getByDataTest('notification-error').should('exist')
|
cy.getByTestID('notification-error').should('exist')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('if username is incorrect', () => {
|
it('if username is incorrect', () => {
|
||||||
|
@ -45,7 +45,7 @@ describe('The Login Page', () => {
|
||||||
cy.getByInputName('password').type(user.password)
|
cy.getByInputName('password').type(user.password)
|
||||||
cy.get('button[type=submit]').click()
|
cy.get('button[type=submit]').click()
|
||||||
|
|
||||||
cy.getByDataTest('notification-error').should('exist')
|
cy.getByTestID('notification-error').should('exist')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('if password is incorrect', () => {
|
it('if password is incorrect', () => {
|
||||||
|
@ -53,7 +53,7 @@ describe('The Login Page', () => {
|
||||||
cy.getByInputName('password').type('not-a-password')
|
cy.getByInputName('password').type('not-a-password')
|
||||||
cy.get('button[type=submit]').click()
|
cy.get('button[type=submit]').click()
|
||||||
|
|
||||||
cy.getByDataTest('notification-error').should('exist')
|
cy.getByTestID('notification-error').should('exist')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -16,14 +16,13 @@ describe('Orgs', () => {
|
||||||
.its('length')
|
.its('length')
|
||||||
.should('be.eq', 1)
|
.should('be.eq', 1)
|
||||||
|
|
||||||
cy.get('.page-header--right > .button')
|
cy.getByTestID('create-org-button').click()
|
||||||
.contains('Create')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
const orgName = '🅱️organization'
|
const orgName = '🅱️organization'
|
||||||
cy.getByInputName('name').type(orgName)
|
|
||||||
|
|
||||||
cy.getByTitle('Create').click()
|
cy.getByTestID('create-org-name-input').type(orgName)
|
||||||
|
|
||||||
|
cy.getByTestID('create-org-submit-button').click()
|
||||||
|
|
||||||
cy.get('.index-list--row')
|
cy.get('.index-list--row')
|
||||||
.should('contain', orgName)
|
.should('contain', orgName)
|
|
@ -22,7 +22,7 @@ describe('Tasks', () => {
|
||||||
cy.getByInputName('interval').type('1d')
|
cy.getByInputName('interval').type('1d')
|
||||||
cy.getByInputName('offset').type('20m')
|
cy.getByInputName('offset').type('20m')
|
||||||
|
|
||||||
cy.getByDataTest('flux-editor').within(() => {
|
cy.getByTestID('flux-editor').within(() => {
|
||||||
cy.get('textarea').type(
|
cy.get('textarea').type(
|
||||||
`from(bucket: "defbuck")
|
`from(bucket: "defbuck")
|
||||||
|> range(start: -2m)`,
|
|> range(start: -2m)`,
|
||||||
|
@ -32,7 +32,7 @@ describe('Tasks', () => {
|
||||||
|
|
||||||
cy.contains('Save').click()
|
cy.contains('Save').click()
|
||||||
|
|
||||||
cy.getByDataTest('task-row')
|
cy.getByTestID('task-row')
|
||||||
.should('have.length', 1)
|
.should('have.length', 1)
|
||||||
.and('contain', taskName)
|
.and('contain', taskName)
|
||||||
})
|
})
|
||||||
|
@ -43,13 +43,13 @@ describe('Tasks', () => {
|
||||||
cy.createTask(id)
|
cy.createTask(id)
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.getByDataTest('task-row').should('have.length', 2)
|
cy.getByTestID('task-row').should('have.length', 2)
|
||||||
|
|
||||||
cy.getByDataTest('confirmation-button')
|
cy.getByTestID('confirmation-button')
|
||||||
.first()
|
.first()
|
||||||
.click({force: true})
|
.click({force: true})
|
||||||
|
|
||||||
cy.getByDataTest('task-row').should('have.length', 1)
|
cy.getByTestID('task-row').should('have.length', 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('fails to create a task without a valid script', () => {
|
it('fails to create a task without a valid script', () => {
|
||||||
|
@ -61,12 +61,12 @@ describe('Tasks', () => {
|
||||||
cy.getByInputName('interval').type('1d')
|
cy.getByInputName('interval').type('1d')
|
||||||
cy.getByInputName('offset').type('20m')
|
cy.getByInputName('offset').type('20m')
|
||||||
|
|
||||||
cy.getByDataTest('flux-editor').within(() => {
|
cy.getByTestID('flux-editor').within(() => {
|
||||||
cy.get('textarea').type('{}', {force: true})
|
cy.get('textarea').type('{}', {force: true})
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.contains('Save').click()
|
cy.contains('Save').click()
|
||||||
|
|
||||||
cy.getByDataTest('notification-error').should('exist')
|
cy.getByTestID('notification-error').should('exist')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe('Variables', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.getByInputName('name').type('Little Variable')
|
cy.getByInputName('name').type('Little Variable')
|
||||||
cy.getByDataTest('flux-editor').within(() => {
|
cy.getByTestID('flux-editor').within(() => {
|
||||||
cy.get('textarea').type('filter(fn: (r) => r._field == "cpu")', {
|
cy.get('textarea').type('filter(fn: (r) => r._field == "cpu")', {
|
||||||
force: true,
|
force: true,
|
||||||
})
|
})
|
||||||
|
@ -26,6 +26,6 @@ describe('Variables', () => {
|
||||||
.contains('Create')
|
.contains('Create')
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
cy.getByDataTest('variable-row').should('have.length', 1)
|
cy.getByTestID('variable-row').should('have.length', 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
createOrg,
|
createOrg,
|
||||||
createSource,
|
createSource,
|
||||||
flush,
|
flush,
|
||||||
getByDataTest,
|
getByTestID,
|
||||||
getByInputName,
|
getByInputName,
|
||||||
getByTitle,
|
getByTitle,
|
||||||
createTask,
|
createTask,
|
||||||
|
@ -22,7 +22,7 @@ declare global {
|
||||||
createDashboard: typeof createDashboard
|
createDashboard: typeof createDashboard
|
||||||
createOrg: typeof createOrg
|
createOrg: typeof createOrg
|
||||||
flush: typeof flush
|
flush: typeof flush
|
||||||
getByDataTest: typeof getByDataTest
|
getByTestID: typeof getByTestID
|
||||||
getByInputName: typeof getByInputName
|
getByInputName: typeof getByInputName
|
||||||
getByTitle: typeof getByTitle
|
getByTitle: typeof getByTitle
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ export const flush = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOM node getters
|
// DOM node getters
|
||||||
export const getByDataTest = (dataTest: string): Cypress.Chainable => {
|
export const getByTestID = (dataTest: string): Cypress.Chainable => {
|
||||||
return cy.get(`[data-testid="${dataTest}"]`)
|
return cy.get(`[data-testid="${dataTest}"]`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ export const getByTitle = (name: string): Cypress.Chainable => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// getters
|
// getters
|
||||||
Cypress.Commands.add('getByDataTest', getByDataTest)
|
Cypress.Commands.add('getByTestID', getByTestID)
|
||||||
Cypress.Commands.add('getByInputName', getByInputName)
|
Cypress.Commands.add('getByInputName', getByInputName)
|
||||||
Cypress.Commands.add('getByTitle', getByTitle)
|
Cypress.Commands.add('getByTitle', getByTitle)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, {SFC} from 'react'
|
import React, {SFC} from 'react'
|
||||||
|
|
||||||
const MockChild: SFC = () => <div data-test="mock-child" />
|
const MockChild: SFC = () => <div data-testid="mock-child" />
|
||||||
|
|
||||||
export default MockChild
|
export default MockChild
|
||||||
|
|
|
@ -10500,6 +10500,11 @@
|
||||||
"xml": "^1.0.0"
|
"xml": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mocks": {
|
||||||
|
"version": "0.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/mocks/-/mocks-0.0.15.tgz",
|
||||||
|
"integrity": "sha1-RmClzFn07Fbrk7/XLwg0+PxPoRw="
|
||||||
|
},
|
||||||
"moment": {
|
"moment": {
|
||||||
"version": "2.22.2",
|
"version": "2.22.2",
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
|
||||||
|
|
|
@ -91,7 +91,6 @@ class ConfirmationButton extends Component<Props, State> {
|
||||||
/>
|
/>
|
||||||
<div className={this.tooltipClassName}>
|
<div className={this.tooltipClassName}>
|
||||||
<div
|
<div
|
||||||
data-test="confirmation-button--click-target"
|
|
||||||
data-testid={testID}
|
data-testid={testID}
|
||||||
className="confirmation-button--tooltip-body"
|
className="confirmation-button--tooltip-body"
|
||||||
onClick={this.handleTooltipClick}
|
onClick={this.handleTooltipClick}
|
||||||
|
|
|
@ -44,9 +44,7 @@ describe('ConfirmationButton', () => {
|
||||||
|
|
||||||
wrapper.find(Button).simulate('click')
|
wrapper.find(Button).simulate('click')
|
||||||
|
|
||||||
wrapper
|
wrapper.find({'data-testid': 'confirmation-button'}).simulate('click')
|
||||||
.find({'data-test': 'confirmation-button--click-target'})
|
|
||||||
.simulate('click')
|
|
||||||
|
|
||||||
expect(onConfirm.mock.results[0].value).toBe(returnValue)
|
expect(onConfirm.mock.results[0].value).toBe(returnValue)
|
||||||
})
|
})
|
||||||
|
|
|
@ -44,7 +44,6 @@ exports[`ConfirmationButton interaction shows the tooltip when clicked 1`] = `
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="confirmation-button--tooltip-body"
|
className="confirmation-button--tooltip-body"
|
||||||
data-test="confirmation-button--click-target"
|
|
||||||
data-testid="confirmation-button"
|
data-testid="confirmation-button"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
>
|
>
|
||||||
|
|
|
@ -55,7 +55,7 @@ class Context extends PureComponent<Props, State> {
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Expected children of type <Context.Menu />')
|
return child
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,26 +17,32 @@ import {
|
||||||
|
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
interface Props {
|
interface PassedProps {
|
||||||
children: JSX.Element | JSX.Element[]
|
children: JSX.Element | JSX.Element[]
|
||||||
icon: IconFont
|
icon: IconFont
|
||||||
text?: string
|
|
||||||
title
|
|
||||||
color?: ComponentColor
|
|
||||||
shape?: ButtonShape
|
|
||||||
onBoostZIndex?: (boostZIndex: boolean) => void
|
onBoostZIndex?: (boostZIndex: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DefaultProps {
|
||||||
|
text?: string
|
||||||
|
color?: ComponentColor
|
||||||
|
shape?: ButtonShape
|
||||||
|
testID?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = PassedProps & DefaultProps
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
isExpanded: boolean
|
isExpanded: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
class ContextMenu extends Component<Props, State> {
|
class ContextMenu extends Component<Props, State> {
|
||||||
public static defaultProps: Partial<Props> = {
|
public static defaultProps: DefaultProps = {
|
||||||
color: ComponentColor.Primary,
|
color: ComponentColor.Primary,
|
||||||
shape: ButtonShape.Square,
|
shape: ButtonShape.Square,
|
||||||
text: '',
|
text: '',
|
||||||
|
testID: 'context-menu',
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
|
@ -48,7 +54,7 @@ class ContextMenu extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {icon, text, shape, color} = this.props
|
const {icon, text, shape, color, testID} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClickOutside onClickOutside={this.handleCollapseMenu}>
|
<ClickOutside onClickOutside={this.handleCollapseMenu}>
|
||||||
|
@ -61,6 +67,7 @@ class ContextMenu extends Component<Props, State> {
|
||||||
icon={icon}
|
icon={icon}
|
||||||
size={ComponentSize.ExtraSmall}
|
size={ComponentSize.ExtraSmall}
|
||||||
color={color}
|
color={color}
|
||||||
|
testID={testID}
|
||||||
/>
|
/>
|
||||||
{this.menu}
|
{this.menu}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,24 +2,36 @@
|
||||||
import React, {Component} from 'react'
|
import React, {Component} from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
interface Props {
|
interface PassedProps {
|
||||||
label: string
|
label: string
|
||||||
description?: string
|
|
||||||
action: (value?: any) => void
|
action: (value?: any) => void
|
||||||
value?: any
|
value?: any
|
||||||
onCollapseMenu?: () => void
|
onCollapseMenu?: () => void
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DefaultProps {
|
||||||
|
description?: string
|
||||||
|
testID?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = PassedProps & DefaultProps
|
||||||
|
|
||||||
class ContextMenuItem extends Component<Props> {
|
class ContextMenuItem extends Component<Props> {
|
||||||
|
public static defaultProps: DefaultProps = {
|
||||||
|
description: null,
|
||||||
|
testID: 'context-menu-item',
|
||||||
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {label, disabled} = this.props
|
const {label, disabled, testID} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={this.className}
|
className={this.className}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
data-testid={testID}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
{this.description}
|
{this.description}
|
||||||
|
|
|
@ -183,7 +183,7 @@ class MultiSelectDropdown extends Component<Props, State> {
|
||||||
autoHeight={true}
|
autoHeight={true}
|
||||||
maxHeight={maxMenuHeight}
|
maxHeight={maxMenuHeight}
|
||||||
>
|
>
|
||||||
<div className="dropdown--menu" data-test="dropdown-menu">
|
<div className="dropdown--menu" data-testid="dropdown-menu">
|
||||||
{React.Children.map(children, (child: JSX.Element) => {
|
{React.Children.map(children, (child: JSX.Element) => {
|
||||||
if (this.childTypeIsValid(child)) {
|
if (this.childTypeIsValid(child)) {
|
||||||
if (child.type === DropdownItem) {
|
if (child.type === DropdownItem) {
|
||||||
|
|
|
@ -60,7 +60,7 @@ describe('MultiSelectDropdown', () => {
|
||||||
|
|
||||||
button.simulate('click')
|
button.simulate('click')
|
||||||
|
|
||||||
expect(wrapper.find('[data-test="dropdown-menu"]')).toHaveLength(1)
|
expect(wrapper.find('[data-testid="dropdown-menu"]')).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
|
|
|
@ -64,7 +64,7 @@ exports[`MultiSelectDropdown with menu expanded matches snapshot 1`] = `
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="dropdown--menu"
|
className="dropdown--menu"
|
||||||
data-test="dropdown-menu"
|
data-testid="dropdown-menu"
|
||||||
>
|
>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
checkbox={true}
|
checkbox={true}
|
||||||
|
|
|
@ -14,26 +14,37 @@ import 'src/clockface/components/empty_state/EmptyState.scss'
|
||||||
// Decorators
|
// Decorators
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
interface Props {
|
interface PassedProps {
|
||||||
size?: ComponentSize
|
|
||||||
children: JSX.Element | JSX.Element[]
|
children: JSX.Element | JSX.Element[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DefaultProps {
|
||||||
|
size?: ComponentSize
|
||||||
|
testID?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = PassedProps & DefaultProps
|
||||||
|
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
class EmptyState extends Component<Props> {
|
class EmptyState extends Component<Props> {
|
||||||
public static defaultProps: Partial<Props> = {
|
public static defaultProps: DefaultProps = {
|
||||||
size: ComponentSize.Small,
|
size: ComponentSize.Small,
|
||||||
|
testID: 'empty-state',
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Text = EmptyStateText
|
public static Text = EmptyStateText
|
||||||
public static SubText = EmptyStateSubText
|
public static SubText = EmptyStateSubText
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {children, size} = this.props
|
const {children, size, testID} = this.props
|
||||||
|
|
||||||
const className = `empty-state empty-state--${size}`
|
const className = `empty-state empty-state--${size}`
|
||||||
|
|
||||||
return <div className={className}>{children}</div>
|
return (
|
||||||
|
<div className={className} data-testid={testID}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ class IndexListBody extends Component<Props> {
|
||||||
<tbody className="index-list--empty">
|
<tbody className="index-list--empty">
|
||||||
<tr className="index-list--empty-row">
|
<tr className="index-list--empty-row">
|
||||||
<td colSpan={columnCount}>
|
<td colSpan={columnCount}>
|
||||||
<div className="index-list--empty-cell" data-test="empty-state">
|
<div className="index-list--empty-cell" data-testid="empty-state">
|
||||||
{emptyState}
|
{emptyState}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -69,9 +69,9 @@ describe('IndexList', () => {
|
||||||
|
|
||||||
const emptyDiv = wrapper
|
const emptyDiv = wrapper
|
||||||
.find('div')
|
.find('div')
|
||||||
.filterWhere(div => div.prop('data-test'))
|
.filterWhere(div => div.prop('data-testid'))
|
||||||
|
|
||||||
expect(emptyDiv.prop('data-test')).toBe('empty-state')
|
expect(emptyDiv.prop('data-testid')).toBe('empty-state')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('matches snapshot when 0 rows exist', () => {
|
it('matches snapshot when 0 rows exist', () => {
|
||||||
|
|
|
@ -69,7 +69,7 @@ exports[`IndexList matches snapshot when 0 rows exist 1`] = `
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="index-list--empty-cell"
|
className="index-list--empty-cell"
|
||||||
data-test="empty-state"
|
data-testid="empty-state"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Empty
|
Empty
|
||||||
|
|
|
@ -22,37 +22,43 @@ export enum AutoComplete {
|
||||||
Off = 'off',
|
Off = 'off',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface PassedProps {
|
||||||
id?: string
|
id?: string
|
||||||
min?: number
|
min?: number
|
||||||
max?: number
|
max?: number
|
||||||
name?: string
|
|
||||||
value: string | number
|
|
||||||
placeholder?: string
|
|
||||||
autocomplete?: AutoComplete
|
|
||||||
onChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
onChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
||||||
onBlur?: (e?: ChangeEvent<HTMLInputElement>) => void
|
onBlur?: (e?: ChangeEvent<HTMLInputElement>) => void
|
||||||
onFocus?: (e?: ChangeEvent<HTMLInputElement>) => void
|
onFocus?: (e?: ChangeEvent<HTMLInputElement>) => void
|
||||||
onKeyPress?: (e: KeyboardEvent<HTMLInputElement>) => void
|
onKeyPress?: (e: KeyboardEvent<HTMLInputElement>) => void
|
||||||
onKeyUp?: (e: KeyboardEvent<HTMLInputElement>) => void
|
onKeyUp?: (e: KeyboardEvent<HTMLInputElement>) => void
|
||||||
onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void
|
onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void
|
||||||
size?: ComponentSize
|
|
||||||
icon?: IconFont
|
icon?: IconFont
|
||||||
status?: ComponentStatus
|
|
||||||
autoFocus?: boolean
|
|
||||||
spellCheck?: boolean
|
|
||||||
type: InputType
|
|
||||||
widthPixels?: number
|
widthPixels?: number
|
||||||
titleText?: string
|
|
||||||
disabledTitleText?: string
|
|
||||||
customClass?: string
|
customClass?: string
|
||||||
maxLength?: number
|
maxLength?: number
|
||||||
tabIndex?: number
|
tabIndex?: number
|
||||||
dataTest?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DefaultProps {
|
||||||
|
type?: InputType
|
||||||
|
name?: string
|
||||||
|
value: string | number
|
||||||
|
placeholder?: string
|
||||||
|
titleText?: string
|
||||||
|
autocomplete?: AutoComplete
|
||||||
|
disabledTitleText?: string
|
||||||
|
size?: ComponentSize
|
||||||
|
status?: ComponentStatus
|
||||||
|
autoFocus?: boolean
|
||||||
|
spellCheck?: boolean
|
||||||
|
testID?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = PassedProps & DefaultProps
|
||||||
|
|
||||||
class Input extends Component<Props> {
|
class Input extends Component<Props> {
|
||||||
public static defaultProps: Partial<Props> = {
|
public static defaultProps: DefaultProps = {
|
||||||
|
type: InputType.Text,
|
||||||
name: '',
|
name: '',
|
||||||
value: '',
|
value: '',
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
|
@ -63,6 +69,7 @@ class Input extends Component<Props> {
|
||||||
status: ComponentStatus.Default,
|
status: ComponentStatus.Default,
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
spellCheck: false,
|
spellCheck: false,
|
||||||
|
testID: 'input-field',
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
@ -85,7 +92,7 @@ class Input extends Component<Props> {
|
||||||
maxLength,
|
maxLength,
|
||||||
autocomplete,
|
autocomplete,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
dataTest,
|
testID,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -112,7 +119,7 @@ class Input extends Component<Props> {
|
||||||
disabled={status === ComponentStatus.Disabled}
|
disabled={status === ComponentStatus.Disabled}
|
||||||
maxLength={maxLength}
|
maxLength={maxLength}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
data-test={dataTest}
|
data-testid={testID}
|
||||||
/>
|
/>
|
||||||
{this.icon}
|
{this.icon}
|
||||||
{this.statusIndicator}
|
{this.statusIndicator}
|
||||||
|
@ -170,7 +177,7 @@ class Input extends Component<Props> {
|
||||||
<span
|
<span
|
||||||
className={`input-status icon ${
|
className={`input-status icon ${
|
||||||
IconFont.AlertTriangle
|
IconFont.AlertTriangle
|
||||||
} data-test="input-error"`}
|
} data-testid="input-error"`}
|
||||||
/>
|
/>
|
||||||
<div className="input-shadow" />
|
<div className="input-shadow" />
|
||||||
</>
|
</>
|
||||||
|
@ -183,7 +190,7 @@ class Input extends Component<Props> {
|
||||||
<span
|
<span
|
||||||
className={`input-status icon ${
|
className={`input-status icon ${
|
||||||
IconFont.Checkmark
|
IconFont.Checkmark
|
||||||
} data-test="input-valid"`}
|
} data-testid="input-valid"`}
|
||||||
/>
|
/>
|
||||||
<div className="input-shadow" />
|
<div className="input-shadow" />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -55,13 +55,13 @@ class OverlayTechnology extends Component<Props, State> {
|
||||||
|
|
||||||
if (showChildren) {
|
if (showChildren) {
|
||||||
return (
|
return (
|
||||||
<div className="overlay--dialog" data-test="overlay-children">
|
<div className="overlay--dialog" data-testid="overlay-children">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="overlay--dialog" data-test="overlay-children" />
|
return <div className="overlay--dialog" data-testid="overlay-children" />
|
||||||
}
|
}
|
||||||
|
|
||||||
private get overlayClass(): string {
|
private get overlayClass(): string {
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import {Link} from 'react-router'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import {Organization} from 'src/types/v2'
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import {UPDATED_AT_TIME_FORMAT} from 'src/dashboards/constants'
|
||||||
|
|
||||||
|
interface PassedProps {
|
||||||
|
name: () => JSX.Element
|
||||||
|
description?: () => JSX.Element
|
||||||
|
updatedAt?: string
|
||||||
|
owner?: Organization
|
||||||
|
labels?: () => JSX.Element
|
||||||
|
metaData?: () => JSX.Element[]
|
||||||
|
contextMenu?: () => JSX.Element
|
||||||
|
children?: JSX.Element[] | JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DefaultProps {
|
||||||
|
testID?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = PassedProps & DefaultProps
|
||||||
|
|
||||||
|
export default class ResourceListCard extends PureComponent<Props> {
|
||||||
|
public static defaultProps: DefaultProps = {
|
||||||
|
testID: 'resource-card',
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const {description, children, testID, labels} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="resource-list--card" data-testid={testID}>
|
||||||
|
{this.nameAndMeta}
|
||||||
|
{description()}
|
||||||
|
{labels()}
|
||||||
|
{children}
|
||||||
|
{this.contextMenu}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get nameAndMeta(): JSX.Element {
|
||||||
|
const {name} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="resource-list--name-meta">
|
||||||
|
{name()}
|
||||||
|
{this.formattedMetaData}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get formattedMetaData(): JSX.Element {
|
||||||
|
const {updatedAt, owner, metaData} = this.props
|
||||||
|
|
||||||
|
if (!updatedAt && !owner && !metaData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="resource-list--meta">
|
||||||
|
{this.ownerLink}
|
||||||
|
{this.updated}
|
||||||
|
{this.metaData}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get updated(): JSX.Element {
|
||||||
|
const {updatedAt} = this.props
|
||||||
|
|
||||||
|
if (updatedAt) {
|
||||||
|
const relativeTimestamp = moment(updatedAt).fromNow()
|
||||||
|
const absoluteTimestamp = moment(updatedAt).format(UPDATED_AT_TIME_FORMAT)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="resource-list--meta-item" title={absoluteTimestamp}>
|
||||||
|
{`Modified ${relativeTimestamp}`}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get ownerLink(): JSX.Element {
|
||||||
|
const {owner} = this.props
|
||||||
|
|
||||||
|
if (owner) {
|
||||||
|
return (
|
||||||
|
<div className="resource-list--meta-item">
|
||||||
|
<Link
|
||||||
|
to={`/organizations/${owner.id}/members_tab`}
|
||||||
|
className="resource-list--owner"
|
||||||
|
>
|
||||||
|
{owner.name}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get metaData(): JSX.Element[] {
|
||||||
|
const {metaData} = this.props
|
||||||
|
|
||||||
|
if (metaData) {
|
||||||
|
return React.Children.map(metaData(), (m: JSX.Element) => {
|
||||||
|
if (m !== null && m !== undefined) {
|
||||||
|
return <div className="resource-list--meta-item">{m}</div>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get contextMenu(): JSX.Element {
|
||||||
|
const {contextMenu} = this.props
|
||||||
|
|
||||||
|
if (contextMenu) {
|
||||||
|
return <div className="resource-list--context-menu">{contextMenu()}</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
@import 'src/style/modules';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Resource Editable Description
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
.resource-description {
|
||||||
|
width: 100%;
|
||||||
|
min-height: $form-xs-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-description--preview,
|
||||||
|
.input.resource-description--input > input {
|
||||||
|
font-size: $form-xs-font;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: $ix-text-font;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-description--preview,
|
||||||
|
.resource-description--input {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-description--preview {
|
||||||
|
width: auto;
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: $radius;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
@include no-user-select();
|
||||||
|
color: $g13-mist;
|
||||||
|
transition: color 0.25s ease, background-color 0.25s ease,
|
||||||
|
border-color 0.25s ease;
|
||||||
|
line-height: $form-xs-font + $ix-border;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: $ix-marg-b;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
color: $g11-sidewalk;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.untitled {
|
||||||
|
color: $g9-mountain;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: text;
|
||||||
|
color: $g20-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure placeholder text matches font weight of title */
|
||||||
|
.input.resource-description--input > input {
|
||||||
|
&::-webkit-input-placeholder {
|
||||||
|
font-weight: $page-title-weight !important;
|
||||||
|
}
|
||||||
|
&::-moz-placeholder {
|
||||||
|
font-weight: $page-title-weight !important;
|
||||||
|
}
|
||||||
|
&:-ms-input-placeholder {
|
||||||
|
font-weight: $page-title-weight !important;
|
||||||
|
}
|
||||||
|
&:-moz-placeholder {
|
||||||
|
font-weight: $page-title-weight !important;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {Component, KeyboardEvent, ChangeEvent} from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import {Input, ComponentSize} from 'src/clockface'
|
||||||
|
import {ClickOutside} from 'src/shared/components/ClickOutside'
|
||||||
|
|
||||||
|
// Decorators
|
||||||
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
import 'src/clockface/components/resource_list/ResourceDescription.scss'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onUpdate: (name: string) => void
|
||||||
|
description: string
|
||||||
|
placeholder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
isEditing: boolean
|
||||||
|
workingDescription: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@ErrorHandling
|
||||||
|
class ResourceDescription extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isEditing: false,
|
||||||
|
workingDescription: props.description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const {description} = this.props
|
||||||
|
const {isEditing} = this.state
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
return (
|
||||||
|
<div className="resource-description">
|
||||||
|
<ClickOutside onClickOutside={this.handleStopEditing}>
|
||||||
|
{this.input}
|
||||||
|
</ClickOutside>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="resource-description">
|
||||||
|
<div
|
||||||
|
className={this.previewClassName}
|
||||||
|
onClick={this.handleStartEditing}
|
||||||
|
>
|
||||||
|
{description || 'No description'}
|
||||||
|
<span className="icon pencil" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get input(): JSX.Element {
|
||||||
|
const {placeholder} = this.props
|
||||||
|
const {workingDescription} = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
size={ComponentSize.ExtraSmall}
|
||||||
|
autoFocus={true}
|
||||||
|
spellCheck={false}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onFocus={this.handleInputFocus}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
|
customClass="resource-description--input"
|
||||||
|
value={workingDescription}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleStartEditing = (): void => {
|
||||||
|
this.setState({isEditing: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleStopEditing = async (): Promise<void> => {
|
||||||
|
const {workingDescription} = this.state
|
||||||
|
const {onUpdate} = this.props
|
||||||
|
|
||||||
|
await onUpdate(workingDescription)
|
||||||
|
|
||||||
|
this.setState({isEditing: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
this.setState({workingDescription: e.target.value})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleKeyDown = async (
|
||||||
|
e: KeyboardEvent<HTMLInputElement>
|
||||||
|
): Promise<void> => {
|
||||||
|
const {onUpdate, description} = this.props
|
||||||
|
const {workingDescription} = this.state
|
||||||
|
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
await onUpdate(workingDescription)
|
||||||
|
this.setState({isEditing: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
this.setState({isEditing: false, workingDescription: description})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputFocus = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
e.currentTarget.select()
|
||||||
|
}
|
||||||
|
|
||||||
|
private get previewClassName(): string {
|
||||||
|
const {description} = this.props
|
||||||
|
|
||||||
|
return classnames('resource-description--preview', {
|
||||||
|
untitled: !description,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResourceDescription
|
|
@ -0,0 +1,204 @@
|
||||||
|
@import 'src/style/modules';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Resource Lists
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Hence the name these are used to display lists of resources and starndardize
|
||||||
|
the appearance and UX of managing resources
|
||||||
|
*/
|
||||||
|
|
||||||
|
.resource-list {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Header & Sorting
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
.resource-list--header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: $ix-marg-c;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--sorting {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--filter {
|
||||||
|
min-width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--sorter {
|
||||||
|
user-select: none;
|
||||||
|
font-size: $form-md-font;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: $g11-sidewalk;
|
||||||
|
transition: color 0.25s ease;
|
||||||
|
margin-left: $ix-marg-c;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $g18-cloud;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.resource-list--sort-descending,
|
||||||
|
&.resource-list--sort-ascending {
|
||||||
|
color: $c-pool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--sort-arrow {
|
||||||
|
width: $ix-marg-c;
|
||||||
|
height: $ix-marg-c;
|
||||||
|
position: relative;
|
||||||
|
margin-left: $ix-marg-a;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
.resource-list--sort-descending &,
|
||||||
|
.resource-list--sort-ascending & {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span.icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--sort-descending & > span.icon {
|
||||||
|
transform: translate(-50%, -50%) rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--sort-ascending & > span.icon {
|
||||||
|
transform: translate(-50%, -50%) rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cards
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
.resource-list--card {
|
||||||
|
position: relative;
|
||||||
|
color: $g13-mist;
|
||||||
|
background-color: $g3-castle;
|
||||||
|
border-radius: $radius;
|
||||||
|
padding: $ix-marg-b $ix-marg-c;
|
||||||
|
margin-bottom: $ix-border;
|
||||||
|
transition: background-color 0.25s ease, color 0.25s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $g16-pearl;
|
||||||
|
background-color: $g4-onyx;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-bottom: $ix-border;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--meta {
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: $form-xs-font;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $g11-sidewalk;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--meta-item {
|
||||||
|
transition: border-color 0.25s ease, color 0.25s ease;
|
||||||
|
border-right: $ix-border solid $g5-pepper;
|
||||||
|
padding-right: $ix-marg-b;
|
||||||
|
margin-right: $ix-marg-b;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--card:hover & {
|
||||||
|
color: $g15-platinum;
|
||||||
|
border-color: $g7-graphite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--card a.resource-list--owner {
|
||||||
|
color: $g11-sidewalk !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--card:hover a.resource-list--owner {
|
||||||
|
color: $g15-platinum !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--card:hover a.resource-list--owner:hover {
|
||||||
|
color: $c-laser !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--context-menu {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: $ix-marg-b;
|
||||||
|
right: $ix-marg-b;
|
||||||
|
|
||||||
|
.resource-list--card:hover & {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list--name-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
> *:first-child {
|
||||||
|
margin-bottom: $ix-marg-a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 767px) {
|
||||||
|
.resource-list--name-meta {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> *:first-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Depth Styling
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
.panel .resource-list--card,
|
||||||
|
.tabs .resource-list--card {
|
||||||
|
background-color: $g4-onyx;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $g5-pepper;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import ResourceListHeader from 'src/clockface/components/resource_list/ResourceListHeader'
|
||||||
|
import ResourceListSorter from 'src/clockface/components/resource_list/ResourceListSorter'
|
||||||
|
import ResourceListBody from 'src/clockface/components/resource_list/ResourceListBody'
|
||||||
|
import ResourceCard from 'src/clockface/components/resource_list/ResourceCard'
|
||||||
|
import ResourceName from 'src/clockface/components/resource_list/ResourceName'
|
||||||
|
import ResourceDescription from 'src/clockface/components/resource_list/ResourceDescription'
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
import 'src/clockface/components/resource_list/ResourceList.scss'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: JSX.Element[] | JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ResourceList extends PureComponent<Props> {
|
||||||
|
public static Header = ResourceListHeader
|
||||||
|
public static Sorter = ResourceListSorter
|
||||||
|
public static Body = ResourceListBody
|
||||||
|
public static Card = ResourceCard
|
||||||
|
public static Name = ResourceName
|
||||||
|
public static Description = ResourceDescription
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return <div className="resource-list">{this.props.children}</div>
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: JSX.Element[] | JSX.Element
|
||||||
|
emptyState: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ResourceListBody extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
return <div className="resource-list--body">{this.children}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
private get children(): JSX.Element | JSX.Element[] {
|
||||||
|
const {children, emptyState} = this.props
|
||||||
|
|
||||||
|
if (React.Children.count(children) === 0) {
|
||||||
|
return emptyState
|
||||||
|
}
|
||||||
|
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: JSX.Element[] | JSX.Element
|
||||||
|
filterComponent?: () => JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ResourceListHeader extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {children} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="resource-list--header">
|
||||||
|
{this.filter}
|
||||||
|
<div className="resource-list--sorting">{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get filter(): JSX.Element {
|
||||||
|
const {filterComponent} = this.props
|
||||||
|
|
||||||
|
if (filterComponent) {
|
||||||
|
return <div className="resource-list--filter">{filterComponent()}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="resource-list--filter" />
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import {Sort} from 'src/clockface/types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
sortKey: string
|
||||||
|
sort: Sort
|
||||||
|
name: string
|
||||||
|
onClick?: (nextSort: Sort, sortKey: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ResourceListSorter extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {name} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={this.className}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
title={this.title}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
{this.sortIndicator}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleClick = (): void => {
|
||||||
|
const {onClick, sort, sortKey} = this.props
|
||||||
|
|
||||||
|
if (!onClick || !sort) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sort === Sort.None) {
|
||||||
|
onClick(Sort.Ascending, sortKey)
|
||||||
|
} else if (sort === Sort.Ascending) {
|
||||||
|
onClick(Sort.Descending, sortKey)
|
||||||
|
} else if (sort === Sort.Descending) {
|
||||||
|
onClick(Sort.None, sortKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get title(): string {
|
||||||
|
const {sort, name} = this.props
|
||||||
|
|
||||||
|
if (sort === Sort.None) {
|
||||||
|
return `Sort ${name} in ${Sort.Ascending} order`
|
||||||
|
} else if (sort === Sort.Ascending) {
|
||||||
|
return `Sort ${name} in ${Sort.Descending} order`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get sortIndicator(): JSX.Element {
|
||||||
|
const {onClick} = this.props
|
||||||
|
|
||||||
|
if (onClick) {
|
||||||
|
return (
|
||||||
|
<span className="resource-list--sort-arrow">
|
||||||
|
<span className="icon caret-down" />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get className(): string {
|
||||||
|
const {sort} = this.props
|
||||||
|
|
||||||
|
return classnames('resource-list--sorter', {
|
||||||
|
'resource-list--sort-descending': sort === Sort.Descending,
|
||||||
|
'resource-list--sort-ascending': sort === Sort.Ascending,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
@import 'src/style/modules';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Editable Name for Resource Cards
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
$resource-name-font-size: 17px;
|
||||||
|
$resource-name-font-weight: 600;
|
||||||
|
|
||||||
|
.resource-name > a,
|
||||||
|
.input.resource-name--input > input {
|
||||||
|
font-size: $resource-name-font-size;
|
||||||
|
font-weight: $resource-name-font-weight;
|
||||||
|
font-family: $ix-text-font;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-name > a {
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: $form-xs-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-name {
|
||||||
|
height: $form-xs-height;
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
margin-right: $ix-marg-a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input.resource-name--input {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure placeholder text matches font weight of title */
|
||||||
|
.input.resource-name--input > input {
|
||||||
|
&::-webkit-input-placeholder {
|
||||||
|
font-weight: $resource-name-font-weight !important;
|
||||||
|
}
|
||||||
|
&::-moz-placeholder {
|
||||||
|
font-weight: $resource-name-font-weight !important;
|
||||||
|
}
|
||||||
|
&:-ms-input-placeholder {
|
||||||
|
font-weight: $resource-name-font-weight !important;
|
||||||
|
}
|
||||||
|
&:-moz-placeholder {
|
||||||
|
font-weight: $resource-name-font-weight !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit button hover behavior */
|
||||||
|
.resource-name--toggle {
|
||||||
|
font-size: $resource-name-font-size * 0.75;
|
||||||
|
transform: translateY(-10%);
|
||||||
|
padding: $ix-marg-a;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: $ix-marg-b;
|
||||||
|
transition: color 0.25s ease, opacity 0.25s ease, width 0.25s ease;
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
color: $g11-sidewalk;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: $g15-platinum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-name:hover,
|
||||||
|
.resource-name--editing {
|
||||||
|
.resource-name--toggle {
|
||||||
|
opacity: 1;
|
||||||
|
width: $ix-marg-b + $ix-marg-c;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {Component, KeyboardEvent, ChangeEvent, MouseEvent} from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import {Input, ComponentSize} from 'src/clockface'
|
||||||
|
import {ClickOutside} from 'src/shared/components/ClickOutside'
|
||||||
|
|
||||||
|
// Decorators
|
||||||
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
import 'src/clockface/components/resource_list/ResourceName.scss'
|
||||||
|
|
||||||
|
interface PassedProps {
|
||||||
|
onUpdate: (name: string) => void
|
||||||
|
name: string
|
||||||
|
onEditName?: (e?: MouseEvent<HTMLAnchorElement>) => void
|
||||||
|
placeholder?: string
|
||||||
|
noNameString: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DefaultProps {
|
||||||
|
parentTestID?: string
|
||||||
|
buttonTestID?: string
|
||||||
|
inputTestID?: string
|
||||||
|
hrefValue?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = PassedProps & DefaultProps
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
isEditing: boolean
|
||||||
|
workingName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@ErrorHandling
|
||||||
|
class ResourceName extends Component<Props, State> {
|
||||||
|
public static defaultProps: DefaultProps = {
|
||||||
|
parentTestID: 'resource-name',
|
||||||
|
buttonTestID: 'resource-name--button',
|
||||||
|
inputTestID: 'resource-name--input',
|
||||||
|
hrefValue: '#',
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isEditing: false,
|
||||||
|
workingName: props.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
onEditName,
|
||||||
|
hrefValue,
|
||||||
|
noNameString,
|
||||||
|
parentTestID,
|
||||||
|
buttonTestID,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={this.className} data-testid={parentTestID}>
|
||||||
|
<a href={hrefValue} onClick={onEditName}>
|
||||||
|
<span>{name || noNameString}</span>
|
||||||
|
</a>
|
||||||
|
<div
|
||||||
|
className="resource-name--toggle"
|
||||||
|
onClick={this.handleStartEditing}
|
||||||
|
data-testid={buttonTestID}
|
||||||
|
>
|
||||||
|
<span className="icon pencil" />
|
||||||
|
</div>
|
||||||
|
{this.input}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get input(): JSX.Element {
|
||||||
|
const {placeholder, inputTestID} = this.props
|
||||||
|
const {workingName, isEditing} = this.state
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
return (
|
||||||
|
<ClickOutside onClickOutside={this.handleStopEditing}>
|
||||||
|
<Input
|
||||||
|
size={ComponentSize.ExtraSmall}
|
||||||
|
maxLength={90}
|
||||||
|
autoFocus={true}
|
||||||
|
spellCheck={false}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onFocus={this.handleInputFocus}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
|
customClass="resource-name--input"
|
||||||
|
value={workingName}
|
||||||
|
testID={inputTestID}
|
||||||
|
/>
|
||||||
|
</ClickOutside>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleStartEditing = (): void => {
|
||||||
|
this.setState({isEditing: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleStopEditing = async (): Promise<void> => {
|
||||||
|
const {workingName} = this.state
|
||||||
|
const {onUpdate} = this.props
|
||||||
|
|
||||||
|
await onUpdate(workingName)
|
||||||
|
|
||||||
|
this.setState({isEditing: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
this.setState({workingName: e.target.value})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleKeyDown = async (
|
||||||
|
e: KeyboardEvent<HTMLInputElement>
|
||||||
|
): Promise<void> => {
|
||||||
|
const {onUpdate, name} = this.props
|
||||||
|
const {workingName} = this.state
|
||||||
|
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.persist()
|
||||||
|
if (!workingName) {
|
||||||
|
this.setState({isEditing: false, workingName: name})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await onUpdate(workingName)
|
||||||
|
this.setState({isEditing: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
this.setState({isEditing: false, workingName: name})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputFocus = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
e.currentTarget.select()
|
||||||
|
}
|
||||||
|
|
||||||
|
private get className(): string {
|
||||||
|
const {name, noNameString} = this.props
|
||||||
|
const {isEditing} = this.state
|
||||||
|
|
||||||
|
return classnames('resource-name', {
|
||||||
|
'resource-name--editing': isEditing,
|
||||||
|
'untitled-name': name === noNameString,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResourceName
|
|
@ -22,6 +22,7 @@ import ProgressBar from './components/wizard/ProgressBar'
|
||||||
import ComponentSpacer from './components/component_spacer/ComponentSpacer'
|
import ComponentSpacer from './components/component_spacer/ComponentSpacer'
|
||||||
import EmptyState from './components/empty_state/EmptyState'
|
import EmptyState from './components/empty_state/EmptyState'
|
||||||
import IndexList from './components/index_views/IndexList'
|
import IndexList from './components/index_views/IndexList'
|
||||||
|
import ResourceList from './components/resource_list/ResourceList'
|
||||||
import Context from './components/context_menu/Context'
|
import Context from './components/context_menu/Context'
|
||||||
import FormElement from 'src/clockface/components/form_layout/FormElement'
|
import FormElement from 'src/clockface/components/form_layout/FormElement'
|
||||||
import DraggableResizer from 'src/clockface/components/draggable_resizer/DraggableResizer'
|
import DraggableResizer from 'src/clockface/components/draggable_resizer/DraggableResizer'
|
||||||
|
@ -95,6 +96,7 @@ export {
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
QuestionMarkTooltip,
|
QuestionMarkTooltip,
|
||||||
Radio,
|
Radio,
|
||||||
|
ResourceList,
|
||||||
ResponsiveGridSizer,
|
ResponsiveGridSizer,
|
||||||
Select,
|
Select,
|
||||||
Sort,
|
Sort,
|
||||||
|
|
|
@ -166,7 +166,7 @@ export default class GraphOptionsCustomizableField extends Component<Props> {
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
id="internalName"
|
id="internalName"
|
||||||
value={displayName}
|
value={displayName}
|
||||||
data-test="custom-time-format"
|
data-testid="custom-time-format"
|
||||||
onChange={this.handleFieldRename}
|
onChange={this.handleFieldRename}
|
||||||
placeholder={`Rename ${internalName}`}
|
placeholder={`Rename ${internalName}`}
|
||||||
disabled={!visible}
|
disabled={!visible}
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import {IconFont, ComponentColor} from '@influxdata/clockface'
|
||||||
|
import {Label, ResourceList, Context} from 'src/clockface'
|
||||||
|
import FeatureFlag from 'src/shared/components/FeatureFlag'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import {Dashboard, Organization} from 'src/types/v2'
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import {DEFAULT_DASHBOARD_NAME} from 'src/dashboards/constants'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
dashboard: Dashboard
|
||||||
|
orgs: Organization[]
|
||||||
|
onDeleteDashboard: (dashboard: Dashboard) => void
|
||||||
|
onCloneDashboard: (dashboard: Dashboard) => void
|
||||||
|
onExportDashboard: (dashboard: Dashboard) => void
|
||||||
|
onUpdateDashboard: (dashboard: Dashboard) => void
|
||||||
|
onEditLabels: (dashboard: Dashboard) => void
|
||||||
|
showOwnerColumn: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DashboardCard extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {dashboard} = this.props
|
||||||
|
const {id} = dashboard
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResourceList.Card
|
||||||
|
key={`dashboard-id--${id}`}
|
||||||
|
testID="resource-card"
|
||||||
|
name={() => (
|
||||||
|
<ResourceList.Name
|
||||||
|
onUpdate={this.handleUpdateDashboard}
|
||||||
|
name={dashboard.name}
|
||||||
|
hrefValue={`/dashboards/${dashboard.id}`}
|
||||||
|
noNameString={DEFAULT_DASHBOARD_NAME}
|
||||||
|
parentTestID="dashboard-card--name"
|
||||||
|
buttonTestID="dashboard-card--name-button"
|
||||||
|
inputTestID="dashboard-card--input"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
description={() => (
|
||||||
|
<ResourceList.Description
|
||||||
|
onUpdate={this.handleUpdateDescription}
|
||||||
|
description={dashboard.description}
|
||||||
|
placeholder={`Describe ${dashboard.name}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
labels={() => this.labels}
|
||||||
|
updatedAt={dashboard.meta.updatedAt}
|
||||||
|
owner={this.ownerOrg}
|
||||||
|
contextMenu={() => this.contextMenu}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleUpdateDashboard = (name: string) => {
|
||||||
|
this.props.onUpdateDashboard({...this.props.dashboard, name})
|
||||||
|
}
|
||||||
|
|
||||||
|
private get contextMenu(): JSX.Element {
|
||||||
|
const {
|
||||||
|
dashboard,
|
||||||
|
onDeleteDashboard,
|
||||||
|
onExportDashboard,
|
||||||
|
onCloneDashboard,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context>
|
||||||
|
<FeatureFlag>
|
||||||
|
<Context.Menu icon={IconFont.CogThick}>
|
||||||
|
<Context.Item
|
||||||
|
label="Export"
|
||||||
|
action={onExportDashboard}
|
||||||
|
value={dashboard}
|
||||||
|
/>
|
||||||
|
</Context.Menu>
|
||||||
|
</FeatureFlag>
|
||||||
|
<Context.Menu
|
||||||
|
icon={IconFont.Duplicate}
|
||||||
|
color={ComponentColor.Secondary}
|
||||||
|
>
|
||||||
|
<Context.Item
|
||||||
|
label="Clone"
|
||||||
|
action={onCloneDashboard}
|
||||||
|
value={dashboard}
|
||||||
|
/>
|
||||||
|
</Context.Menu>
|
||||||
|
<Context.Menu
|
||||||
|
icon={IconFont.Trash}
|
||||||
|
color={ComponentColor.Danger}
|
||||||
|
testID="context-delete-menu"
|
||||||
|
>
|
||||||
|
<Context.Item
|
||||||
|
label="Delete"
|
||||||
|
action={onDeleteDashboard}
|
||||||
|
value={dashboard}
|
||||||
|
testID="context-delete-dashboard"
|
||||||
|
/>
|
||||||
|
</Context.Menu>
|
||||||
|
</Context>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get labels(): JSX.Element {
|
||||||
|
const {dashboard} = this.props
|
||||||
|
|
||||||
|
if (!dashboard.labels.length) {
|
||||||
|
return (
|
||||||
|
<Label.Container
|
||||||
|
limitChildCount={4}
|
||||||
|
onEdit={this.handleEditLabels}
|
||||||
|
resourceName="this Dashboard"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Label.Container
|
||||||
|
limitChildCount={8}
|
||||||
|
onEdit={this.handleEditLabels}
|
||||||
|
resourceName="this Dashboard"
|
||||||
|
>
|
||||||
|
{dashboard.labels.map((label, index) => (
|
||||||
|
<Label
|
||||||
|
key={label.id || `label-${index}`}
|
||||||
|
id={label.id}
|
||||||
|
colorHex={label.properties.color}
|
||||||
|
name={label.name}
|
||||||
|
description={label.properties.description}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Label.Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleEditLabels = () => {
|
||||||
|
const {dashboard, onEditLabels} = this.props
|
||||||
|
onEditLabels(dashboard)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get ownerOrg(): Organization {
|
||||||
|
const {dashboard, orgs} = this.props
|
||||||
|
return orgs.find(o => o.id === dashboard.orgID)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleUpdateDescription = (description: string): void => {
|
||||||
|
const {onUpdateDashboard} = this.props
|
||||||
|
const dashboard = {...this.props.dashboard, description}
|
||||||
|
|
||||||
|
onUpdateDashboard(dashboard)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import DashboardCard from 'src/dashboards/components/dashboard_index/DashboardCard'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import {Dashboard, Organization} from 'src/types/v2'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
dashboards: Dashboard[]
|
||||||
|
onDeleteDashboard: (dashboard: Dashboard) => void
|
||||||
|
onCloneDashboard: (dashboard: Dashboard) => void
|
||||||
|
onExportDashboard: (dashboard: Dashboard) => void
|
||||||
|
onUpdateDashboard: (dashboard: Dashboard) => void
|
||||||
|
onEditLabels: (dashboard: Dashboard) => void
|
||||||
|
orgs: Organization[]
|
||||||
|
showOwnerColumn: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DashboardCards extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {
|
||||||
|
dashboards,
|
||||||
|
onExportDashboard,
|
||||||
|
onCloneDashboard,
|
||||||
|
onDeleteDashboard,
|
||||||
|
onUpdateDashboard,
|
||||||
|
onEditLabels,
|
||||||
|
orgs,
|
||||||
|
showOwnerColumn,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return dashboards.map(d => (
|
||||||
|
<DashboardCard
|
||||||
|
key={d.id}
|
||||||
|
dashboard={d}
|
||||||
|
onExportDashboard={onExportDashboard}
|
||||||
|
onCloneDashboard={onCloneDashboard}
|
||||||
|
onDeleteDashboard={onDeleteDashboard}
|
||||||
|
onUpdateDashboard={onUpdateDashboard}
|
||||||
|
onEditLabels={onEditLabels}
|
||||||
|
orgs={orgs}
|
||||||
|
showOwnerColumn={showOwnerColumn}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
|
@ -114,10 +114,6 @@ class DashboardIndex extends PureComponent<Props, State> {
|
||||||
<Page.Title title="Dashboards" />
|
<Page.Title title="Dashboards" />
|
||||||
</Page.Header.Left>
|
</Page.Header.Left>
|
||||||
<Page.Header.Right>
|
<Page.Header.Right>
|
||||||
<SearchWidget
|
|
||||||
placeholderText="Filter dashboards by name..."
|
|
||||||
onSearch={this.handleFilterDashboards}
|
|
||||||
/>
|
|
||||||
<AddResourceDropdown
|
<AddResourceDropdown
|
||||||
onSelectNew={this.handleCreateDashboard}
|
onSelectNew={this.handleCreateDashboard}
|
||||||
onSelectImport={this.handleToggleImportOverlay}
|
onSelectImport={this.handleToggleImportOverlay}
|
||||||
|
@ -128,6 +124,12 @@ class DashboardIndex extends PureComponent<Props, State> {
|
||||||
<Page.Contents fullWidth={false} scrollable={true}>
|
<Page.Contents fullWidth={false} scrollable={true}>
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
<DashboardsIndexContents
|
<DashboardsIndexContents
|
||||||
|
filterComponent={() => (
|
||||||
|
<SearchWidget
|
||||||
|
placeholderText="Filter dashboards by name..."
|
||||||
|
onSearch={this.handleFilterDashboards}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
orgs={orgs}
|
orgs={orgs}
|
||||||
dashboards={dashboards}
|
dashboards={dashboards}
|
||||||
onSetDefaultDashboard={this.handleSetDefaultDashboard}
|
onSetDefaultDashboard={this.handleSetDefaultDashboard}
|
||||||
|
|
|
@ -27,6 +27,7 @@ interface Props {
|
||||||
notify: (message: Notification) => void
|
notify: (message: Notification) => void
|
||||||
searchTerm: string
|
searchTerm: string
|
||||||
showOwnerColumn: boolean
|
showOwnerColumn: boolean
|
||||||
|
filterComponent?: () => JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
|
@ -45,6 +46,7 @@ export default class DashboardsIndexContents extends Component<Props> {
|
||||||
orgs,
|
orgs,
|
||||||
showOwnerColumn,
|
showOwnerColumn,
|
||||||
dashboards,
|
dashboards,
|
||||||
|
filterComponent,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -56,6 +58,7 @@ export default class DashboardsIndexContents extends Component<Props> {
|
||||||
>
|
>
|
||||||
{filteredDashboards => (
|
{filteredDashboards => (
|
||||||
<Table
|
<Table
|
||||||
|
filterComponent={filterComponent}
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
dashboards={filteredDashboards}
|
dashboards={filteredDashboards}
|
||||||
onDeleteDashboard={onDeleteDashboard}
|
onDeleteDashboard={onDeleteDashboard}
|
||||||
|
|
|
@ -7,22 +7,17 @@ import _ from 'lodash'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
IconFont,
|
IconFont,
|
||||||
Alignment,
|
|
||||||
ComponentSize,
|
ComponentSize,
|
||||||
ComponentColor,
|
ComponentColor,
|
||||||
} from '@influxdata/clockface'
|
} from '@influxdata/clockface'
|
||||||
import {EmptyState, IndexList} from 'src/clockface'
|
import {EmptyState, ResourceList} from 'src/clockface'
|
||||||
import TableRows from 'src/dashboards/components/dashboard_index/TableRows'
|
import DashboardCards from 'src/dashboards/components/dashboard_index/DashboardCards'
|
||||||
import SortingHat from 'src/shared/components/sorting_hat/SortingHat'
|
import SortingHat from 'src/shared/components/sorting_hat/SortingHat'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import {Sort} from 'src/clockface'
|
import {Sort} from 'src/clockface'
|
||||||
import {Dashboard, Organization} from 'src/types/v2'
|
import {Dashboard, Organization} from 'src/types/v2'
|
||||||
|
|
||||||
// Constants
|
|
||||||
const OWNER_COL_WIDTH = 17
|
|
||||||
const NAME_COL_WIDTH = 63
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
searchTerm: string
|
searchTerm: string
|
||||||
dashboards: Dashboard[]
|
dashboards: Dashboard[]
|
||||||
|
@ -36,6 +31,7 @@ interface Props {
|
||||||
onEditLabels: (dashboard: Dashboard) => void
|
onEditLabels: (dashboard: Dashboard) => void
|
||||||
orgs: Organization[]
|
orgs: Organization[]
|
||||||
showOwnerColumn: boolean
|
showOwnerColumn: boolean
|
||||||
|
filterComponent?: () => JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DatedDashboard extends Dashboard {
|
interface DatedDashboard extends Dashboard {
|
||||||
|
@ -59,36 +55,30 @@ class DashboardsTable extends PureComponent<Props & WithRouterProps, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
const {filterComponent} = this.props
|
||||||
const {sortKey, sortDirection} = this.state
|
const {sortKey, sortDirection} = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IndexList>
|
<ResourceList>
|
||||||
<IndexList.Header>
|
<ResourceList.Header filterComponent={filterComponent}>
|
||||||
<IndexList.HeaderCell
|
<ResourceList.Sorter
|
||||||
columnName={this.headerKeys[0]}
|
name={this.headerKeys[0]}
|
||||||
sortKey={this.headerKeys[0]}
|
sortKey={this.headerKeys[0]}
|
||||||
sort={sortKey === this.headerKeys[0] ? sortDirection : Sort.None}
|
sort={sortKey === this.headerKeys[0] ? sortDirection : Sort.None}
|
||||||
width={this.nameColWidth}
|
|
||||||
onClick={this.handleClickColumn}
|
onClick={this.handleClickColumn}
|
||||||
/>
|
/>
|
||||||
{this.ownerColumnHeader}
|
{this.ownerSorter}
|
||||||
<IndexList.HeaderCell
|
<ResourceList.Sorter
|
||||||
columnName={this.headerKeys[2]}
|
name={this.headerKeys[2]}
|
||||||
sortKey={this.headerKeys[2]}
|
sortKey={this.headerKeys[2]}
|
||||||
sort={sortKey === this.headerKeys[2] ? sortDirection : Sort.None}
|
sort={sortKey === this.headerKeys[2] ? sortDirection : Sort.None}
|
||||||
width="11%"
|
|
||||||
onClick={this.handleClickColumn}
|
onClick={this.handleClickColumn}
|
||||||
/>
|
/>
|
||||||
<IndexList.HeaderCell
|
</ResourceList.Header>
|
||||||
columnName=""
|
<ResourceList.Body emptyState={this.emptyState}>
|
||||||
width="10%"
|
{this.sortedCards}
|
||||||
alignment={Alignment.Right}
|
</ResourceList.Body>
|
||||||
/>
|
</ResourceList>
|
||||||
</IndexList.Header>
|
|
||||||
<IndexList.Body emptyState={this.emptyState} columnCount={5}>
|
|
||||||
{this.sortedRows}
|
|
||||||
</IndexList.Body>
|
|
||||||
</IndexList>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,38 +86,27 @@ class DashboardsTable extends PureComponent<Props & WithRouterProps, State> {
|
||||||
return ['name', 'owner', 'modified', 'default']
|
return ['name', 'owner', 'modified', 'default']
|
||||||
}
|
}
|
||||||
|
|
||||||
private get ownerColumnHeader(): JSX.Element {
|
private get ownerSorter(): JSX.Element {
|
||||||
const {showOwnerColumn} = this.props
|
const {showOwnerColumn} = this.props
|
||||||
const {sortKey, sortDirection} = this.state
|
const {sortKey, sortDirection} = this.state
|
||||||
|
|
||||||
if (showOwnerColumn) {
|
if (showOwnerColumn) {
|
||||||
return (
|
return (
|
||||||
<IndexList.HeaderCell
|
<ResourceList.Sorter
|
||||||
columnName={this.headerKeys[1]}
|
name={this.headerKeys[1]}
|
||||||
sortKey={this.headerKeys[1]}
|
sortKey={this.headerKeys[1]}
|
||||||
sort={sortKey === this.headerKeys[1] ? sortDirection : Sort.None}
|
sort={sortKey === this.headerKeys[1] ? sortDirection : Sort.None}
|
||||||
width={`${OWNER_COL_WIDTH}%`}
|
|
||||||
onClick={this.handleClickColumn}
|
onClick={this.handleClickColumn}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get nameColWidth(): string {
|
|
||||||
const {showOwnerColumn} = this.props
|
|
||||||
|
|
||||||
if (showOwnerColumn) {
|
|
||||||
return `${NAME_COL_WIDTH}%`
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${NAME_COL_WIDTH + OWNER_COL_WIDTH}%`
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleClickColumn = (nextSort: Sort, sortKey: SortKey) => {
|
private handleClickColumn = (nextSort: Sort, sortKey: SortKey) => {
|
||||||
this.setState({sortKey, sortDirection: nextSort})
|
this.setState({sortKey, sortDirection: nextSort})
|
||||||
}
|
}
|
||||||
|
|
||||||
private get sortedRows(): JSX.Element {
|
private get sortedCards(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
dashboards,
|
dashboards,
|
||||||
onExportDashboard,
|
onExportDashboard,
|
||||||
|
@ -149,7 +128,7 @@ class DashboardsTable extends PureComponent<Props & WithRouterProps, State> {
|
||||||
direction={sortDirection}
|
direction={sortDirection}
|
||||||
>
|
>
|
||||||
{ds => (
|
{ds => (
|
||||||
<TableRows
|
<DashboardCards
|
||||||
dashboards={ds}
|
dashboards={ds}
|
||||||
onCloneDashboard={onCloneDashboard}
|
onCloneDashboard={onCloneDashboard}
|
||||||
onExportDashboard={onExportDashboard}
|
onExportDashboard={onExportDashboard}
|
||||||
|
|
|
@ -59,7 +59,7 @@ class SaveAsButton extends PureComponent<Props, State> {
|
||||||
active={saveAsOption === SaveAsOption.Dashboard}
|
active={saveAsOption === SaveAsOption.Dashboard}
|
||||||
value={SaveAsOption.Dashboard}
|
value={SaveAsOption.Dashboard}
|
||||||
onClick={this.handleSetSaveAsOption}
|
onClick={this.handleSetSaveAsOption}
|
||||||
data-test="cell-radio-button"
|
data-testid="cell-radio-button"
|
||||||
>
|
>
|
||||||
Dashboard Cell
|
Dashboard Cell
|
||||||
</Radio.Button>
|
</Radio.Button>
|
||||||
|
@ -67,7 +67,7 @@ class SaveAsButton extends PureComponent<Props, State> {
|
||||||
active={saveAsOption === SaveAsOption.Task}
|
active={saveAsOption === SaveAsOption.Task}
|
||||||
value={SaveAsOption.Task}
|
value={SaveAsOption.Task}
|
||||||
onClick={this.handleSetSaveAsOption}
|
onClick={this.handleSetSaveAsOption}
|
||||||
data-test="task-radio-button"
|
data-testid="task-radio-button"
|
||||||
>
|
>
|
||||||
Task
|
Task
|
||||||
</Radio.Button>
|
</Radio.Button>
|
||||||
|
|
|
@ -36,7 +36,7 @@ exports[`SaveAsButton rendering renders 1`] = `
|
||||||
>
|
>
|
||||||
<RadioButton
|
<RadioButton
|
||||||
active={true}
|
active={true}
|
||||||
data-test="cell-radio-button"
|
data-testid="cell-radio-button"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
disabledTitleText="This option is disabled"
|
disabledTitleText="This option is disabled"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
|
@ -47,7 +47,7 @@ exports[`SaveAsButton rendering renders 1`] = `
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
<RadioButton
|
<RadioButton
|
||||||
active={false}
|
active={false}
|
||||||
data-test="task-radio-button"
|
data-testid="task-radio-button"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
disabledTitleText="This option is disabled"
|
disabledTitleText="This option is disabled"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
|
|
|
@ -42,7 +42,7 @@ describe('DataLoaders.Components.CollectorsWizard.Configure.ConfigFieldHandler',
|
||||||
telegrafPluginsInfo[TelegrafPluginInputCpu.NameEnum.Cpu].fields,
|
telegrafPluginsInfo[TelegrafPluginInputCpu.NameEnum.Cpu].fields,
|
||||||
})
|
})
|
||||||
const noConfig = wrapper.find({
|
const noConfig = wrapper.find({
|
||||||
'data-test': 'no-config',
|
'data-testid': 'no-config',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.exists()).toBe(true)
|
expect(wrapper.exists()).toBe(true)
|
||||||
|
|
|
@ -44,7 +44,7 @@ export class ConfigFieldHandler extends PureComponent<Props> {
|
||||||
const {configFields, telegrafPlugin, onSetConfigArrayValue} = this.props
|
const {configFields, telegrafPlugin, onSetConfigArrayValue} = this.props
|
||||||
|
|
||||||
if (!configFields) {
|
if (!configFields) {
|
||||||
return <p data-test={'no-config'}>No configuration required.</p>
|
return <p data-testid={'no-config'}>No configuration required.</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(configFields).map(
|
return Object.entries(configFields).map(
|
||||||
|
|
|
@ -64,7 +64,7 @@ describe('DataLoaders.Components.CollectorsWizard.Configure.PluginConfigForm', (
|
||||||
telegrafPlugin,
|
telegrafPlugin,
|
||||||
})
|
})
|
||||||
|
|
||||||
const link = wrapper.find({'data-test': 'docs-link'})
|
const link = wrapper.find({'data-testid': 'docs-link'})
|
||||||
|
|
||||||
expect(link.exists()).toBe(true)
|
expect(link.exists()).toBe(true)
|
||||||
expect(link.prop('href')).toContain(telegrafPlugin.name)
|
expect(link.prop('href')).toContain(telegrafPlugin.name)
|
||||||
|
|
|
@ -52,7 +52,7 @@ export class PluginConfigForm extends PureComponent<Props> {
|
||||||
For more information about this plugin, see{' '}
|
For more information about this plugin, see{' '}
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
data-test="docs-link"
|
data-testid="docs-link"
|
||||||
href={`https://github.com/influxdata/telegraf/tree/master/plugins/inputs/${
|
href={`https://github.com/influxdata/telegraf/tree/master/plugins/inputs/${
|
||||||
telegrafPlugin.name
|
telegrafPlugin.name
|
||||||
}`}
|
}`}
|
||||||
|
|
|
@ -21,6 +21,7 @@ exports[`ScraperTarget rendering renders correctly with bucket 1`] = `
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="error"
|
status="error"
|
||||||
|
testID="input-field"
|
||||||
titleText="Name"
|
titleText="Name"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
|
@ -54,6 +55,7 @@ exports[`ScraperTarget rendering renders correctly with bucket 1`] = `
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="error"
|
status="error"
|
||||||
|
testID="input-field"
|
||||||
titleText="Target URL"
|
titleText="Target URL"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
|
@ -85,6 +87,7 @@ exports[`ScraperTarget rendering renders correctly with name 1`] = `
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="default"
|
status="default"
|
||||||
|
testID="input-field"
|
||||||
titleText="Name"
|
titleText="Name"
|
||||||
type="text"
|
type="text"
|
||||||
value="MyScraper"
|
value="MyScraper"
|
||||||
|
@ -118,6 +121,7 @@ exports[`ScraperTarget rendering renders correctly with name 1`] = `
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="error"
|
status="error"
|
||||||
|
testID="input-field"
|
||||||
titleText="Target URL"
|
titleText="Target URL"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
|
@ -149,6 +153,7 @@ exports[`ScraperTarget rendering renders correctly with no bucket, url, name 1`]
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="error"
|
status="error"
|
||||||
|
testID="input-field"
|
||||||
titleText="Name"
|
titleText="Name"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
|
@ -182,6 +187,7 @@ exports[`ScraperTarget rendering renders correctly with no bucket, url, name 1`]
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="error"
|
status="error"
|
||||||
|
testID="input-field"
|
||||||
titleText="Target URL"
|
titleText="Target URL"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
|
@ -213,6 +219,7 @@ exports[`ScraperTarget rendering renders correctly with url 1`] = `
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="error"
|
status="error"
|
||||||
|
testID="input-field"
|
||||||
titleText="Name"
|
titleText="Name"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
|
@ -246,6 +253,7 @@ exports[`ScraperTarget rendering renders correctly with url 1`] = `
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="default"
|
status="default"
|
||||||
|
testID="input-field"
|
||||||
titleText="Target URL"
|
titleText="Target URL"
|
||||||
type="text"
|
type="text"
|
||||||
value="http://url.com"
|
value="http://url.com"
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe('Account', () => {
|
||||||
it('displays the users info by default', () => {
|
it('displays the users info by default', () => {
|
||||||
const {wrapper} = setup()
|
const {wrapper} = setup()
|
||||||
|
|
||||||
const nameInput = wrapper.find({'data-test': 'nameInput'})
|
const nameInput = wrapper.find({'data-testid': 'nameInput'})
|
||||||
expect(nameInput.props().value).toBe(me.name)
|
expect(nameInput.props().value).toBe(me.name)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -44,7 +44,7 @@ export class Settings extends PureComponent<StateProps, State> {
|
||||||
<Form.Element label="Username">
|
<Form.Element label="Username">
|
||||||
<Input
|
<Input
|
||||||
value={me.name}
|
value={me.name}
|
||||||
dataTest="nameInput"
|
testID="nameInput"
|
||||||
titleText="Username"
|
titleText="Username"
|
||||||
size={ComponentSize.Small}
|
size={ComponentSize.Small}
|
||||||
status={ComponentStatus.Disabled}
|
status={ComponentStatus.Disabled}
|
||||||
|
|
|
@ -28,7 +28,7 @@ export default class TokenRow extends PureComponent<Props> {
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
onClick={this.handleClickDescription}
|
onClick={this.handleClickDescription}
|
||||||
data-test={`token-description-${id}`}
|
data-testid={`token-description-${id}`}
|
||||||
>
|
>
|
||||||
{description}
|
{description}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -56,7 +56,7 @@ describe('Account', () => {
|
||||||
describe('clicking the token description', () => {
|
describe('clicking the token description', () => {
|
||||||
it('opens the ViewTokenModal', () => {
|
it('opens the ViewTokenModal', () => {
|
||||||
const description = wrapper.find({
|
const description = wrapper.find({
|
||||||
'data-test': `token-description-${1}`,
|
'data-testid': `token-description-${1}`,
|
||||||
})
|
})
|
||||||
description.simulate('click')
|
description.simulate('click')
|
||||||
wrapper.update()
|
wrapper.update()
|
||||||
|
|
|
@ -111,7 +111,6 @@ exports[`Account rendering renders! 1`] = `
|
||||||
<Input
|
<Input
|
||||||
autoFocus={false}
|
autoFocus={false}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
dataTest="nameInput"
|
|
||||||
disabledTitleText="This input is disabled"
|
disabledTitleText="This input is disabled"
|
||||||
name=""
|
name=""
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
|
@ -119,7 +118,9 @@ exports[`Account rendering renders! 1`] = `
|
||||||
size="sm"
|
size="sm"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="disabled"
|
status="disabled"
|
||||||
|
testID="nameInput"
|
||||||
titleText="Username"
|
titleText="Username"
|
||||||
|
type="text"
|
||||||
value="groot"
|
value="groot"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -134,13 +135,14 @@ exports[`Account rendering renders! 1`] = `
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoFocus={false}
|
autoFocus={false}
|
||||||
className="input-field"
|
className="input-field"
|
||||||
data-test="nameInput"
|
data-testid="nameInput"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
name=""
|
name=""
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
placeholder=""
|
placeholder=""
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
title="This input is disabled"
|
title="This input is disabled"
|
||||||
|
type="text"
|
||||||
value="groot"
|
value="groot"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -19,7 +19,9 @@ exports[`Account rendering renders! 1`] = `
|
||||||
size="sm"
|
size="sm"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="default"
|
status="default"
|
||||||
|
testID="input-field"
|
||||||
titleText=""
|
titleText=""
|
||||||
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
widthPixels={256}
|
widthPixels={256}
|
||||||
>
|
>
|
||||||
|
@ -35,12 +37,14 @@ exports[`Account rendering renders! 1`] = `
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoFocus={false}
|
autoFocus={false}
|
||||||
className="input-field"
|
className="input-field"
|
||||||
|
data-testid="input-field"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
name=""
|
name=""
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
placeholder="Filter Tokens..."
|
placeholder="Filter Tokens..."
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
title=""
|
title=""
|
||||||
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
|
@ -244,6 +248,7 @@ exports[`Account rendering renders! 1`] = `
|
||||||
emptyState={
|
emptyState={
|
||||||
<EmptyState
|
<EmptyState
|
||||||
size="lg"
|
size="lg"
|
||||||
|
testID="empty-state"
|
||||||
>
|
>
|
||||||
<EmptyStateText
|
<EmptyStateText
|
||||||
text="There are not any Tokens associated with this account. Contact your administrator"
|
text="There are not any Tokens associated with this account. Contact your administrator"
|
||||||
|
@ -306,7 +311,7 @@ exports[`Account rendering renders! 1`] = `
|
||||||
className="index-list--cell"
|
className="index-list--cell"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
data-test="token-description-1"
|
data-testid="token-description-1"
|
||||||
href="#"
|
href="#"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
>
|
>
|
||||||
|
@ -426,7 +431,7 @@ exports[`Account rendering renders! 1`] = `
|
||||||
className="index-list--cell"
|
className="index-list--cell"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
data-test="token-description-2"
|
data-testid="token-description-2"
|
||||||
href="#"
|
href="#"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
>
|
>
|
||||||
|
@ -506,7 +511,7 @@ exports[`Account rendering renders! 1`] = `
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="overlay--dialog"
|
className="overlay--dialog"
|
||||||
data-test="overlay-children"
|
data-testid="overlay-children"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="overlay--mask"
|
className="overlay--mask"
|
||||||
|
|
|
@ -32,8 +32,8 @@ describe('Onboarding.Components.OnboardingButtons', () => {
|
||||||
onClickBack,
|
onClickBack,
|
||||||
})
|
})
|
||||||
|
|
||||||
const nextButton = wrapper.find('[data-test="next"]')
|
const nextButton = wrapper.find('[data-testid="next"]')
|
||||||
const backButton = wrapper.find('[data-test="back"]')
|
const backButton = wrapper.find('[data-testid="back"]')
|
||||||
|
|
||||||
backButton.simulate('click')
|
backButton.simulate('click')
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ describe('Onboarding.Components.OnboardingButtons', () => {
|
||||||
onClickSkip,
|
onClickSkip,
|
||||||
})
|
})
|
||||||
|
|
||||||
const skipButton = wrapper.find('[data-test="skip"]')
|
const skipButton = wrapper.find('[data-testid="skip"]')
|
||||||
skipButton.simulate('click')
|
skipButton.simulate('click')
|
||||||
|
|
||||||
expect(skipButton.exists()).toBe(true)
|
expect(skipButton.exists()).toBe(true)
|
||||||
|
|
|
@ -61,7 +61,7 @@ class OnboardingButtons extends PureComponent<Props> {
|
||||||
text={nextButtonText}
|
text={nextButtonText}
|
||||||
size={ComponentSize.Medium}
|
size={ComponentSize.Medium}
|
||||||
type={ButtonType.Submit}
|
type={ButtonType.Submit}
|
||||||
data-test="next"
|
data-testid="next"
|
||||||
ref={this.submitRef}
|
ref={this.submitRef}
|
||||||
status={nextButtonStatus}
|
status={nextButtonStatus}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
@ -89,7 +89,7 @@ class OnboardingButtons extends PureComponent<Props> {
|
||||||
text={backButtonText}
|
text={backButtonText}
|
||||||
size={ComponentSize.Medium}
|
size={ComponentSize.Medium}
|
||||||
onClick={onClickBack}
|
onClick={onClickBack}
|
||||||
data-test="back"
|
data-testid="back"
|
||||||
tabIndex={1}
|
tabIndex={1}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -109,7 +109,7 @@ class OnboardingButtons extends PureComponent<Props> {
|
||||||
color={ComponentColor.Default}
|
color={ComponentColor.Default}
|
||||||
text={skipButtonText}
|
text={skipButtonText}
|
||||||
onClick={onClickSkip}
|
onClick={onClickSkip}
|
||||||
data-test="skip"
|
data-testid="skip"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -52,7 +52,9 @@ exports[`Onboarding.Components.AdminStep renders 1`] = `
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="default"
|
status="default"
|
||||||
|
testID="input-field"
|
||||||
titleText="Username"
|
titleText="Username"
|
||||||
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
</FormElement>
|
</FormElement>
|
||||||
|
@ -76,6 +78,7 @@ exports[`Onboarding.Components.AdminStep renders 1`] = `
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="default"
|
status="default"
|
||||||
|
testID="input-field"
|
||||||
titleText="Password"
|
titleText="Password"
|
||||||
type="password"
|
type="password"
|
||||||
value=""
|
value=""
|
||||||
|
@ -101,6 +104,7 @@ exports[`Onboarding.Components.AdminStep renders 1`] = `
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="default"
|
status="default"
|
||||||
|
testID="input-field"
|
||||||
titleText="Confirm Password"
|
titleText="Confirm Password"
|
||||||
type="password"
|
type="password"
|
||||||
value=""
|
value=""
|
||||||
|
@ -127,7 +131,9 @@ exports[`Onboarding.Components.AdminStep renders 1`] = `
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="default"
|
status="default"
|
||||||
|
testID="input-field"
|
||||||
titleText="Initial Organization Name"
|
titleText="Initial Organization Name"
|
||||||
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
</FormElement>
|
</FormElement>
|
||||||
|
@ -152,7 +158,9 @@ exports[`Onboarding.Components.AdminStep renders 1`] = `
|
||||||
size="md"
|
size="md"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="default"
|
status="default"
|
||||||
|
testID="input-field"
|
||||||
titleText="Initial Bucket Name"
|
titleText="Initial Bucket Name"
|
||||||
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
</FormElement>
|
</FormElement>
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
ButtonShape,
|
ButtonShape,
|
||||||
ComponentSize,
|
ComponentSize,
|
||||||
ComponentColor,
|
ComponentColor,
|
||||||
|
IconFont,
|
||||||
} from '@influxdata/clockface'
|
} from '@influxdata/clockface'
|
||||||
import {Bucket} from '@influxdata/influx'
|
import {Bucket} from '@influxdata/influx'
|
||||||
import {DataLoaderType} from 'src/types/v2/dataLoaders'
|
import {DataLoaderType} from 'src/types/v2/dataLoaders'
|
||||||
|
@ -57,6 +58,7 @@ export default class BucketRow extends PureComponent<Props> {
|
||||||
<IndexList.Cell alignment={Alignment.Right}>
|
<IndexList.Cell alignment={Alignment.Right}>
|
||||||
<Context align={Alignment.Center}>
|
<Context align={Alignment.Center}>
|
||||||
<Context.Menu
|
<Context.Menu
|
||||||
|
icon={IconFont.Plus}
|
||||||
text="Add Data"
|
text="Add Data"
|
||||||
shape={ButtonShape.Default}
|
shape={ButtonShape.Default}
|
||||||
color={ComponentColor.Primary}
|
color={ComponentColor.Primary}
|
||||||
|
|
|
@ -64,6 +64,7 @@ export default class CreateOrgOverlay extends PureComponent<Props, State> {
|
||||||
value={org.name}
|
value={org.name}
|
||||||
onChange={this.handleChangeInput}
|
onChange={this.handleChangeInput}
|
||||||
status={nameInputStatus}
|
status={nameInputStatus}
|
||||||
|
testID="create-org-name-input"
|
||||||
/>
|
/>
|
||||||
</Form.Element>
|
</Form.Element>
|
||||||
</OverlayBody>
|
</OverlayBody>
|
||||||
|
@ -74,6 +75,7 @@ export default class CreateOrgOverlay extends PureComponent<Props, State> {
|
||||||
type={ButtonType.Submit}
|
type={ButtonType.Submit}
|
||||||
color={ComponentColor.Primary}
|
color={ComponentColor.Primary}
|
||||||
status={this.submitButtonStatus}
|
status={this.submitButtonStatus}
|
||||||
|
testID="create-org-submit-button"
|
||||||
/>
|
/>
|
||||||
</OverlayFooter>
|
</OverlayFooter>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
@ -95,7 +95,6 @@ Object {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="confirmation-button--tooltip-body"
|
class="confirmation-button--tooltip-body"
|
||||||
data-test="confirmation-button--click-target"
|
|
||||||
data-testid="confirmation-button"
|
data-testid="confirmation-button"
|
||||||
>
|
>
|
||||||
Confirm
|
Confirm
|
||||||
|
@ -118,11 +117,14 @@ Object {
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
||||||
data-testid="button"
|
data-testid="context-menu"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
title="Add Data"
|
title="Add Data"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="button-icon icon plus"
|
||||||
|
/>
|
||||||
Add Data
|
Add Data
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
|
@ -133,6 +135,7 @@ Object {
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Configure Telegraf Agent
|
Configure Telegraf Agent
|
||||||
<div
|
<div
|
||||||
|
@ -143,6 +146,7 @@ Object {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Line Protocol
|
Line Protocol
|
||||||
<div
|
<div
|
||||||
|
@ -153,6 +157,7 @@ Object {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Scrape Metrics
|
Scrape Metrics
|
||||||
<div
|
<div
|
||||||
|
@ -228,7 +233,6 @@ Object {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="confirmation-button--tooltip-body"
|
class="confirmation-button--tooltip-body"
|
||||||
data-test="confirmation-button--click-target"
|
|
||||||
data-testid="confirmation-button"
|
data-testid="confirmation-button"
|
||||||
>
|
>
|
||||||
Confirm
|
Confirm
|
||||||
|
@ -251,11 +255,14 @@ Object {
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
||||||
data-testid="button"
|
data-testid="context-menu"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
title="Add Data"
|
title="Add Data"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="button-icon icon plus"
|
||||||
|
/>
|
||||||
Add Data
|
Add Data
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
|
@ -266,6 +273,7 @@ Object {
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Configure Telegraf Agent
|
Configure Telegraf Agent
|
||||||
<div
|
<div
|
||||||
|
@ -276,6 +284,7 @@ Object {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Line Protocol
|
Line Protocol
|
||||||
<div
|
<div
|
||||||
|
@ -286,6 +295,7 @@ Object {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Scrape Metrics
|
Scrape Metrics
|
||||||
<div
|
<div
|
||||||
|
@ -308,7 +318,7 @@ Object {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="overlay--dialog"
|
class="overlay--dialog"
|
||||||
data-test="overlay-children"
|
data-testid="overlay-children"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="overlay--mask"
|
class="overlay--mask"
|
||||||
|
@ -319,7 +329,7 @@ Object {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="overlay--dialog"
|
class="overlay--dialog"
|
||||||
data-test="overlay-children"
|
data-testid="overlay-children"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="overlay--mask"
|
class="overlay--mask"
|
||||||
|
@ -418,7 +428,6 @@ Object {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="confirmation-button--tooltip-body"
|
class="confirmation-button--tooltip-body"
|
||||||
data-test="confirmation-button--click-target"
|
|
||||||
data-testid="confirmation-button"
|
data-testid="confirmation-button"
|
||||||
>
|
>
|
||||||
Confirm
|
Confirm
|
||||||
|
@ -441,11 +450,14 @@ Object {
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
||||||
data-testid="button"
|
data-testid="context-menu"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
title="Add Data"
|
title="Add Data"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="button-icon icon plus"
|
||||||
|
/>
|
||||||
Add Data
|
Add Data
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
|
@ -456,6 +468,7 @@ Object {
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Configure Telegraf Agent
|
Configure Telegraf Agent
|
||||||
<div
|
<div
|
||||||
|
@ -466,6 +479,7 @@ Object {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Line Protocol
|
Line Protocol
|
||||||
<div
|
<div
|
||||||
|
@ -476,6 +490,7 @@ Object {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Scrape Metrics
|
Scrape Metrics
|
||||||
<div
|
<div
|
||||||
|
@ -551,7 +566,6 @@ Object {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="confirmation-button--tooltip-body"
|
class="confirmation-button--tooltip-body"
|
||||||
data-test="confirmation-button--click-target"
|
|
||||||
data-testid="confirmation-button"
|
data-testid="confirmation-button"
|
||||||
>
|
>
|
||||||
Confirm
|
Confirm
|
||||||
|
@ -574,11 +588,14 @@ Object {
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
||||||
data-testid="button"
|
data-testid="context-menu"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
title="Add Data"
|
title="Add Data"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="button-icon icon plus"
|
||||||
|
/>
|
||||||
Add Data
|
Add Data
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
|
@ -589,6 +606,7 @@ Object {
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Configure Telegraf Agent
|
Configure Telegraf Agent
|
||||||
<div
|
<div
|
||||||
|
@ -599,6 +617,7 @@ Object {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Line Protocol
|
Line Protocol
|
||||||
<div
|
<div
|
||||||
|
@ -609,6 +628,7 @@ Object {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="context-menu--item"
|
class="context-menu--item"
|
||||||
|
data-testid="context-menu-item"
|
||||||
>
|
>
|
||||||
Scrape Metrics
|
Scrape Metrics
|
||||||
<div
|
<div
|
||||||
|
@ -631,7 +651,7 @@ Object {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="overlay--dialog"
|
class="overlay--dialog"
|
||||||
data-test="overlay-children"
|
data-testid="overlay-children"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="overlay--mask"
|
class="overlay--mask"
|
||||||
|
@ -642,7 +662,7 @@ Object {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="overlay--dialog"
|
class="overlay--dialog"
|
||||||
data-test="overlay-children"
|
data-testid="overlay-children"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="overlay--mask"
|
class="overlay--mask"
|
||||||
|
|
|
@ -73,6 +73,7 @@ class OrganizationsIndex extends PureComponent<Props, State> {
|
||||||
icon={IconFont.Plus}
|
icon={IconFont.Plus}
|
||||||
text="Create Organization"
|
text="Create Organization"
|
||||||
titleText="Create a new Organization"
|
titleText="Create a new Organization"
|
||||||
|
testID="create-org-button"
|
||||||
/>
|
/>
|
||||||
</Page.Header.Right>
|
</Page.Header.Right>
|
||||||
</Page.Header>
|
</Page.Header>
|
||||||
|
|
|
@ -73,7 +73,7 @@ const DropdownMenu: SFC<Props> = ({
|
||||||
[menuClass]: menuClass,
|
[menuClass]: menuClass,
|
||||||
})}
|
})}
|
||||||
style={{width: menuWidth}}
|
style={{width: menuWidth}}
|
||||||
data-test="dropdown-ul"
|
data-testid="dropdown-ul"
|
||||||
>
|
>
|
||||||
<FancyScrollbar
|
<FancyScrollbar
|
||||||
autoHide={false}
|
autoHide={false}
|
||||||
|
|
|
@ -52,7 +52,7 @@ const DropdownMenuItem: SFC<ItemProps> = ({
|
||||||
highlight: index === highlightedItemIndex,
|
highlight: index === highlightedItemIndex,
|
||||||
active: item.text === selected,
|
active: item.text === selected,
|
||||||
})}
|
})}
|
||||||
data-test="dropdown-item"
|
data-testid="dropdown-item"
|
||||||
>
|
>
|
||||||
<a href="#" onClick={onSelection(item)} onMouseOver={onHighlight(index)}>
|
<a href="#" onClick={onSelection(item)} onMouseOver={onHighlight(index)}>
|
||||||
{item.text}
|
{item.text}
|
||||||
|
|
|
@ -37,7 +37,7 @@ describe('Components.Shared.InputClickToEdit', () => {
|
||||||
const inputField = inputClickToEdit.children().find('input')
|
const inputField = inputClickToEdit.children().find('input')
|
||||||
const disabledDiv = inputClickToEdit
|
const disabledDiv = inputClickToEdit
|
||||||
.children()
|
.children()
|
||||||
.find({'data-test': 'disabled'})
|
.find({'data-testid': 'disabled'})
|
||||||
|
|
||||||
expect(initialDiv.exists()).toBe(true)
|
expect(initialDiv.exists()).toBe(true)
|
||||||
expect(inputField.exists()).toBe(false)
|
expect(inputField.exists()).toBe(false)
|
||||||
|
@ -51,7 +51,7 @@ describe('Components.Shared.InputClickToEdit', () => {
|
||||||
const inputField = inputClickToEdit.children().find('input')
|
const inputField = inputClickToEdit.children().find('input')
|
||||||
const disabledDiv = inputClickToEdit
|
const disabledDiv = inputClickToEdit
|
||||||
.children()
|
.children()
|
||||||
.find({'data-test': 'disabled'})
|
.find({'data-testid': 'disabled'})
|
||||||
|
|
||||||
expect(inputField.exists()).toBe(false)
|
expect(inputField.exists()).toBe(false)
|
||||||
expect(disabledDiv.exists()).toBe(true)
|
expect(disabledDiv.exists()).toBe(true)
|
||||||
|
@ -62,9 +62,9 @@ describe('Components.Shared.InputClickToEdit', () => {
|
||||||
const {inputClickToEdit} = setup({disabled})
|
const {inputClickToEdit} = setup({disabled})
|
||||||
const disabledDiv = inputClickToEdit
|
const disabledDiv = inputClickToEdit
|
||||||
.children()
|
.children()
|
||||||
.find({'data-test': 'disabled'})
|
.find({'data-testid': 'disabled'})
|
||||||
const icon = disabledDiv.children().find({
|
const icon = disabledDiv.children().find({
|
||||||
'data-test': 'icon',
|
'data-testid': 'icon',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(icon.exists()).toBe(false)
|
expect(icon.exists()).toBe(false)
|
||||||
|
|
|
@ -116,7 +116,7 @@ class InputClickToEdit extends PureComponent<Props, State> {
|
||||||
|
|
||||||
return disabled ? (
|
return disabled ? (
|
||||||
<div className={wrapperClass}>
|
<div className={wrapperClass}>
|
||||||
<div data-test="disabled" className="input-cte__disabled">
|
<div data-testid="disabled" className="input-cte__disabled">
|
||||||
{value}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -144,7 +144,7 @@ class InputClickToEdit extends PureComponent<Props, State> {
|
||||||
>
|
>
|
||||||
<span className="input-cte-span">{value || placeholder}</span>
|
<span className="input-cte-span">{value || placeholder}</span>
|
||||||
{appearAsNormalInput || (
|
{appearAsNormalInput || (
|
||||||
<span data-test="icon" className="icon pencil" />
|
<span data-testid="icon" className="icon pencil" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -67,14 +67,14 @@ describe('SubSections', () => {
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
it('renders the currently active tab', () => {
|
it('renders the currently active tab', () => {
|
||||||
const wrapper = setup()
|
const wrapper = setup()
|
||||||
const content = wrapper.dive().find({'data-test': 'subsectionContent'})
|
const content = wrapper.dive().find({'data-testid': 'subsectionContent'})
|
||||||
|
|
||||||
expect(content.find(Guava).exists()).toBe(true)
|
expect(content.find(Guava).exists()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('only renders enabled tabs', () => {
|
it('only renders enabled tabs', () => {
|
||||||
const wrapper = setup()
|
const wrapper = setup()
|
||||||
const nav = wrapper.dive().find({'data-test': 'subsectionNav'})
|
const nav = wrapper.dive().find({'data-testid': 'subsectionNav'})
|
||||||
|
|
||||||
const tabs = nav.find(SubSectionsTab)
|
const tabs = nav.find(SubSectionsTab)
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ class SubSections extends Component<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row subsection">
|
<div className="row subsection">
|
||||||
<div className="col-md-2 subsection--nav" data-test="subsectionNav">
|
<div className="col-md-2 subsection--nav" data-testid="subsectionNav">
|
||||||
<div className="subsection--tabs">
|
<div className="subsection--tabs">
|
||||||
{sections.map(
|
{sections.map(
|
||||||
section =>
|
section =>
|
||||||
|
@ -42,7 +42,7 @@ class SubSections extends Component<Props> {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="col-md-10 subsection--content"
|
className="col-md-10 subsection--content"
|
||||||
data-test="subsectionContent"
|
data-testid="subsectionContent"
|
||||||
>
|
>
|
||||||
{this.activeSectionComponent}
|
{this.activeSectionComponent}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -65,7 +65,7 @@ class Notification extends Component<Props, State> {
|
||||||
<div
|
<div
|
||||||
className={this.notificationClassname}
|
className={this.notificationClassname}
|
||||||
ref={this.handleNotificationRef}
|
ref={this.handleNotificationRef}
|
||||||
data-testid={this.dataTest}
|
data-testid={this.dataTestID}
|
||||||
>
|
>
|
||||||
<span className={`icon ${icon}`} />
|
<span className={`icon ${icon}`} />
|
||||||
<div className="notification-message">{message}</div>
|
<div className="notification-message">{message}</div>
|
||||||
|
@ -75,7 +75,7 @@ class Notification extends Component<Props, State> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private get dataTest(): string {
|
private get dataTestID(): string {
|
||||||
const {type} = this.props.notification
|
const {type} = this.props.notification
|
||||||
return `notification-${type}`
|
return `notification-${type}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,9 @@ exports[`TaskForm rendering renders 1`] = `
|
||||||
size="sm"
|
size="sm"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
status="default"
|
status="default"
|
||||||
|
testID="input-field"
|
||||||
titleText=""
|
titleText=""
|
||||||
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
</FormElement>
|
</FormElement>
|
||||||
|
|
|
@ -106,7 +106,7 @@ class TimeFormat extends PureComponent<Props, State> {
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
placeholder="Enter custom format..."
|
placeholder="Enter custom format..."
|
||||||
value={format}
|
value={format}
|
||||||
data-test="custom-time-format"
|
data-testid="custom-time-format"
|
||||||
customClass="custom-time-format"
|
customClass="custom-time-format"
|
||||||
onChange={this.handleChangeFormat}
|
onChange={this.handleChangeFormat}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in New Issue