Remove unused labels components (#12513)

pull/12536/head
alexpaxton 2019-03-11 13:14:47 -07:00 committed by GitHub
parent 96ed02fdae
commit 3e9234f999
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 0 additions and 1344 deletions

View File

@ -6,9 +6,6 @@ import classnames from 'classnames'
// Types
import {ComponentSize, Greys} from 'src/clockface/types'
// Components
import LabelContainer from 'src/clockface/components/label/LabelContainer'
// Styles
import './Label.scss'
@ -41,8 +38,6 @@ class Label extends Component<Props, State> {
testID: 'label--pill',
}
public static Container = LabelContainer
constructor(props: Props) {
super(props)

View File

@ -1,167 +0,0 @@
/*
Label Container Styles
------------------------------------------------------------------------------
*/
@import 'src/style/modules';
$label-margin: 1px;
$label-edit-button-diameter: 18px;
.label--container {
width: 100%;
}
.label--container-margin {
width: calc(100% + #{$label-margin * 2});
position: relative;
left: $label-margin * -2;
display: flex;
flex-wrap: wrap;
padding: $label-margin;
> .label {
margin: $label-margin;
}
}
/*
Additional Labels Indicator
------------------------------------------------------------------------------
*/
.additional-labels {
position: relative;
user-select: none;
font-weight: 700;
margin: $label-margin;
transition: background-color 0.25s ease, color 0.25s ease;
background-color: rgba($g6-smoke, 0.5);
color: $g11-sidewalk;
&:hover {
cursor: pointer;
background-color: $g6-smoke;
color: $g15-platinum;
}
}
.label--tooltip {
z-index: 9999;
visibility: hidden;
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
transition-property: all;
}
.label--tooltip-container {
@extend %drop-shadow;
opacity: 0;
background-color: $g0-obsidian;
border-radius: $radius;
padding: $ix-marg-c;
margin-top: $ix-marg-b + $ix-border;
transition: opacity 0.25s ease;
position: relative;
&:after {
content: '';
position: absolute;
top: 0;
left: 50%;
border: $ix-marg-b solid transparent;
border-bottom-color: $g0-obsidian;
transform: translate(-50%, -100%);
}
> .label {
margin: $label-margin;
}
}
.additional-labels:hover .label--tooltip {
visibility: visible;
}
.additional-labels:hover .label--tooltip-container {
opacity: 1;
}
/*
Edit Labels on Resource
------------------------------------------------------------------------------
*/
.label--edit-button {
width: $label-edit-button-diameter;
height: $label-edit-button-diameter;
margin: $label-margin;
position: relative;
transition: opacity 0.25s ease;
&:before,
&:after {
content: '';
pointer-events: none;
background-color: $g20-white;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
z-index: 3;
transition: width 0.25s ease;
height: $ix-border;
width: $label-edit-button-diameter - $ix-marg-b;
border-radius: $ix-border / 2;
}
&:after {
transform: translate(-50%,-50%) rotate(90deg);
}
> .button.button-sm {
position: absolute;
z-index: 2;
top: 50%;
left: 50%;
width: $label-edit-button-diameter;
height: $label-edit-button-diameter;
transform: translate(-50%, -50%);
border-radius: 50%;
color: transparent;
transition:
background-color 0.25s ease,
border-color 0.25s ease,
box-shadow 0.25s ease,
height 0.25s ease,
width 0.25s ease;
> .button-icon {
font-size: $form-sm-font;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
}
&:hover > .button.button-sm {
width: $form-sm-height - $ix-marg-a;
height: $form-sm-height - $ix-marg-a;
}
&:hover:before,
&:hover:after {
width: $form-sm-height - $ix-marg-c;
}
}
/* When used inside an index list, hide until row hover */
.index-list--row-cell .label--edit-button {
opacity: 0;
}
.index-list--row-cell:hover .label--edit-button {
opacity: 1;
}

View File

@ -1,118 +0,0 @@
// Libraries
import React, {Component} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
// Components
import LabelTooltip from 'src/clockface/components/label/LabelTooltip'
import {Button} from '@influxdata/clockface'
// Types
import {ButtonShape, IconFont, ComponentColor} from '@influxdata/clockface'
// Styles
import 'src/clockface/components/label/LabelContainer.scss'
interface Props {
children?: JSX.Element[]
className?: string
limitChildCount?: number
resourceName?: string
onEdit?: () => void
testID?: string
}
class LabelContainer extends Component<Props> {
public static defaultProps: Partial<Props> = {
limitChildCount: 999,
resourceName: 'this resource',
testID: 'labels-con',
}
public render() {
const {className, testID} = this.props
return (
<div
className={classnames('label--container', {
[`${className}`]: className,
})}
data-testid={testID}
>
<div className="label--container-margin">
{this.children}
{this.additionalChildrenIndicator}
{this.editButton}
</div>
</div>
)
}
private get sortedChildren(): JSX.Element[] {
const {children} = this.props
if (children && React.Children.count(children) > 1) {
return children.sort((a: JSX.Element, b: JSX.Element) => {
const textA = a.props.name.toUpperCase()
const textB = b.props.name.toUpperCase()
return textA < textB ? -1 : textA > textB ? 1 : 0
})
}
return children
}
private get children(): JSX.Element[] | JSX.Element {
const {children, limitChildCount} = this.props
if (children) {
return React.Children.map(
this.sortedChildren,
(child: JSX.Element, i: number) => {
if (i < limitChildCount) {
return child
}
}
)
}
}
private get additionalChildrenIndicator(): JSX.Element {
const {children, limitChildCount} = this.props
const childCount = React.Children.count(children)
if (limitChildCount < childCount) {
const additionalCount = childCount - limitChildCount
return (
<div className="additional-labels">
+{additionalCount} more
<LabelTooltip labels={this.sortedChildren.slice(limitChildCount)} />
</div>
)
}
}
private get editButton(): JSX.Element {
const {onEdit, children, resourceName} = this.props
const titleText = React.Children.count(children)
? `Edit Labels for ${resourceName}`
: `Add Labels to ${resourceName}`
if (onEdit) {
return (
<div className="label--edit-button">
<Button
color={ComponentColor.Primary}
titleText={titleText}
onClick={onEdit}
shape={ButtonShape.Square}
icon={IconFont.Plus}
/>
</div>
)
}
}
}
export default LabelContainer

View File

@ -1,82 +0,0 @@
/*
Label Selector Styles
------------------------------------------------------------------------------
*/
@import 'src/style/modules';
.label-selector--input {
position: relative;
}
.label-selector--menu-container {
overflow: hidden;
z-index: 999;
width: 100%;
position: absolute;
top: 100%;
left: 0;
@include gradient-h($g2-kevlar, $g4-onyx);
border-radius: $radius;
box-shadow: 0 2px 5px 0.6px fade-out($g0-obsidian, 0.7);
}
.label-selector--menu-container .fancy-scroll--track-h {
display: none;
}
.label-selector--menu {
display: flex;
flex-wrap: wrap;
width: 100%;
padding: $ix-marg-b - ($ix-border / 2);
}
.label-selector--menu-item {
display: inline-flex;
align-items: flex-start;
margin: $ix-border / 2;
}
.label-selector--empty {
width: 100%;
font-size: 13px;
font-weight: 500;
user-select: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: $g9-mountain;
font-style: italic;
min-height: 30px;
line-height: 30px;
&:first-child {
margin-bottom: $ix-marg-b - ($ix-border / 2);
}
}
.label-selector--selection {
display: flex;
flex-wrap: nowrap;
margin-bottom: $ix-marg-c;
}
.label-selector--remove-all {
margin-top: $ix-marg-b;
margin-left: $ix-marg-b;
}
.label-selector--selected,
.label-selector--none-selected {
flex: 1 0 0;
margin-top: $ix-marg-b;
}
.label-selector--none-selected {
font-size: 13px;
color: $g10-wolf;
font-weight: 500;
font-style: italic;
user-select: none;
}

View File

@ -1,312 +0,0 @@
// Libraries
import React, {Component, ChangeEvent, KeyboardEvent} from 'react'
import _ from 'lodash'
// APIs
import {client} from 'src/utils/api'
// Components
import {Button} from '@influxdata/clockface'
import Input from 'src/clockface/components/inputs/Input'
import Label from 'src/clockface/components/label/Label'
import LabelContainer from 'src/clockface/components/label/LabelContainer'
import LabelSelectorMenu from 'src/clockface/components/label/LabelSelectorMenu'
import {ClickOutside} from 'src/shared/components/ClickOutside'
// Types
import {ComponentSize} from 'src/clockface/types'
import {Label as APILabel} from 'src/types/v2/labels'
// Styles
import './LabelSelector.scss'
import {ErrorHandling} from 'src/shared/decorators/errors'
enum ArrowDirection {
Up = -1,
Down = 1,
}
interface Props {
selectedLabels: APILabel[]
allLabels: APILabel[]
onAddLabel: (label: APILabel) => void
onRemoveLabel: (label: APILabel) => void
onRemoveAllLabels: () => void
onCreateLabel: (label: APILabel) => void
resourceType: string
inputSize?: ComponentSize
}
interface State {
filterValue: string
filteredLabels: APILabel[]
isSuggesting: boolean
highlightedID: string
}
@ErrorHandling
class LabelSelector extends Component<Props, State> {
public static defaultProps: Partial<Props> = {
inputSize: ComponentSize.Small,
}
constructor(props: Props) {
super(props)
const initialFilteredLabels = _.differenceBy(
props.allLabels,
props.selectedLabels,
label => label.name
)
this.state = {
highlightedID: null,
filterValue: '',
filteredLabels: initialFilteredLabels,
isSuggesting: false,
}
}
public componentDidMount() {
this.handleStartSuggesting()
}
public render() {
return (
<div className="label-selector">
<div className="label-selector--selection">
{this.selectedLabels}
{this.clearSelectedButton}
</div>
<ClickOutside onClickOutside={this.handleStopSuggesting}>
{this.input}
</ClickOutside>
</div>
)
}
private get selectedLabels(): JSX.Element {
const {selectedLabels, resourceType} = this.props
if (selectedLabels && selectedLabels.length) {
return (
<LabelContainer className="label-selector--selected">
{selectedLabels.map(label => (
<Label
key={label.name}
name={label.name}
id={label.name}
colorHex={label.properties.color}
onDelete={this.handleDelete}
description={label.properties.description}
/>
))}
</LabelContainer>
)
}
return (
<div className="label-selector--none-selected">{`${_.upperFirst(
resourceType
)} has no labels`}</div>
)
}
private get suggestionMenu(): JSX.Element {
const {allLabels, selectedLabels} = this.props
const {isSuggesting, highlightedID, filterValue} = this.state
const allLabelsUsed = allLabels.length === selectedLabels.length
if (isSuggesting) {
return (
<LabelSelectorMenu
filterValue={filterValue}
allLabelsUsed={allLabelsUsed}
filteredLabels={this.availableLabels}
highlightItemID={highlightedID}
onItemClick={this.handleAddLabel}
onItemHighlight={this.handleItemHighlight}
onCreateLabel={this.handleCreateLabel}
/>
)
}
}
private get input(): JSX.Element {
const {resourceType, inputSize} = this.props
const {filterValue} = this.state
return (
<div className="label-selector--input">
<Input
placeholder={`Add labels to ${resourceType}`}
value={filterValue}
onFocus={this.handleStartSuggesting}
onKeyDown={this.handleKeyDown}
onChange={this.handleInputChange}
size={inputSize}
autoFocus={true}
/>
{this.suggestionMenu}
</div>
)
}
private handleAddLabel = (labelID: string): void => {
const {onAddLabel, allLabels} = this.props
const label = allLabels.find(label => label.name === labelID)
onAddLabel(label)
this.handleStopSuggesting()
}
private handleItemHighlight = (highlightedID: string): void => {
this.setState({highlightedID})
}
private handleStartSuggesting = () => {
const {availableLabels} = this
const {isSuggesting} = this.state
if (_.isEmpty(availableLabels) && !isSuggesting) {
return this.setState({
isSuggesting: true,
highlightedID: null,
filterValue: '',
})
}
const highlightedID = this.availableLabels[0].name
this.setState({isSuggesting: true, highlightedID, filterValue: ''})
}
private handleStopSuggesting = () => {
const {allLabels: filteredLabels} = this.props
this.setState({isSuggesting: false, filterValue: '', filteredLabels})
}
private handleInputChange = (e: ChangeEvent<HTMLInputElement>): void => {
let highlightedID = this.state.highlightedID
const {allLabels, selectedLabels} = this.props
const filterValue = e.target.value
const availableLabels = _.differenceBy(
allLabels,
selectedLabels,
l => l.name
)
const filteredLabels = availableLabels.filter(label => {
return label.name.includes(filterValue)
})
const highlightedIDAvailable = filteredLabels.find(
al => al.name === highlightedID
)
if (!highlightedIDAvailable && filteredLabels.length) {
highlightedID = filteredLabels[0].name
}
if (filterValue.length === 0) {
return this.setState({
isSuggesting: true,
filteredLabels: this.props.allLabels,
highlightedID: null,
filterValue: '',
})
}
this.setState({filterValue, filteredLabels, highlightedID})
}
private get availableLabels(): APILabel[] {
const {selectedLabels} = this.props
const {filteredLabels} = this.state
return _.differenceBy(filteredLabels, selectedLabels, label => label.name)
}
private handleDelete = (labelID: string): void => {
const {onRemoveLabel, selectedLabels} = this.props
const label = selectedLabels.find(l => l.name === labelID)
onRemoveLabel(label)
}
private handleHighlightAdjacentItem = (direction: ArrowDirection): void => {
const {highlightedID} = this.state
const {availableLabels} = this
if (!availableLabels.length || !highlightedID) {
return null
}
const highlightedIndex = _.findIndex(
availableLabels,
label => label.name === highlightedID
)
const adjacentIndex = Math.min(
Math.max(highlightedIndex + direction, 0),
availableLabels.length - 1
)
const adjacentID = availableLabels[adjacentIndex].name
this.setState({highlightedID: adjacentID})
}
private handleKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
const {highlightedID} = this.state
if (!highlightedID) {
return
}
switch (e.key) {
case 'Escape':
e.currentTarget.blur()
return this.handleStopSuggesting()
case 'Enter':
e.currentTarget.blur()
return this.handleAddLabel(highlightedID)
case 'ArrowUp':
return this.handleHighlightAdjacentItem(ArrowDirection.Up)
case 'ArrowDown':
return this.handleHighlightAdjacentItem(ArrowDirection.Down)
default:
break
}
}
private get clearSelectedButton(): JSX.Element {
const {selectedLabels, onRemoveAllLabels} = this.props
if (_.isEmpty(selectedLabels)) {
return
}
return (
<Button
text="Remove All Labels"
size={ComponentSize.ExtraSmall}
customClass="label-selector--remove-all"
onClick={onRemoveAllLabels}
/>
)
}
private handleCreateLabel = async (label: APILabel) => {
const newLabel = await client.labels.create(label.name, label.properties)
this.props.onAddLabel(newLabel)
this.handleStopSuggesting()
}
}
export default LabelSelector

View File

@ -1,95 +0,0 @@
// Libraries
import React, {Component} from 'react'
import _ from 'lodash'
// Components
import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar'
import LabelSelectorMenuItem from 'src/clockface/components/label/LabelSelectorMenuItem'
import ResourceLabelForm from 'src/shared/components/ResourceLabelForm'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
// Types
import {Label} from 'src/types/v2/labels'
interface Props {
filterValue: string
highlightItemID: string
filteredLabels: Label[]
onItemClick: (labelID: string) => void
onItemHighlight: (labelID: string) => void
allLabelsUsed: boolean
onCreateLabel: (label: Label) => Promise<void>
}
@ErrorHandling
class LabelSelectorMenu extends Component<Props> {
public render() {
return (
<div className="label-selector--menu-container">
<FancyScrollbar autoHide={false} autoHeight={true} maxHeight={250}>
<div className="label-selector--menu">
{this.menuItems}
{this.emptyText}
{this.resourceLabelForm}
</div>
</FancyScrollbar>
</div>
)
}
private get menuItems(): JSX.Element[] | JSX.Element {
const {
filteredLabels,
onItemClick,
onItemHighlight,
highlightItemID,
} = this.props
if (filteredLabels.length) {
return filteredLabels.map(label => (
<LabelSelectorMenuItem
highlighted={highlightItemID === label.name}
key={label.name}
name={label.name}
id={label.name}
description={label.properties.description}
colorHex={label.properties.color}
onClick={onItemClick}
onHighlight={onItemHighlight}
/>
))
}
}
private get emptyText(): JSX.Element {
const {allLabelsUsed, filterValue} = this.props
if (!filterValue) {
return null
}
let text = `No labels match "${filterValue}" want to create a new label?`
if (allLabelsUsed) {
text = 'You have somehow managed to add all the labels, wow!'
}
return <div className="label-selector--empty">{text}</div>
}
private get resourceLabelForm(): JSX.Element {
const {filterValue, onCreateLabel, filteredLabels} = this.props
if (!filterValue || filteredLabels.find(l => l.name === filterValue)) {
return
}
return (
<ResourceLabelForm labelName={filterValue} onSubmit={onCreateLabel} />
)
}
}
export default LabelSelectorMenu

View File

@ -1,54 +0,0 @@
// Libraries
import React, {Component} from 'react'
import _ from 'lodash'
// Components
import Label from 'src/clockface/components/label/Label'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
highlighted: boolean
id: string
name: string
colorHex: string
description: string
onClick: (labelID: string) => void
onHighlight: (labelID: string) => void
}
@ErrorHandling
class LabelSelectorMenuItem extends Component<Props> {
public render() {
const {name, colorHex, description, id} = this.props
return (
<span
className="label-selector--menu-item"
onMouseOver={this.handleMouseOver}
>
<Label
onClick={this.handleClick}
name={name}
description={description}
id={id}
colorHex={colorHex}
/>
</span>
)
}
private handleMouseOver = (): void => {
const {onHighlight, id} = this.props
onHighlight(id)
}
private handleClick = (): void => {
const {onClick, id} = this.props
onClick(id)
}
}
export default LabelSelectorMenuItem

View File

@ -1,14 +0,0 @@
// Libraries
import React, {SFC} from 'react'
interface Props {
labels: JSX.Element | JSX.Element[]
}
const LabelTooltip: SFC<Props> = ({labels}) => (
<div className="label--tooltip">
<div className="label--tooltip-container">{labels}</div>
</div>
)
export default LabelTooltip

View File

@ -29,7 +29,6 @@ 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'
import Label from 'src/clockface/components/label/Label'
import LabelSelector from 'src/clockface/components/label/LabelSelector'
import GridSizer from 'src/clockface/components/grid_sizer/GridSizer'
import ResponsiveGridSizer from 'src/clockface/components/grid_sizer/ResponsiveGridSizer'
import Select from 'src/clockface/components/Select'
@ -86,7 +85,6 @@ export {
Input,
InputType,
Label,
LabelSelector,
MultiSelectDropdown,
MultiInputType,
MultipleInput,

View File

@ -1,47 +0,0 @@
// Libraries
import React from 'react'
import {shallow} from 'enzyme'
// Components
import TableRow from 'src/dashboards/components/dashboard_index/TableRow'
import {Label} from 'src/clockface'
// Dummy Data
import {dashboardWithLabels, dashboard, orgs} from 'mocks/dummyData'
const setup = (override = {}) => {
const props = {
dashboard,
orgs,
onDeleteDashboard: jest.fn(),
onCloneDashboard: jest.fn(),
onExportDashboard: jest.fn(),
onUpdateDashboard: jest.fn(),
onEditLabels: jest.fn(),
onFilterChange: jest.fn(),
showOwnerColumn: true,
...override,
}
return shallow(<TableRow {...props} />)
}
describe('Dashboard index row', () => {
it('renders with no labels', () => {
const wrapper = setup()
expect(wrapper.exists()).toBe(true)
})
describe('if there are labels', () => {
it('renders correctly', () => {
const wrapper = setup({dashboard: dashboardWithLabels})
const labelContainer = wrapper.find(Label.Container)
const labels = wrapper.find(Label)
expect(labelContainer.exists()).toBe(true)
expect(labels.length).toBe(dashboardWithLabels.labels.length)
})
})
})

View File

@ -1,222 +0,0 @@
// Libraries
import React, {PureComponent} from 'react'
import {Link} from 'react-router'
import moment from 'moment'
// Components
import {
Stack,
Button,
IconFont,
ComponentSize,
ComponentColor,
} from '@influxdata/clockface'
import {
IndexList,
ConfirmationButton,
Label,
ComponentSpacer,
} from 'src/clockface'
import EditableDescription from 'src/shared/components/editable_description/EditableDescription'
import FeatureFlag from 'src/shared/components/FeatureFlag'
import EditableName from 'src/shared/components/EditableName'
// Types
import {Dashboard, Organization} from 'src/types/v2'
import {Alignment} from 'src/clockface'
// Constants
import {
UPDATED_AT_TIME_FORMAT,
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
onFilterChange: (searchTerm: string) => void
showOwnerColumn: boolean
}
export default class DashboardsIndexTableRow extends PureComponent<Props> {
public render() {
const {dashboard, onDeleteDashboard} = this.props
const {id} = dashboard
return (
<IndexList.Row
key={`dashboard-id--${id}`}
disabled={false}
testID={`dashboard-index--row ${id}`}
>
<IndexList.Cell testID="dashboard-index--cell">
<ComponentSpacer
stackChildren={Stack.Rows}
align={Alignment.Left}
stretchToFitWidth={true}
>
<ComponentSpacer
stackChildren={Stack.Columns}
align={Alignment.Left}
>
{this.resourceNames}
{this.labels}
</ComponentSpacer>
<EditableDescription
description={dashboard.description}
placeholder={`Describe ${dashboard.name}`}
onUpdate={this.handleUpdateDescription}
/>
</ComponentSpacer>
</IndexList.Cell>
{this.ownerCell}
{this.lastModifiedCell}
<IndexList.Cell alignment={Alignment.Right} revealOnHover={true}>
<ComponentSpacer align={Alignment.Left} stackChildren={Stack.Columns}>
<FeatureFlag>
<Button
size={ComponentSize.ExtraSmall}
color={ComponentColor.Default}
text="Export"
icon={IconFont.Export}
onClick={this.handleExport}
/>
</FeatureFlag>
<Button
size={ComponentSize.ExtraSmall}
color={ComponentColor.Secondary}
text="Clone"
icon={IconFont.Duplicate}
onClick={this.handleClone}
/>
<ConfirmationButton
text="Delete"
size={ComponentSize.ExtraSmall}
onConfirm={onDeleteDashboard}
returnValue={dashboard}
confirmText="Confirm"
/>
</ComponentSpacer>
</IndexList.Cell>
</IndexList.Row>
)
}
private get ownerCell(): JSX.Element {
const {showOwnerColumn} = this.props
if (showOwnerColumn) {
return <IndexList.Cell>{this.ownerName}</IndexList.Cell>
}
}
private get resourceNames(): JSX.Element {
const {dashboard} = this.props
return (
<EditableName
onUpdate={this.handleUpdateDashboard}
name={dashboard.name}
hrefValue={`/dashboards/${dashboard.id}`}
noNameString={DEFAULT_DASHBOARD_NAME}
/>
)
}
private handleUpdateDashboard = async (name: string) => {
await this.props.onUpdateDashboard({...this.props.dashboard, name})
}
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={4}
className="index-list--labels"
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}
onClick={this.handleLabelClick}
/>
))}
</Label.Container>
)
}
private handleLabelClick = (id: string) => {
const label = this.props.dashboard.labels.find(l => l.id === id)
this.props.onFilterChange(label.name)
}
private get lastModifiedCell(): JSX.Element {
const {dashboard} = this.props
const relativeTimestamp = moment(dashboard.meta.updatedAt).fromNow()
const absoluteTimestamp = moment(dashboard.meta.updatedAt).format(
UPDATED_AT_TIME_FORMAT
)
return (
<IndexList.Cell>
<span title={absoluteTimestamp}>{relativeTimestamp}</span>
</IndexList.Cell>
)
}
private handleEditLabels = () => {
const {dashboard, onEditLabels} = this.props
onEditLabels(dashboard)
}
private get ownerName(): JSX.Element {
const {dashboard, orgs} = this.props
const ownerOrg = orgs.find(o => o.id === dashboard.orgID)
return (
<Link to={`/organizations/${dashboard.orgID}/members`}>
{ownerOrg.name}
</Link>
)
}
private handleUpdateDescription = (description: string): void => {
const {onUpdateDashboard} = this.props
const dashboard = {...this.props.dashboard, description}
onUpdateDashboard(dashboard)
}
private handleClone = () => {
const {onCloneDashboard, dashboard} = this.props
onCloneDashboard(dashboard)
}
private handleExport = () => {
const {onExportDashboard, dashboard} = this.props
onExportDashboard(dashboard)
}
}

