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', () => {
|
||||
it('can create a bucket', () => {
|
||||
const newBucket = '🅱️ucket'
|
||||
cy.getByDataTest('table-row').should('have.length', 1)
|
||||
cy.getByTestID('table-row').should('have.length', 1)
|
||||
|
||||
cy.contains('Create').click()
|
||||
cy.getByDataTest('overlay--container').within(() => {
|
||||
cy.getByTestID('overlay--container').within(() => {
|
||||
cy.getByInputName('name').type(newBucket)
|
||||
cy.get('.button')
|
||||
.contains('Create')
|
||||
.click()
|
||||
})
|
||||
|
||||
cy.getByDataTest('table-row')
|
||||
cy.getByTestID('table-row')
|
||||
.should('have.length', 2)
|
||||
.and('contain', newBucket)
|
||||
})
|
||||
|
@ -44,14 +44,14 @@ describe('Buckets', () => {
|
|||
cy.contains(name).click()
|
||||
})
|
||||
|
||||
cy.getByDataTest('retention-intervals').click()
|
||||
cy.getByTestID('retention-intervals').click()
|
||||
|
||||
cy.getByInputName('days').type('{uparrow}')
|
||||
cy.getByInputName('hours').type('{uparrow}')
|
||||
cy.getByInputName('minutes').type('{uparrow}')
|
||||
cy.getByInputName('seconds').type('{uparrow}')
|
||||
|
||||
cy.getByDataTest('overlay--container').within(() => {
|
||||
cy.getByTestID('overlay--container').within(() => {
|
||||
cy.getByInputName('name')
|
||||
.clear()
|
||||
.type(newName)
|
||||
|
@ -59,7 +59,7 @@ describe('Buckets', () => {
|
|||
cy.contains('Save').click()
|
||||
})
|
||||
|
||||
cy.getByDataTest('table-row')
|
||||
cy.getByTestID('table-row')
|
||||
.should('contain', '1 day')
|
||||
.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.get('button[type=submit]').click()
|
||||
|
||||
cy.getByDataTest('nav').should('exist')
|
||||
cy.getByTestID('nav').should('exist')
|
||||
})
|
||||
|
||||
describe('login failure', () => {
|
||||
|
@ -30,14 +30,14 @@ describe('The Login Page', () => {
|
|||
cy.getByInputName('password').type(user.password)
|
||||
cy.get('button[type=submit]').click()
|
||||
|
||||
cy.getByDataTest('notification-error').should('exist')
|
||||
cy.getByTestID('notification-error').should('exist')
|
||||
})
|
||||
|
||||
it('if password is not present', () => {
|
||||
cy.getByInputName('username').type(user.username)
|
||||
cy.get('button[type=submit]').click()
|
||||
|
||||
cy.getByDataTest('notification-error').should('exist')
|
||||
cy.getByTestID('notification-error').should('exist')
|
||||
})
|
||||
|
||||
it('if username is incorrect', () => {
|
||||
|
@ -45,7 +45,7 @@ describe('The Login Page', () => {
|
|||
cy.getByInputName('password').type(user.password)
|
||||
cy.get('button[type=submit]').click()
|
||||
|
||||
cy.getByDataTest('notification-error').should('exist')
|
||||
cy.getByTestID('notification-error').should('exist')
|
||||
})
|
||||
|
||||
it('if password is incorrect', () => {
|
||||
|
@ -53,7 +53,7 @@ describe('The Login Page', () => {
|
|||
cy.getByInputName('password').type('not-a-password')
|
||||
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')
|
||||
.should('be.eq', 1)
|
||||
|
||||
cy.get('.page-header--right > .button')
|
||||
.contains('Create')
|
||||
.click()
|
||||
cy.getByTestID('create-org-button').click()
|
||||
|
||||
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')
|
||||
.should('contain', orgName)
|
|
@ -22,7 +22,7 @@ describe('Tasks', () => {
|
|||
cy.getByInputName('interval').type('1d')
|
||||
cy.getByInputName('offset').type('20m')
|
||||
|
||||
cy.getByDataTest('flux-editor').within(() => {
|
||||
cy.getByTestID('flux-editor').within(() => {
|
||||
cy.get('textarea').type(
|
||||
`from(bucket: "defbuck")
|
||||
|> range(start: -2m)`,
|
||||
|
@ -32,7 +32,7 @@ describe('Tasks', () => {
|
|||
|
||||
cy.contains('Save').click()
|
||||
|
||||
cy.getByDataTest('task-row')
|
||||
cy.getByTestID('task-row')
|
||||
.should('have.length', 1)
|
||||
.and('contain', taskName)
|
||||
})
|
||||
|
@ -43,13 +43,13 @@ describe('Tasks', () => {
|
|||
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()
|
||||
.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', () => {
|
||||
|
@ -61,12 +61,12 @@ describe('Tasks', () => {
|
|||
cy.getByInputName('interval').type('1d')
|
||||
cy.getByInputName('offset').type('20m')
|
||||
|
||||
cy.getByDataTest('flux-editor').within(() => {
|
||||
cy.getByTestID('flux-editor').within(() => {
|
||||
cy.get('textarea').type('{}', {force: true})
|
||||
})
|
||||
|
||||
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.getByDataTest('flux-editor').within(() => {
|
||||
cy.getByTestID('flux-editor').within(() => {
|
||||
cy.get('textarea').type('filter(fn: (r) => r._field == "cpu")', {
|
||||
force: true,
|
||||
})
|
||||
|
@ -26,6 +26,6 @@ describe('Variables', () => {
|
|||
.contains('Create')
|
||||
.click()
|
||||
|
||||
cy.getByDataTest('variable-row').should('have.length', 1)
|
||||
cy.getByTestID('variable-row').should('have.length', 1)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
createOrg,
|
||||
createSource,
|
||||
flush,
|
||||
getByDataTest,
|
||||
getByTestID,
|
||||
getByInputName,
|
||||
getByTitle,
|
||||
createTask,
|
||||
|
@ -22,7 +22,7 @@ declare global {
|
|||
createDashboard: typeof createDashboard
|
||||
createOrg: typeof createOrg
|
||||
flush: typeof flush
|
||||
getByDataTest: typeof getByDataTest
|
||||
getByTestID: typeof getByTestID
|
||||
getByInputName: typeof getByInputName
|
||||
getByTitle: typeof getByTitle
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ export const flush = () => {
|
|||
}
|
||||
|
||||
// DOM node getters
|
||||
export const getByDataTest = (dataTest: string): Cypress.Chainable => {
|
||||
export const getByTestID = (dataTest: string): Cypress.Chainable => {
|
||||
return cy.get(`[data-testid="${dataTest}"]`)
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ export const getByTitle = (name: string): Cypress.Chainable => {
|
|||
}
|
||||
|
||||
// getters
|
||||
Cypress.Commands.add('getByDataTest', getByDataTest)
|
||||
Cypress.Commands.add('getByTestID', getByTestID)
|
||||
Cypress.Commands.add('getByInputName', getByInputName)
|
||||
Cypress.Commands.add('getByTitle', getByTitle)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, {SFC} from 'react'
|
||||
|
||||
const MockChild: SFC = () => <div data-test="mock-child" />
|
||||
const MockChild: SFC = () => <div data-testid="mock-child" />
|
||||
|
||||
export default MockChild
|
||||
|
|
|
@ -10500,6 +10500,11 @@
|
|||
"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": {
|
||||
"version": "2.22.2",
|
||||
"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
|
||||
data-test="confirmation-button--click-target"
|
||||
data-testid={testID}
|
||||
className="confirmation-button--tooltip-body"
|
||||
onClick={this.handleTooltipClick}
|
||||
|
|
|
@ -44,9 +44,7 @@ describe('ConfirmationButton', () => {
|
|||
|
||||
wrapper.find(Button).simulate('click')
|
||||
|
||||
wrapper
|
||||
.find({'data-test': 'confirmation-button--click-target'})
|
||||
.simulate('click')
|
||||
wrapper.find({'data-testid': 'confirmation-button'}).simulate('click')
|
||||
|
||||
expect(onConfirm.mock.results[0].value).toBe(returnValue)
|
||||
})
|
||||
|
|
|
@ -44,7 +44,6 @@ exports[`ConfirmationButton interaction shows the tooltip when clicked 1`] = `
|
|||
>
|
||||
<div
|
||||
className="confirmation-button--tooltip-body"
|
||||
data-test="confirmation-button--click-target"
|
||||
data-testid="confirmation-button"
|
||||
onClick={[Function]}
|
||||
>
|
||||
|
|
|
@ -55,7 +55,7 @@ class Context extends PureComponent<Props, State> {
|
|||
/>
|
||||
)
|
||||
} else {
|
||||
throw new Error('Expected children of type <Context.Menu />')
|
||||
return child
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
|
|
|
@ -17,26 +17,32 @@ import {
|
|||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
interface PassedProps {
|
||||
children: JSX.Element | JSX.Element[]
|
||||
icon: IconFont
|
||||
text?: string
|
||||
title
|
||||
color?: ComponentColor
|
||||
shape?: ButtonShape
|
||||
onBoostZIndex?: (boostZIndex: boolean) => void
|
||||
}
|
||||
|
||||
interface DefaultProps {
|
||||
text?: string
|
||||
color?: ComponentColor
|
||||
shape?: ButtonShape
|
||||
testID?: string
|
||||
}
|
||||
|
||||
type Props = PassedProps & DefaultProps
|
||||
|
||||
interface State {
|
||||
isExpanded: boolean
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class ContextMenu extends Component<Props, State> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
public static defaultProps: DefaultProps = {
|
||||
color: ComponentColor.Primary,
|
||||
shape: ButtonShape.Square,
|
||||
text: '',
|
||||
testID: 'context-menu',
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
|
@ -48,7 +54,7 @@ class ContextMenu extends Component<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {icon, text, shape, color} = this.props
|
||||
const {icon, text, shape, color, testID} = this.props
|
||||
|
||||
return (
|
||||
<ClickOutside onClickOutside={this.handleCollapseMenu}>
|
||||
|
@ -61,6 +67,7 @@ class ContextMenu extends Component<Props, State> {
|
|||
icon={icon}
|
||||
size={ComponentSize.ExtraSmall}
|
||||
color={color}
|
||||
testID={testID}
|
||||
/>
|
||||
{this.menu}
|
||||
</div>
|
||||
|
|
|
@ -2,24 +2,36 @@
|
|||
import React, {Component} from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
interface PassedProps {
|
||||
label: string
|
||||
description?: string
|
||||
action: (value?: any) => void
|
||||
value?: any
|
||||
onCollapseMenu?: () => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface DefaultProps {
|
||||
description?: string
|
||||
testID?: string
|
||||
}
|
||||
|
||||
type Props = PassedProps & DefaultProps
|
||||
|
||||
class ContextMenuItem extends Component<Props> {
|
||||
public static defaultProps: DefaultProps = {
|
||||
description: null,
|
||||
testID: 'context-menu-item',
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {label, disabled} = this.props
|
||||
const {label, disabled, testID} = this.props
|
||||
|
||||
return (
|
||||
<button
|
||||
className={this.className}
|
||||
onClick={this.handleClick}
|
||||
disabled={disabled}
|
||||
data-testid={testID}
|
||||
>
|
||||
{label}
|
||||
{this.description}
|
||||
|
|
|
@ -183,7 +183,7 @@ class MultiSelectDropdown extends Component<Props, State> {
|
|||
autoHeight={true}
|
||||
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) => {
|
||||
if (this.childTypeIsValid(child)) {
|
||||
if (child.type === DropdownItem) {
|
||||
|
|
|
@ -60,7 +60,7 @@ describe('MultiSelectDropdown', () => {
|
|||
|
||||
button.simulate('click')
|
||||
|
||||
expect(wrapper.find('[data-test="dropdown-menu"]')).toHaveLength(1)
|
||||
expect(wrapper.find('[data-testid="dropdown-menu"]')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('matches snapshot', () => {
|
||||
|
|
|
@ -64,7 +64,7 @@ exports[`MultiSelectDropdown with menu expanded matches snapshot 1`] = `
|
|||
>
|
||||
<div
|
||||
className="dropdown--menu"
|
||||
data-test="dropdown-menu"
|
||||
data-testid="dropdown-menu"
|
||||
>
|
||||
<DropdownItem
|
||||
checkbox={true}
|
||||
|
|
|
@ -14,26 +14,37 @@ import 'src/clockface/components/empty_state/EmptyState.scss'
|
|||
// Decorators
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
size?: ComponentSize
|
||||
interface PassedProps {
|
||||
children: JSX.Element | JSX.Element[]
|
||||
}
|
||||
|
||||
interface DefaultProps {
|
||||
size?: ComponentSize
|
||||
testID?: string
|
||||
}
|
||||
|
||||
type Props = PassedProps & DefaultProps
|
||||
|
||||
@ErrorHandling
|
||||
class EmptyState extends Component<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
public static defaultProps: DefaultProps = {
|
||||
size: ComponentSize.Small,
|
||||
testID: 'empty-state',
|
||||
}
|
||||
|
||||
public static Text = EmptyStateText
|
||||
public static SubText = EmptyStateSubText
|
||||
|
||||
public render() {
|
||||
const {children, size} = this.props
|
||||
const {children, size, testID} = this.props
|
||||
|
||||
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">
|
||||
<tr className="index-list--empty-row">
|
||||
<td colSpan={columnCount}>
|
||||
<div className="index-list--empty-cell" data-test="empty-state">
|
||||
<div className="index-list--empty-cell" data-testid="empty-state">
|
||||
{emptyState}
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -69,9 +69,9 @@ describe('IndexList', () => {
|
|||
|
||||
const emptyDiv = wrapper
|
||||
.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', () => {
|
||||
|
|
|
@ -69,7 +69,7 @@ exports[`IndexList matches snapshot when 0 rows exist 1`] = `
|
|||
>
|
||||
<div
|
||||
className="index-list--empty-cell"
|
||||
data-test="empty-state"
|
||||
data-testid="empty-state"
|
||||
>
|
||||
<div>
|
||||
Empty
|
||||
|
|
|
@ -22,37 +22,43 @@ export enum AutoComplete {
|
|||
Off = 'off',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
interface PassedProps {
|
||||
id?: string
|
||||
min?: number
|
||||
max?: number
|
||||
name?: string
|
||||
value: string | number
|
||||
placeholder?: string
|
||||
autocomplete?: AutoComplete
|
||||
onChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
onBlur?: (e?: ChangeEvent<HTMLInputElement>) => void
|
||||
onFocus?: (e?: ChangeEvent<HTMLInputElement>) => void
|
||||
onKeyPress?: (e: KeyboardEvent<HTMLInputElement>) => void
|
||||
onKeyUp?: (e: KeyboardEvent<HTMLInputElement>) => void
|
||||
onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void
|
||||
size?: ComponentSize
|
||||
icon?: IconFont
|
||||
status?: ComponentStatus
|
||||
autoFocus?: boolean
|
||||
spellCheck?: boolean
|
||||
type: InputType
|
||||
widthPixels?: number
|
||||
titleText?: string
|
||||
disabledTitleText?: string
|
||||
customClass?: string
|
||||
maxLength?: 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> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
public static defaultProps: DefaultProps = {
|
||||
type: InputType.Text,
|
||||
name: '',
|
||||
value: '',
|
||||
placeholder: '',
|
||||
|
@ -63,6 +69,7 @@ class Input extends Component<Props> {
|
|||
status: ComponentStatus.Default,
|
||||
autoFocus: false,
|
||||
spellCheck: false,
|
||||
testID: 'input-field',
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
@ -85,7 +92,7 @@ class Input extends Component<Props> {
|
|||
maxLength,
|
||||
autocomplete,
|
||||
tabIndex,
|
||||
dataTest,
|
||||
testID,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
|
@ -112,7 +119,7 @@ class Input extends Component<Props> {
|
|||
disabled={status === ComponentStatus.Disabled}
|
||||
maxLength={maxLength}
|
||||
tabIndex={tabIndex}
|
||||
data-test={dataTest}
|
||||
data-testid={testID}
|
||||
/>
|
||||
{this.icon}
|
||||
{this.statusIndicator}
|
||||
|
@ -170,7 +177,7 @@ class Input extends Component<Props> {
|
|||
<span
|
||||
className={`input-status icon ${
|
||||
IconFont.AlertTriangle
|
||||
} data-test="input-error"`}
|
||||
} data-testid="input-error"`}
|
||||
/>
|
||||
<div className="input-shadow" />
|
||||
</>
|
||||
|
@ -183,7 +190,7 @@ class Input extends Component<Props> {
|
|||
<span
|
||||
className={`input-status icon ${
|
||||
IconFont.Checkmark
|
||||
} data-test="input-valid"`}
|
||||
} data-testid="input-valid"`}
|
||||
/>
|
||||
<div className="input-shadow" />
|
||||
</>
|
||||
|
|
|
@ -55,13 +55,13 @@ class OverlayTechnology extends Component<Props, State> {
|
|||
|
||||
if (showChildren) {
|
||||
return (
|
||||
<div className="overlay--dialog" data-test="overlay-children">
|
||||
<div className="overlay--dialog" data-testid="overlay-children">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <div className="overlay--dialog" data-test="overlay-children" />
|
||||
return <div className="overlay--dialog" data-testid="overlay-children" />
|
||||
}
|
||||
|
||||
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 EmptyState from './components/empty_state/EmptyState'
|
||||
import IndexList from './components/index_views/IndexList'
|
||||
import ResourceList from './components/resource_list/ResourceList'
|
||||
import Context from './components/context_menu/Context'
|
||||
import FormElement from 'src/clockface/components/form_layout/FormElement'
|
||||
import DraggableResizer from 'src/clockface/components/draggable_resizer/DraggableResizer'
|
||||
|
@ -95,6 +96,7 @@ export {
|
|||
ProgressBar,
|
||||
QuestionMarkTooltip,
|
||||
Radio,
|
||||
ResourceList,
|
||||
ResponsiveGridSizer,
|
||||
Select,
|
||||
Sort,
|
||||
|
|
|
@ -166,7 +166,7 @@ export default class GraphOptionsCustomizableField extends Component<Props> {
|
|||
spellCheck={false}
|
||||
id="internalName"
|
||||
value={displayName}
|
||||
data-test="custom-time-format"
|
||||
data-testid="custom-time-format"
|
||||
onChange={this.handleFieldRename}
|
||||
placeholder={`Rename ${internalName}`}
|
||||
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.Header.Left>
|
||||
<Page.Header.Right>
|
||||
<SearchWidget
|
||||
placeholderText="Filter dashboards by name..."
|
||||
onSearch={this.handleFilterDashboards}
|
||||
/>
|
||||
<AddResourceDropdown
|
||||
onSelectNew={this.handleCreateDashboard}
|
||||
onSelectImport={this.handleToggleImportOverlay}
|
||||
|
@ -128,6 +124,12 @@ class DashboardIndex extends PureComponent<Props, State> {
|
|||
<Page.Contents fullWidth={false} scrollable={true}>
|
||||
<div className="col-md-12">
|
||||
<DashboardsIndexContents
|
||||
filterComponent={() => (
|
||||
<SearchWidget
|
||||
placeholderText="Filter dashboards by name..."
|
||||
onSearch={this.handleFilterDashboards}
|
||||
/>
|
||||
)}
|
||||
orgs={orgs}
|
||||
dashboards={dashboards}
|
||||
onSetDefaultDashboard={this.handleSetDefaultDashboard}
|
||||
|
|
|
@ -27,6 +27,7 @@ interface Props {
|
|||
notify: (message: Notification) => void
|
||||
searchTerm: string
|
||||
showOwnerColumn: boolean
|
||||
filterComponent?: () => JSX.Element
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
|
@ -45,6 +46,7 @@ export default class DashboardsIndexContents extends Component<Props> {
|
|||
orgs,
|
||||
showOwnerColumn,
|
||||
dashboards,
|
||||
filterComponent,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
|
@ -56,6 +58,7 @@ export default class DashboardsIndexContents extends Component<Props> {
|
|||
>
|
||||
{filteredDashboards => (
|
||||
<Table
|
||||
filterComponent={filterComponent}
|
||||
searchTerm={searchTerm}
|
||||
dashboards={filteredDashboards}
|
||||
onDeleteDashboard={onDeleteDashboard}
|
||||
|
|
|
@ -7,22 +7,17 @@ import _ from 'lodash'
|
|||
import {
|
||||
Button,
|
||||
IconFont,
|
||||
Alignment,
|
||||
ComponentSize,
|
||||
ComponentColor,
|
||||
} from '@influxdata/clockface'
|
||||
import {EmptyState, IndexList} from 'src/clockface'
|
||||
import TableRows from 'src/dashboards/components/dashboard_index/TableRows'
|
||||
import {EmptyState, ResourceList} from 'src/clockface'
|
||||
import DashboardCards from 'src/dashboards/components/dashboard_index/DashboardCards'
|
||||
import SortingHat from 'src/shared/components/sorting_hat/SortingHat'
|
||||
|
||||
// Types
|
||||
import {Sort} from 'src/clockface'
|
||||
import {Dashboard, Organization} from 'src/types/v2'
|
||||
|
||||
// Constants
|
||||
const OWNER_COL_WIDTH = 17
|
||||
const NAME_COL_WIDTH = 63
|
||||
|
||||
interface Props {
|
||||
searchTerm: string
|
||||
dashboards: Dashboard[]
|
||||
|
@ -36,6 +31,7 @@ interface Props {
|
|||
onEditLabels: (dashboard: Dashboard) => void
|
||||
orgs: Organization[]
|
||||
showOwnerColumn: boolean
|
||||
filterComponent?: () => JSX.Element
|
||||
}
|
||||
|
||||
interface DatedDashboard extends Dashboard {
|
||||
|
@ -59,36 +55,30 @@ class DashboardsTable extends PureComponent<Props & WithRouterProps, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {filterComponent} = this.props
|
||||
const {sortKey, sortDirection} = this.state
|
||||
|
||||
return (
|
||||
<IndexList>
|
||||
<IndexList.Header>
|
||||
<IndexList.HeaderCell
|
||||
columnName={this.headerKeys[0]}
|
||||
<ResourceList>
|
||||
<ResourceList.Header filterComponent={filterComponent}>
|
||||
<ResourceList.Sorter
|
||||
name={this.headerKeys[0]}
|
||||
sortKey={this.headerKeys[0]}
|
||||
sort={sortKey === this.headerKeys[0] ? sortDirection : Sort.None}
|
||||
width={this.nameColWidth}
|
||||
onClick={this.handleClickColumn}
|
||||
/>
|
||||
{this.ownerColumnHeader}
|
||||
<IndexList.HeaderCell
|
||||
columnName={this.headerKeys[2]}
|
||||
{this.ownerSorter}
|
||||
<ResourceList.Sorter
|
||||
name={this.headerKeys[2]}
|
||||
sortKey={this.headerKeys[2]}
|
||||
sort={sortKey === this.headerKeys[2] ? sortDirection : Sort.None}
|
||||
width="11%"
|
||||
onClick={this.handleClickColumn}
|
||||
/>
|
||||
<IndexList.HeaderCell
|
||||
columnName=""
|
||||
width="10%"
|
||||
alignment={Alignment.Right}
|
||||
/>
|
||||
</IndexList.Header>
|
||||
<IndexList.Body emptyState={this.emptyState} columnCount={5}>
|
||||
{this.sortedRows}
|
||||
</IndexList.Body>
|
||||
</IndexList>
|
||||
</ResourceList.Header>
|
||||
<ResourceList.Body emptyState={this.emptyState}>
|
||||
{this.sortedCards}
|
||||
</ResourceList.Body>
|
||||
</ResourceList>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -96,38 +86,27 @@ class DashboardsTable extends PureComponent<Props & WithRouterProps, State> {
|
|||
return ['name', 'owner', 'modified', 'default']
|
||||
}
|
||||
|
||||
private get ownerColumnHeader(): JSX.Element {
|
||||
private get ownerSorter(): JSX.Element {
|
||||
const {showOwnerColumn} = this.props
|
||||
const {sortKey, sortDirection} = this.state
|
||||
|
||||
if (showOwnerColumn) {
|
||||
return (
|
||||
<IndexList.HeaderCell
|
||||
columnName={this.headerKeys[1]}
|
||||
<ResourceList.Sorter
|
||||
name={this.headerKeys[1]}
|
||||
sortKey={this.headerKeys[1]}
|
||||
sort={sortKey === this.headerKeys[1] ? sortDirection : Sort.None}
|
||||
width={`${OWNER_COL_WIDTH}%`}
|
||||
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) => {
|
||||
this.setState({sortKey, sortDirection: nextSort})
|
||||
}
|
||||
|
||||
private get sortedRows(): JSX.Element {
|
||||
private get sortedCards(): JSX.Element {
|
||||
const {
|
||||
dashboards,
|
||||
onExportDashboard,
|
||||
|
@ -149,7 +128,7 @@ class DashboardsTable extends PureComponent<Props & WithRouterProps, State> {
|
|||
direction={sortDirection}
|
||||
>
|
||||
{ds => (
|
||||
<TableRows
|
||||
<DashboardCards
|
||||
dashboards={ds}
|
||||
onCloneDashboard={onCloneDashboard}
|
||||
onExportDashboard={onExportDashboard}
|
||||
|
|
|
@ -59,7 +59,7 @@ class SaveAsButton extends PureComponent<Props, State> {
|
|||
active={saveAsOption === SaveAsOption.Dashboard}
|
||||
value={SaveAsOption.Dashboard}
|
||||
onClick={this.handleSetSaveAsOption}
|
||||
data-test="cell-radio-button"
|
||||
data-testid="cell-radio-button"
|
||||
>
|
||||
Dashboard Cell
|
||||
</Radio.Button>
|
||||
|
@ -67,7 +67,7 @@ class SaveAsButton extends PureComponent<Props, State> {
|
|||
active={saveAsOption === SaveAsOption.Task}
|
||||
value={SaveAsOption.Task}
|
||||
onClick={this.handleSetSaveAsOption}
|
||||
data-test="task-radio-button"
|
||||
data-testid="task-radio-button"
|
||||
>
|
||||
Task
|
||||
</Radio.Button>
|
||||
|
|
|
@ -36,7 +36,7 @@ exports[`SaveAsButton rendering renders 1`] = `
|
|||
>
|
||||
<RadioButton
|
||||
active={true}
|
||||
data-test="cell-radio-button"
|
||||
data-testid="cell-radio-button"
|
||||
disabled={false}
|
||||
disabledTitleText="This option is disabled"
|
||||
onClick={[Function]}
|
||||
|
@ -47,7 +47,7 @@ exports[`SaveAsButton rendering renders 1`] = `
|
|||
</RadioButton>
|
||||
<RadioButton
|
||||
active={false}
|
||||
data-test="task-radio-button"
|
||||
data-testid="task-radio-button"
|
||||
disabled={false}
|
||||
disabledTitleText="This option is disabled"
|
||||
onClick={[Function]}
|
||||
|
|
|
@ -42,7 +42,7 @@ describe('DataLoaders.Components.CollectorsWizard.Configure.ConfigFieldHandler',
|
|||
telegrafPluginsInfo[TelegrafPluginInputCpu.NameEnum.Cpu].fields,
|
||||
})
|
||||
const noConfig = wrapper.find({
|
||||
'data-test': 'no-config',
|
||||
'data-testid': 'no-config',
|
||||
})
|
||||
|
||||
expect(wrapper.exists()).toBe(true)
|
||||
|
|
|
@ -44,7 +44,7 @@ export class ConfigFieldHandler extends PureComponent<Props> {
|
|||
const {configFields, telegrafPlugin, onSetConfigArrayValue} = this.props
|
||||
|
||||
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(
|
||||
|
|
|
@ -64,7 +64,7 @@ describe('DataLoaders.Components.CollectorsWizard.Configure.PluginConfigForm', (
|
|||
telegrafPlugin,
|
||||
})
|
||||
|
||||
const link = wrapper.find({'data-test': 'docs-link'})
|
||||
const link = wrapper.find({'data-testid': 'docs-link'})
|
||||
|
||||
expect(link.exists()).toBe(true)
|
||||
expect(link.prop('href')).toContain(telegrafPlugin.name)
|
||||
|
|
|
@ -52,7 +52,7 @@ export class PluginConfigForm extends PureComponent<Props> {
|
|||
For more information about this plugin, see{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
data-test="docs-link"
|
||||
data-testid="docs-link"
|
||||
href={`https://github.com/influxdata/telegraf/tree/master/plugins/inputs/${
|
||||
telegrafPlugin.name
|
||||
}`}
|
||||
|
|
|
@ -21,6 +21,7 @@ exports[`ScraperTarget rendering renders correctly with bucket 1`] = `
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="error"
|
||||
testID="input-field"
|
||||
titleText="Name"
|
||||
type="text"
|
||||
value=""
|
||||
|
@ -54,6 +55,7 @@ exports[`ScraperTarget rendering renders correctly with bucket 1`] = `
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="error"
|
||||
testID="input-field"
|
||||
titleText="Target URL"
|
||||
type="text"
|
||||
value=""
|
||||
|
@ -85,6 +87,7 @@ exports[`ScraperTarget rendering renders correctly with name 1`] = `
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="default"
|
||||
testID="input-field"
|
||||
titleText="Name"
|
||||
type="text"
|
||||
value="MyScraper"
|
||||
|
@ -118,6 +121,7 @@ exports[`ScraperTarget rendering renders correctly with name 1`] = `
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="error"
|
||||
testID="input-field"
|
||||
titleText="Target URL"
|
||||
type="text"
|
||||
value=""
|
||||
|
@ -149,6 +153,7 @@ exports[`ScraperTarget rendering renders correctly with no bucket, url, name 1`]
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="error"
|
||||
testID="input-field"
|
||||
titleText="Name"
|
||||
type="text"
|
||||
value=""
|
||||
|
@ -182,6 +187,7 @@ exports[`ScraperTarget rendering renders correctly with no bucket, url, name 1`]
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="error"
|
||||
testID="input-field"
|
||||
titleText="Target URL"
|
||||
type="text"
|
||||
value=""
|
||||
|
@ -213,6 +219,7 @@ exports[`ScraperTarget rendering renders correctly with url 1`] = `
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="error"
|
||||
testID="input-field"
|
||||
titleText="Name"
|
||||
type="text"
|
||||
value=""
|
||||
|
@ -246,6 +253,7 @@ exports[`ScraperTarget rendering renders correctly with url 1`] = `
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="default"
|
||||
testID="input-field"
|
||||
titleText="Target URL"
|
||||
type="text"
|
||||
value="http://url.com"
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('Account', () => {
|
|||
it('displays the users info by default', () => {
|
||||
const {wrapper} = setup()
|
||||
|
||||
const nameInput = wrapper.find({'data-test': 'nameInput'})
|
||||
const nameInput = wrapper.find({'data-testid': 'nameInput'})
|
||||
expect(nameInput.props().value).toBe(me.name)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -44,7 +44,7 @@ export class Settings extends PureComponent<StateProps, State> {
|
|||
<Form.Element label="Username">
|
||||
<Input
|
||||
value={me.name}
|
||||
dataTest="nameInput"
|
||||
testID="nameInput"
|
||||
titleText="Username"
|
||||
size={ComponentSize.Small}
|
||||
status={ComponentStatus.Disabled}
|
||||
|
|
|
@ -28,7 +28,7 @@ export default class TokenRow extends PureComponent<Props> {
|
|||
<a
|
||||
href="#"
|
||||
onClick={this.handleClickDescription}
|
||||
data-test={`token-description-${id}`}
|
||||
data-testid={`token-description-${id}`}
|
||||
>
|
||||
{description}
|
||||
</a>
|
||||
|
|
|
@ -56,7 +56,7 @@ describe('Account', () => {
|
|||
describe('clicking the token description', () => {
|
||||
it('opens the ViewTokenModal', () => {
|
||||
const description = wrapper.find({
|
||||
'data-test': `token-description-${1}`,
|
||||
'data-testid': `token-description-${1}`,
|
||||
})
|
||||
description.simulate('click')
|
||||
wrapper.update()
|
||||
|
|
|
@ -111,7 +111,6 @@ exports[`Account rendering renders! 1`] = `
|
|||
<Input
|
||||
autoFocus={false}
|
||||
autocomplete="off"
|
||||
dataTest="nameInput"
|
||||
disabledTitleText="This input is disabled"
|
||||
name=""
|
||||
onChange={[Function]}
|
||||
|
@ -119,7 +118,9 @@ exports[`Account rendering renders! 1`] = `
|
|||
size="sm"
|
||||
spellCheck={false}
|
||||
status="disabled"
|
||||
testID="nameInput"
|
||||
titleText="Username"
|
||||
type="text"
|
||||
value="groot"
|
||||
>
|
||||
<div
|
||||
|
@ -134,13 +135,14 @@ exports[`Account rendering renders! 1`] = `
|
|||
autoComplete="off"
|
||||
autoFocus={false}
|
||||
className="input-field"
|
||||
data-test="nameInput"
|
||||
data-testid="nameInput"
|
||||
disabled={true}
|
||||
name=""
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
spellCheck={false}
|
||||
title="This input is disabled"
|
||||
type="text"
|
||||
value="groot"
|
||||
/>
|
||||
<div
|
||||
|
|
|
@ -19,7 +19,9 @@ exports[`Account rendering renders! 1`] = `
|
|||
size="sm"
|
||||
spellCheck={false}
|
||||
status="default"
|
||||
testID="input-field"
|
||||
titleText=""
|
||||
type="text"
|
||||
value=""
|
||||
widthPixels={256}
|
||||
>
|
||||
|
@ -35,12 +37,14 @@ exports[`Account rendering renders! 1`] = `
|
|||
autoComplete="off"
|
||||
autoFocus={false}
|
||||
className="input-field"
|
||||
data-testid="input-field"
|
||||
disabled={false}
|
||||
name=""
|
||||
onChange={[Function]}
|
||||
placeholder="Filter Tokens..."
|
||||
spellCheck={false}
|
||||
title=""
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
|
@ -244,6 +248,7 @@ exports[`Account rendering renders! 1`] = `
|
|||
emptyState={
|
||||
<EmptyState
|
||||
size="lg"
|
||||
testID="empty-state"
|
||||
>
|
||||
<EmptyStateText
|
||||
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"
|
||||
>
|
||||
<a
|
||||
data-test="token-description-1"
|
||||
data-testid="token-description-1"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
|
@ -426,7 +431,7 @@ exports[`Account rendering renders! 1`] = `
|
|||
className="index-list--cell"
|
||||
>
|
||||
<a
|
||||
data-test="token-description-2"
|
||||
data-testid="token-description-2"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
|
@ -506,7 +511,7 @@ exports[`Account rendering renders! 1`] = `
|
|||
>
|
||||
<div
|
||||
className="overlay--dialog"
|
||||
data-test="overlay-children"
|
||||
data-testid="overlay-children"
|
||||
/>
|
||||
<div
|
||||
className="overlay--mask"
|
||||
|
|
|
@ -32,8 +32,8 @@ describe('Onboarding.Components.OnboardingButtons', () => {
|
|||
onClickBack,
|
||||
})
|
||||
|
||||
const nextButton = wrapper.find('[data-test="next"]')
|
||||
const backButton = wrapper.find('[data-test="back"]')
|
||||
const nextButton = wrapper.find('[data-testid="next"]')
|
||||
const backButton = wrapper.find('[data-testid="back"]')
|
||||
|
||||
backButton.simulate('click')
|
||||
|
||||
|
@ -53,7 +53,7 @@ describe('Onboarding.Components.OnboardingButtons', () => {
|
|||
onClickSkip,
|
||||
})
|
||||
|
||||
const skipButton = wrapper.find('[data-test="skip"]')
|
||||
const skipButton = wrapper.find('[data-testid="skip"]')
|
||||
skipButton.simulate('click')
|
||||
|
||||
expect(skipButton.exists()).toBe(true)
|
||||
|
|
|
@ -61,7 +61,7 @@ class OnboardingButtons extends PureComponent<Props> {
|
|||
text={nextButtonText}
|
||||
size={ComponentSize.Medium}
|
||||
type={ButtonType.Submit}
|
||||
data-test="next"
|
||||
data-testid="next"
|
||||
ref={this.submitRef}
|
||||
status={nextButtonStatus}
|
||||
tabIndex={0}
|
||||
|
@ -89,7 +89,7 @@ class OnboardingButtons extends PureComponent<Props> {
|
|||
text={backButtonText}
|
||||
size={ComponentSize.Medium}
|
||||
onClick={onClickBack}
|
||||
data-test="back"
|
||||
data-testid="back"
|
||||
tabIndex={1}
|
||||
/>
|
||||
)
|
||||
|
@ -109,7 +109,7 @@ class OnboardingButtons extends PureComponent<Props> {
|
|||
color={ComponentColor.Default}
|
||||
text={skipButtonText}
|
||||
onClick={onClickSkip}
|
||||
data-test="skip"
|
||||
data-testid="skip"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -52,7 +52,9 @@ exports[`Onboarding.Components.AdminStep renders 1`] = `
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="default"
|
||||
testID="input-field"
|
||||
titleText="Username"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</FormElement>
|
||||
|
@ -76,6 +78,7 @@ exports[`Onboarding.Components.AdminStep renders 1`] = `
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="default"
|
||||
testID="input-field"
|
||||
titleText="Password"
|
||||
type="password"
|
||||
value=""
|
||||
|
@ -101,6 +104,7 @@ exports[`Onboarding.Components.AdminStep renders 1`] = `
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="default"
|
||||
testID="input-field"
|
||||
titleText="Confirm Password"
|
||||
type="password"
|
||||
value=""
|
||||
|
@ -127,7 +131,9 @@ exports[`Onboarding.Components.AdminStep renders 1`] = `
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="default"
|
||||
testID="input-field"
|
||||
titleText="Initial Organization Name"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</FormElement>
|
||||
|
@ -152,7 +158,9 @@ exports[`Onboarding.Components.AdminStep renders 1`] = `
|
|||
size="md"
|
||||
spellCheck={false}
|
||||
status="default"
|
||||
testID="input-field"
|
||||
titleText="Initial Bucket Name"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</FormElement>
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
ButtonShape,
|
||||
ComponentSize,
|
||||
ComponentColor,
|
||||
IconFont,
|
||||
} from '@influxdata/clockface'
|
||||
import {Bucket} from '@influxdata/influx'
|
||||
import {DataLoaderType} from 'src/types/v2/dataLoaders'
|
||||
|
@ -57,6 +58,7 @@ export default class BucketRow extends PureComponent<Props> {
|
|||
<IndexList.Cell alignment={Alignment.Right}>
|
||||
<Context align={Alignment.Center}>
|
||||
<Context.Menu
|
||||
icon={IconFont.Plus}
|
||||
text="Add Data"
|
||||
shape={ButtonShape.Default}
|
||||
color={ComponentColor.Primary}
|
||||
|
|
|
@ -64,6 +64,7 @@ export default class CreateOrgOverlay extends PureComponent<Props, State> {
|
|||
value={org.name}
|
||||
onChange={this.handleChangeInput}
|
||||
status={nameInputStatus}
|
||||
testID="create-org-name-input"
|
||||
/>
|
||||
</Form.Element>
|
||||
</OverlayBody>
|
||||
|
@ -74,6 +75,7 @@ export default class CreateOrgOverlay extends PureComponent<Props, State> {
|
|||
type={ButtonType.Submit}
|
||||
color={ComponentColor.Primary}
|
||||
status={this.submitButtonStatus}
|
||||
testID="create-org-submit-button"
|
||||
/>
|
||||
</OverlayFooter>
|
||||
</Form>
|
||||
|
|
|
@ -95,7 +95,6 @@ Object {
|
|||
>
|
||||
<div
|
||||
class="confirmation-button--tooltip-body"
|
||||
data-test="confirmation-button--click-target"
|
||||
data-testid="confirmation-button"
|
||||
>
|
||||
Confirm
|
||||
|
@ -118,11 +117,14 @@ Object {
|
|||
>
|
||||
<button
|
||||
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
||||
data-testid="button"
|
||||
data-testid="context-menu"
|
||||
tabindex="0"
|
||||
title="Add Data"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="button-icon icon plus"
|
||||
/>
|
||||
Add Data
|
||||
</button>
|
||||
<div
|
||||
|
@ -133,6 +135,7 @@ Object {
|
|||
>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Configure Telegraf Agent
|
||||
<div
|
||||
|
@ -143,6 +146,7 @@ Object {
|
|||
</button>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Line Protocol
|
||||
<div
|
||||
|
@ -153,6 +157,7 @@ Object {
|
|||
</button>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Scrape Metrics
|
||||
<div
|
||||
|
@ -228,7 +233,6 @@ Object {
|
|||
>
|
||||
<div
|
||||
class="confirmation-button--tooltip-body"
|
||||
data-test="confirmation-button--click-target"
|
||||
data-testid="confirmation-button"
|
||||
>
|
||||
Confirm
|
||||
|
@ -251,11 +255,14 @@ Object {
|
|||
>
|
||||
<button
|
||||
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
||||
data-testid="button"
|
||||
data-testid="context-menu"
|
||||
tabindex="0"
|
||||
title="Add Data"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="button-icon icon plus"
|
||||
/>
|
||||
Add Data
|
||||
</button>
|
||||
<div
|
||||
|
@ -266,6 +273,7 @@ Object {
|
|||
>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Configure Telegraf Agent
|
||||
<div
|
||||
|
@ -276,6 +284,7 @@ Object {
|
|||
</button>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Line Protocol
|
||||
<div
|
||||
|
@ -286,6 +295,7 @@ Object {
|
|||
</button>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Scrape Metrics
|
||||
<div
|
||||
|
@ -308,7 +318,7 @@ Object {
|
|||
>
|
||||
<div
|
||||
class="overlay--dialog"
|
||||
data-test="overlay-children"
|
||||
data-testid="overlay-children"
|
||||
/>
|
||||
<div
|
||||
class="overlay--mask"
|
||||
|
@ -319,7 +329,7 @@ Object {
|
|||
>
|
||||
<div
|
||||
class="overlay--dialog"
|
||||
data-test="overlay-children"
|
||||
data-testid="overlay-children"
|
||||
/>
|
||||
<div
|
||||
class="overlay--mask"
|
||||
|
@ -418,7 +428,6 @@ Object {
|
|||
>
|
||||
<div
|
||||
class="confirmation-button--tooltip-body"
|
||||
data-test="confirmation-button--click-target"
|
||||
data-testid="confirmation-button"
|
||||
>
|
||||
Confirm
|
||||
|
@ -441,11 +450,14 @@ Object {
|
|||
>
|
||||
<button
|
||||
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
||||
data-testid="button"
|
||||
data-testid="context-menu"
|
||||
tabindex="0"
|
||||
title="Add Data"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="button-icon icon plus"
|
||||
/>
|
||||
Add Data
|
||||
</button>
|
||||
<div
|
||||
|
@ -456,6 +468,7 @@ Object {
|
|||
>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Configure Telegraf Agent
|
||||
<div
|
||||
|
@ -466,6 +479,7 @@ Object {
|
|||
</button>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Line Protocol
|
||||
<div
|
||||
|
@ -476,6 +490,7 @@ Object {
|
|||
</button>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Scrape Metrics
|
||||
<div
|
||||
|
@ -551,7 +566,6 @@ Object {
|
|||
>
|
||||
<div
|
||||
class="confirmation-button--tooltip-body"
|
||||
data-test="confirmation-button--click-target"
|
||||
data-testid="confirmation-button"
|
||||
>
|
||||
Confirm
|
||||
|
@ -574,11 +588,14 @@ Object {
|
|||
>
|
||||
<button
|
||||
class="button button-xs button-primary context-menu--toggle context-menu--primary"
|
||||
data-testid="button"
|
||||
data-testid="context-menu"
|
||||
tabindex="0"
|
||||
title="Add Data"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="button-icon icon plus"
|
||||
/>
|
||||
Add Data
|
||||
</button>
|
||||
<div
|
||||
|
@ -589,6 +606,7 @@ Object {
|
|||
>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Configure Telegraf Agent
|
||||
<div
|
||||
|
@ -599,6 +617,7 @@ Object {
|
|||
</button>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Line Protocol
|
||||
<div
|
||||
|
@ -609,6 +628,7 @@ Object {
|
|||
</button>
|
||||
<button
|
||||
class="context-menu--item"
|
||||
data-testid="context-menu-item"
|
||||
>
|
||||
Scrape Metrics
|
||||
<div
|
||||
|
@ -631,7 +651,7 @@ Object {
|
|||
>
|
||||
<div
|
||||
class="overlay--dialog"
|
||||
data-test="overlay-children"
|
||||
data-testid="overlay-children"
|
||||
/>
|
||||
<div
|
||||
class="overlay--mask"
|
||||
|
@ -642,7 +662,7 @@ Object {
|
|||
>
|
||||
<div
|
||||
class="overlay--dialog"
|
||||
data-test="overlay-children"
|
||||
data-testid="overlay-children"
|
||||
/>
|
||||
<div
|
||||
class="overlay--mask"
|
||||
|
|
|
@ -73,6 +73,7 @@ class OrganizationsIndex extends PureComponent<Props, State> {
|
|||
icon={IconFont.Plus}
|
||||
text="Create Organization"
|
||||
titleText="Create a new Organization"
|
||||
testID="create-org-button"
|
||||
/>
|
||||
</Page.Header.Right>
|
||||
</Page.Header>
|
||||
|
|
|
@ -73,7 +73,7 @@ const DropdownMenu: SFC<Props> = ({
|
|||
[menuClass]: menuClass,
|
||||
})}
|
||||
style={{width: menuWidth}}
|
||||
data-test="dropdown-ul"
|
||||
data-testid="dropdown-ul"
|
||||
>
|
||||
<FancyScrollbar
|
||||
autoHide={false}
|
||||
|
|
|
@ -52,7 +52,7 @@ const DropdownMenuItem: SFC<ItemProps> = ({
|
|||
highlight: index === highlightedItemIndex,
|
||||
active: item.text === selected,
|
||||
})}
|
||||
data-test="dropdown-item"
|
||||
data-testid="dropdown-item"
|
||||
>
|
||||
<a href="#" onClick={onSelection(item)} onMouseOver={onHighlight(index)}>
|
||||
{item.text}
|
||||
|
|
|
@ -37,7 +37,7 @@ describe('Components.Shared.InputClickToEdit', () => {
|
|||
const inputField = inputClickToEdit.children().find('input')
|
||||
const disabledDiv = inputClickToEdit
|
||||
.children()
|
||||
.find({'data-test': 'disabled'})
|
||||
.find({'data-testid': 'disabled'})
|
||||
|
||||
expect(initialDiv.exists()).toBe(true)
|
||||
expect(inputField.exists()).toBe(false)
|
||||
|
@ -51,7 +51,7 @@ describe('Components.Shared.InputClickToEdit', () => {
|
|||
const inputField = inputClickToEdit.children().find('input')
|
||||
const disabledDiv = inputClickToEdit
|
||||
.children()
|
||||
.find({'data-test': 'disabled'})
|
||||
.find({'data-testid': 'disabled'})
|
||||
|
||||
expect(inputField.exists()).toBe(false)
|
||||
expect(disabledDiv.exists()).toBe(true)
|
||||
|
@ -62,9 +62,9 @@ describe('Components.Shared.InputClickToEdit', () => {
|
|||
const {inputClickToEdit} = setup({disabled})
|
||||
const disabledDiv = inputClickToEdit
|
||||
.children()
|
||||
.find({'data-test': 'disabled'})
|
||||
.find({'data-testid': 'disabled'})
|
||||
const icon = disabledDiv.children().find({
|
||||
'data-test': 'icon',
|
||||
'data-testid': 'icon',
|
||||
})
|
||||
|
||||
expect(icon.exists()).toBe(false)
|
||||
|
|
|
@ -116,7 +116,7 @@ class InputClickToEdit extends PureComponent<Props, State> {
|
|||
|
||||
return disabled ? (
|
||||
<div className={wrapperClass}>
|
||||
<div data-test="disabled" className="input-cte__disabled">
|
||||
<div data-testid="disabled" className="input-cte__disabled">
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -144,7 +144,7 @@ class InputClickToEdit extends PureComponent<Props, State> {
|
|||
>
|
||||
<span className="input-cte-span">{value || placeholder}</span>
|
||||
{appearAsNormalInput || (
|
||||
<span data-test="icon" className="icon pencil" />
|
||||
<span data-testid="icon" className="icon pencil" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -67,14 +67,14 @@ describe('SubSections', () => {
|
|||
describe('render', () => {
|
||||
it('renders the currently active tab', () => {
|
||||
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)
|
||||
})
|
||||
|
||||
it('only renders enabled tabs', () => {
|
||||
const wrapper = setup()
|
||||
const nav = wrapper.dive().find({'data-test': 'subsectionNav'})
|
||||
const nav = wrapper.dive().find({'data-testid': 'subsectionNav'})
|
||||
|
||||
const tabs = nav.find(SubSectionsTab)
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class SubSections extends Component<Props> {
|
|||
|
||||
return (
|
||||
<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">
|
||||
{sections.map(
|
||||
section =>
|
||||
|
@ -42,7 +42,7 @@ class SubSections extends Component<Props> {
|
|||
</div>
|
||||
<div
|
||||
className="col-md-10 subsection--content"
|
||||
data-test="subsectionContent"
|
||||
data-testid="subsectionContent"
|
||||
>
|
||||
{this.activeSectionComponent}
|
||||
</div>
|
||||
|
|
|
@ -65,7 +65,7 @@ class Notification extends Component<Props, State> {
|
|||
<div
|
||||
className={this.notificationClassname}
|
||||
ref={this.handleNotificationRef}
|
||||
data-testid={this.dataTest}
|
||||
data-testid={this.dataTestID}
|
||||
>
|
||||
<span className={`icon ${icon}`} />
|
||||
<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
|
||||
return `notification-${type}`
|
||||
}
|
||||
|
|
|
@ -20,7 +20,9 @@ exports[`TaskForm rendering renders 1`] = `
|
|||
size="sm"
|
||||
spellCheck={false}
|
||||
status="default"
|
||||
testID="input-field"
|
||||
titleText=""
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</FormElement>
|
||||
|
|
|
@ -106,7 +106,7 @@ class TimeFormat extends PureComponent<Props, State> {
|
|||
spellCheck={false}
|
||||
placeholder="Enter custom format..."
|
||||
value={format}
|
||||
data-test="custom-time-format"
|
||||
data-testid="custom-time-format"
|
||||
customClass="custom-time-format"
|
||||
onChange={this.handleChangeFormat}
|
||||
/>
|
||||
|
|
Loading…
Reference in New Issue