From 390ddaa38f6208b193ddc563805a49714ce36d90 Mon Sep 17 00:00:00 2001 From: Rose Parker Date: Tue, 2 Jun 2020 15:51:48 -0400 Subject: [PATCH] chore: convert index list to resource list --- CHANGELOG.md | 2 + ui/cypress/e2e/tokens.test.ts | 104 +++++++++--------- .../authorizations/components/TokenList.tsx | 37 ++----- ui/src/authorizations/components/TokenRow.tsx | 72 ++++++------ .../authorizations/components/TokensTab.tsx | 63 +++++++++-- .../generateSortItems.ts | 42 +++++++ 6 files changed, 191 insertions(+), 129 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 370f934f146..b6ce9eaa63b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ ### UI Improvements 1. [18319](https://github.com/influxdata/influxdb/pull/18319): Display bucket ID in bucket list and enable 1 click copying +1. [18361](https://github.com/influxdata/influxdb/pull/18361): Tokens list is now consistent with the other resource lists + ## v2.0.0-beta.11 [2020-05-26] diff --git a/ui/cypress/e2e/tokens.test.ts b/ui/cypress/e2e/tokens.test.ts index 6cc5334d180..68bd4004f69 100644 --- a/ui/cypress/e2e/tokens.test.ts +++ b/ui/cypress/e2e/tokens.test.ts @@ -87,14 +87,14 @@ describe('tokens', () => { cy.fixture('routes').then(({orgs}) => { cy.visit(`${orgs}/${id}/load-data/tokens`) }) - cy.get('[data-testid="index-list"]', {timeout: PAGE_LOAD_SLA}) + cy.get('[data-testid="resource-list"]', {timeout: PAGE_LOAD_SLA}) }) }) }) it('can list tokens', () => { - cy.getByTestID('table-row').should('have.length', 4) - cy.getByTestID('table-row').then(rows => { + cy.getByTestID('resource-card').should('have.length', 4) + cy.getByTestID('resource-card').then(rows => { authData = authData.sort((a, b) => // eslint-disable-next-line a.description < b.description @@ -105,11 +105,9 @@ describe('tokens', () => { ) for (let i = 0; i < rows.length; i++) { - cy.getByTestID('editable-name') + cy.getByTestID('resource-editable-name') .eq(i) - .children('a') - .children('span') - .should('have.text', authData[i].description) + .contains(authData[i].description) if (authData[i].status) { cy.getByTestID('slide-toggle') @@ -127,22 +125,22 @@ describe('tokens', () => { it('can filter tokens', () => { // basic filter cy.getByTestID('input-field--filter').type('test') - cy.getByTestID('table-row').should('have.length', 3) + cy.getByTestID('resource-card').should('have.length', 3) // clear filter cy.getByTestID('input-field--filter').clear() - cy.getByTestID('table-row').should('have.length', 4) + cy.getByTestID('resource-card').should('have.length', 4) // exotic filter cy.getByTestID('input-field--filter').type('\u0950') - cy.getByTestID('table-row').should('have.length', 1) + cy.getByTestID('resource-card').should('have.length', 1) }) it('can change token activation status', () => { // toggle on - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .contains('token test 02') - .parents('[data-testid=table-row]') + .parents('[data-testid=resource-card]') .within(() => { cy.getByTestID('slide-toggle') .click() @@ -162,16 +160,16 @@ describe('tokens', () => { }) }) - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .contains('token test 02') - .parents('[data-testid=table-row]') + .parents('[data-testid=resource-card]') .within(() => { cy.getByTestID('slide-toggle').should('have.class', 'active') }) - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .contains('token test 02') - .parents('[data-testid=table-row]') + .parents('[data-testid=resource-card]') .within(() => { cy.getByTestID('slide-toggle') .click() @@ -191,67 +189,63 @@ describe('tokens', () => { }) }) - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .contains('token test 02') - .parents('[data-testid=table-row]') + .parents('[data-testid=resource-card]') .within(() => { cy.getByTestID('slide-toggle').should('not.have.class', 'active') }) }) it('can delete a token', () => { - cy.getByTestID('table-row').should('have.length', 4) + cy.getByTestID('resource-card').should('have.length', 4) - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .contains('token test 03') - .parents('[data-testid=table-row]') + .parents('[data-testid=resource-card]') .within(() => { - cy.getByTestID('delete-token--button').click() - }) - .then(() => { - cy.getByTestID('delete-token--popover--contents').within(() => { - cy.getByTestID('delete-token--confirm-button').click() - }) + cy.getByTestID('context-menu').click() + + cy.getByTestID('delete-token') + .contains('Delete') + .click() }) - cy.getByTestID('table-row').should('have.length', 3) + cy.getByTestID('resource-card').should('have.length', 3) - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .contains('token test 03') .should('not.exist') // Delete remaining tokens - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .first() .within(() => { - cy.getByTestID('delete-token--button').click() - }) - .then(() => { - cy.getByTestID('delete-token--popover--contents').within(() => { - cy.getByTestID('delete-token--confirm-button').click() - }) + cy.getByTestID('context-menu').click() + + cy.getByTestID('delete-token') + .contains('Delete') + .click() }) - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .first() .within(() => { - cy.getByTestID('delete-token--button').click() - }) - .then(() => { - cy.getByTestID('delete-token--popover--contents').within(() => { - cy.getByTestID('delete-token--confirm-button').click() - }) + cy.getByTestID('context-menu').click() + + cy.getByTestID('delete-token') + .contains('Delete') + .click() }) - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .first() .within(() => { - cy.getByTestID('delete-token--button').click() - }) - .then(() => { - cy.getByTestID('delete-token--popover--contents').within(() => { - cy.getByTestID('delete-token--confirm-button').click() - }) + cy.getByTestID('context-menu').click() + + cy.getByTestID('delete-token') + .contains('Delete') + .click() }) // Assert empty state @@ -261,7 +255,7 @@ describe('tokens', () => { }) it('can generate a read/write token', () => { - cy.getByTestID('table-row').should('have.length', 4) + cy.getByTestID('resource-card').should('have.length', 4) // create some extra buckets for filters cy.get('@org').then(({id, name}: Organization) => { @@ -280,7 +274,7 @@ describe('tokens', () => { // check cancel cy.getByTestID('button--cancel').click() cy.getByTestID('overlay--container').should('not.be.visible') - cy.getByTestID('table-row').should('have.length', 4) + cy.getByTestID('resource-card').should('have.length', 4) // open overlay - again cy.getByTestID('dropdown-button--gen-token').click() @@ -305,11 +299,11 @@ describe('tokens', () => { cy.getByTestID('button--save').click() // Verify token - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .should('have.length', 5) .contains('Jeton 01') .should('be.visible') - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .contains('Jeton 01') .click() cy.getByTestID('overlay--container').should('be.visible') @@ -349,7 +343,7 @@ describe('tokens', () => { }) it('can view a token', () => { - cy.getByTestID('table-row') + cy.getByTestID('resource-card') .contains('token test \u0950') .click() diff --git a/ui/src/authorizations/components/TokenList.tsx b/ui/src/authorizations/components/TokenList.tsx index e053227de15..373791ec118 100644 --- a/ui/src/authorizations/components/TokenList.tsx +++ b/ui/src/authorizations/components/TokenList.tsx @@ -4,7 +4,7 @@ import _ from 'lodash' import memoizeOne from 'memoize-one' // Components -import {Overlay, IndexList} from '@influxdata/clockface' +import {Overlay, ResourceList} from '@influxdata/clockface' import TokenRow from 'src/authorizations/components/TokenRow' import ViewTokenOverlay from 'src/authorizations/components/ViewTokenOverlay' @@ -15,12 +15,12 @@ import {Sort} from '@influxdata/clockface' // Utils import {getSortedResources} from 'src/shared/utils/sort' -import TokensEmptyState from './TokensEmptyState' type SortKey = keyof Authorization interface Props { auths: Authorization[] + emptyState: JSX.Element searchTerm: string sortKey: string sortDirection: Sort @@ -47,35 +47,16 @@ export default class TokenList extends PureComponent { } public render() { - const {sortKey, sortDirection, onClickColumn, searchTerm} = this.props const {isTokenOverlayVisible, authInView} = this.state return ( <> - - - - - - } - columnCount={2} - > + + {this.rows} - - + + + { ) } - private get headerKeys(): SortKey[] { - return ['description', 'status'] - } - private get rows(): JSX.Element[] { const {auths, sortDirection, sortKey, sortType} = this.props const sortedAuths = this.memGetSortedResources( diff --git a/ui/src/authorizations/components/TokenRow.tsx b/ui/src/authorizations/components/TokenRow.tsx index 2b931c8be93..9d1b983c0d4 100644 --- a/ui/src/authorizations/components/TokenRow.tsx +++ b/ui/src/authorizations/components/TokenRow.tsx @@ -1,5 +1,5 @@ // Libraries -import React, {PureComponent, MouseEvent} from 'react' +import React, {PureComponent} from 'react' import {connect} from 'react-redux' // Actions @@ -11,14 +11,15 @@ import { // Components import { ComponentSize, + FlexBox, + InputLabel, SlideToggle, ComponentColor, - IndexList, - Alignment, - ConfirmationButton, - Appearance, + ResourceCard, + IconFont, } from '@influxdata/clockface' -import EditableName from 'src/shared/components/EditableName' + +import {Context} from 'src/clockface' // Types import {Authorization} from 'src/types' @@ -39,40 +40,42 @@ type Props = DispatchProps & OwnProps class TokenRow extends PureComponent { public render() { const {description} = this.props.auth - + const {auth} = this.props + const labelText = this.isTokenEnabled ? 'Active' : 'Inactive' return ( - - - - - + + + + {[<>Created at: {auth.createdAt}]} + + - - - {labelText} + + + ) + } + + private get contextMenu(): JSX.Element { + return ( + + + - - + + ) } @@ -96,11 +99,8 @@ class TokenRow extends PureComponent { this.props.onDelete(id, description) } - private handleClickDescription = (e: MouseEvent) => { + private handleClickDescription = () => { const {onClickDescription, auth} = this.props - - e.preventDefault() - onClickDescription(auth.id) } diff --git a/ui/src/authorizations/components/TokensTab.tsx b/ui/src/authorizations/components/TokensTab.tsx index bd33f9bc6d4..1cd310d6203 100644 --- a/ui/src/authorizations/components/TokensTab.tsx +++ b/ui/src/authorizations/components/TokensTab.tsx @@ -2,14 +2,16 @@ import React, {PureComponent} from 'react' import {connect} from 'react-redux' import {withRouter, WithRouterProps} from 'react-router' +import {isEmpty} from 'lodash' // Components -import {Sort} from '@influxdata/clockface' +import {Sort, ComponentSize, EmptyState} from '@influxdata/clockface' import SearchWidget from 'src/shared/components/search_widget/SearchWidget' import TokenList from 'src/authorizations/components/TokenList' import FilterList from 'src/shared/components/FilterList' import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader' import GenerateTokenDropdown from 'src/authorizations/components/GenerateTokenDropdown' +import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown' // Types import {AppState, Authorization, ResourceType} from 'src/types' @@ -21,6 +23,7 @@ import {getAll} from 'src/resources/selectors' enum AuthSearchKeys { Description = 'description', Status = 'status', + CreatedAt = 'createdAt', } interface State { @@ -56,12 +59,22 @@ class TokensTab extends PureComponent { const {tokens} = this.props const leftHeaderItems = ( - + <> + + + ) const rightHeaderItems = @@ -80,6 +93,7 @@ class TokensTab extends PureComponent { {filteredAuths => ( { ) } + private handleSort = ( + sortKey: SortKey, + sortDirection: Sort, + sortType: SortTypes + ): void => { + this.setState({sortKey, sortDirection, sortType}) + } + private handleClickColumn = (nextSort: Sort, sortKey: SortKey) => { const sortType = SortTypes.String this.setState({sortKey, sortDirection: nextSort, sortType}) @@ -102,7 +124,32 @@ class TokensTab extends PureComponent { } private get searchKeys(): AuthSearchKeys[] { - return [AuthSearchKeys.Status, AuthSearchKeys.Description] + return [ + AuthSearchKeys.Status, + AuthSearchKeys.Description, + AuthSearchKeys.CreatedAt, + ] + } + + private get emptyState(): JSX.Element { + const {searchTerm} = this.state + + if (isEmpty(searchTerm)) { + return ( + + + Looks like there aren't any Tokens, why not generate one? + + + + ) + } + + return ( + + No Tokens match your query + + ) } } diff --git a/ui/src/shared/components/resource_sort_dropdown/generateSortItems.ts b/ui/src/shared/components/resource_sort_dropdown/generateSortItems.ts index 157067af3d0..a09d9021fd7 100644 --- a/ui/src/shared/components/resource_sort_dropdown/generateSortItems.ts +++ b/ui/src/shared/components/resource_sort_dropdown/generateSortItems.ts @@ -10,6 +10,7 @@ import { Bucket, Telegraf, Scraper, + Authorization, } from 'src/types' export type DashboardSortKey = keyof Dashboard | 'meta.updatedAt' @@ -20,6 +21,7 @@ export type TemplateSortKey = keyof Template | 'meta.name' | 'meta.description' export type BucketSortKey = keyof Bucket | 'retentionRules[0].everySeconds' export type TelegrafSortKey = keyof Telegraf export type ScraperSortKey = keyof Scraper +export type AuthorizationSortKey = keyof Authorization export type SortKey = | DashboardSortKey @@ -30,6 +32,7 @@ export type SortKey = | BucketSortKey | TelegrafSortKey | ScraperSortKey + | AuthorizationSortKey export interface SortDropdownItem { label: string @@ -294,5 +297,44 @@ export const generateSortItems = ( sortDirection: Sort.Descending, }, ] + case ResourceType.Authorizations: + return [ + { + label: 'Description (A → Z)', + sortKey: 'description', + sortType: SortTypes.String, + sortDirection: Sort.Ascending, + }, + { + label: 'Description (Z → A)', + sortKey: 'description', + sortType: SortTypes.String, + sortDirection: Sort.Descending, + }, + { + label: 'Status (Active)', + sortKey: 'status', + sortType: SortTypes.String, + sortDirection: Sort.Ascending, + }, + { + label: 'Status (Inactive)', + sortKey: 'status', + sortType: SortTypes.String, + sortDirection: Sort.Descending, + }, + { + label: 'Date Created (Oldest)', + sortKey: 'createdAt', + sortType: SortTypes.String, + sortDirection: Sort.Ascending, + }, + { + label: 'Date Created (Newest)', + sortKey: 'createdAt', + sortType: SortTypes.String, + sortDirection: Sort.Descending, + }, + ] } }