View File

@ -1,51 +0,0 @@
// Libraries
import React, {PureComponent} from 'react'
// Components
import TableRow from 'src/dashboards/components/dashboard_index/TableRow'
// 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
onFilterChange: (searchTerm: string) => void
orgs: Organization[]
showOwnerColumn: boolean
}
export default class DashboardsIndexTableRows extends PureComponent<Props> {
public render() {
const {
dashboards,
onExportDashboard,
onCloneDashboard,
onDeleteDashboard,
onUpdateDashboard,
onEditLabels,
orgs,
showOwnerColumn,
onFilterChange,
} = this.props
return dashboards.map(d => (
<TableRow
key={d.id}
dashboard={d}
onExportDashboard={onExportDashboard}
onCloneDashboard={onCloneDashboard}
onDeleteDashboard={onDeleteDashboard}
onUpdateDashboard={onUpdateDashboard}
onEditLabels={onEditLabels}
orgs={orgs}
showOwnerColumn={showOwnerColumn}
onFilterChange={onFilterChange}
/>
))
}
}

View File

@ -1,175 +0,0 @@
// Libraries
import React, {PureComponent} from 'react'
import _ from 'lodash'
// Components
import {
ComponentSize,
ComponentColor,
ComponentStatus,
Button,
} from '@influxdata/clockface'
import {
OverlayContainer,
OverlayHeading,
OverlayBody,
LabelSelector,
Grid,
Form,
} from 'src/clockface'
import FetchLabels from 'src/shared/components/FetchLabels'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
// Types
import {Label} from 'src/types/v2/labels'
import {RemoteDataState} from 'src/types'
// Utils
import {getDeep} from 'src/utils/wrappers'
interface Props<T> {
resource: T
onDismissOverlay: () => void
onAddLabels: (resourceID: string, labels: Label[]) => void
onRemoveLabels: (resourceID: string, labels: Label[]) => void
}
interface State {
selectedLabels: Label[]
loading: RemoteDataState
}
@ErrorHandling
class EditLabelsOverlay<T> extends PureComponent<Props<T>, State> {
constructor(props: Props<T>) {
super(props)
this.state = {
selectedLabels: _.get(props, 'resource.labels'),
loading: RemoteDataState.NotStarted,
}
}
public render() {
const {onDismissOverlay, resource} = this.props
const {selectedLabels} = this.state
return (
<OverlayContainer maxWidth={720}>
<OverlayHeading title="Manage Labels" onDismiss={onDismissOverlay} />
<OverlayBody>
<Form>
<Grid>
<Grid.Row>
<Grid.Column>
<Form.Element label="">
<Form.Box>
<FetchLabels>
{labels => (
<LabelSelector
inputSize={ComponentSize.Medium}
allLabels={labels}
resourceType={_.get(resource, 'name')}
selectedLabels={selectedLabels}
onAddLabel={this.handleAddLabel}
onRemoveLabel={this.handleRemoveLabel}
onRemoveAllLabels={this.handleRemoveAllLabels}
/>
)}
</FetchLabels>
</Form.Box>
</Form.Element>
<Form.Footer>
<Button text="Cancel" onClick={onDismissOverlay} />
<Button
color={ComponentColor.Success}
text="Save Changes"
onClick={this.handleSaveLabels}
status={this.buttonStatus}
customClass="resource-labels--save-edits"
/>
</Form.Footer>
</Grid.Column>
</Grid.Row>
</Grid>
</Form>
</OverlayBody>
</OverlayContainer>
)
}
private get buttonStatus(): ComponentStatus {
if (this.changes.isChanged) {
if (this.state.loading === RemoteDataState.Loading) {
return ComponentStatus.Loading
}
return ComponentStatus.Default
}
return ComponentStatus.Disabled
}
private get changes(): {
isChanged: boolean
removedLabels: Label[]
addedLabels: Label[]
} {
const {resource} = this.props
const {selectedLabels} = this.state
const labels = getDeep<Label[]>(resource, 'labels', [])
const intersection = _.intersectionBy(labels, selectedLabels, 'name')
const removedLabels = _.differenceBy(labels, intersection, 'name')
const addedLabels = _.differenceBy(selectedLabels, intersection, 'name')
return {
isChanged: !!removedLabels.length || !!addedLabels.length,
addedLabels,
removedLabels,
}
}
private handleRemoveAllLabels = (): void => {
this.setState({selectedLabels: []})
}
private handleRemoveLabel = (label: Label): void => {
const selectedLabels = this.state.selectedLabels.filter(
l => l.name !== label.name
)
this.setState({selectedLabels})
}
private handleAddLabel = (label: Label): void => {
const selectedLabels = [...this.state.selectedLabels, label]
this.setState({selectedLabels})
}
private handleSaveLabels = async (): Promise<void> => {
const {onAddLabels, onRemoveLabels, resource, onDismissOverlay} = this.props
const {addedLabels, removedLabels} = this.changes
const resourceID = _.get(resource, 'id')
this.setState({loading: RemoteDataState.Loading})
if (addedLabels.length) {
await onAddLabels(resourceID, addedLabels)
}
if (removedLabels.length) {
await onRemoveLabels(resourceID, removedLabels)
}
this.setState({loading: RemoteDataState.Done})
onDismissOverlay()
}
}
export default EditLabelsOverlay