Feat/edit labels on tasks (#11122)

* Replace EditDashboardLabelsOverlay with generic EditLabelsOverlay
* Add ability to add/remove labels from a task
pull/11128/head
Alirie Gray 2019-01-15 17:02:32 -08:00 committed by GitHub
parent fb42bf672c
commit d9a3b6d76c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1105 additions and 325 deletions

View File

@ -3196,6 +3196,150 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
'/tasks/{taskID}/labels':
get:
tags:
- Tasks
summary: list all labels for a task
parameters:
- $ref: '#/components/parameters/TraceSpan'
- in: path
name: taskID
schema:
type: string
required: true
description: ID of the task
responses:
'200':
description: a list of all labels for a task
content:
application/json:
schema:
type: object
properties:
labels:
$ref: "#/components/schemas/Labels"
links:
$ref: "#/components/schemas/Links"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
tags:
- Tasks
summary: add a label to a task
parameters:
- $ref: '#/components/parameters/TraceSpan'
- in: path
name: taskID
schema:
type: string
required: true
description: ID of the task
requestBody:
description: label to add
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Label"
responses:
'200':
description: a list of all labels for a task
content:
application/json:
schema:
type: object
properties:
labels:
type: array
items:
type: string
links:
$ref: "#/components/schemas/Links"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
'/tasks/{taskID}/labels/{label}':
delete:
tags:
- Tasks
summary: delete a label from a task
parameters:
- $ref: '#/components/parameters/TraceSpan'
- in: path
name: taskID
schema:
type: string
required: true
description: ID of the task
- in: path
name: label
schema:
type: string
required: true
description: the label name
responses:
'204':
description: delete has been accepted
'404':
description: task not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
patch:
tags:
- Tasks
summary: update a label from a task
parameters:
- $ref: '#/components/parameters/TraceSpan'
- in: path
name: taskID
schema:
type: string
required: true
description: ID of the task
- in: path
name: label
schema:
type: string
required: true
description: the label name
requestBody:
description: label update to apply
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LabelRequest"
responses:
'200':
description: updated successfully
'404':
description: task not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/me: /me:
get: get:
x-generated: true x-generated: true
@ -5947,4 +6091,15 @@ components:
type: object type: object
description: Key/Value pairs associated with this label. Keys can be removed by sending an update with an empty value. description: Key/Value pairs associated with this label. Keys can be removed by sending an update with an empty value.
example: {"color": "#ffb3b3", "description": "this is a description"} example: {"color": "#ffb3b3", "description": "this is a description"}
LabelRequest:
type: object
properties:
resourceID:
type: string
name:
type: string
properties:
type: object
description: Key/Value pairs associated with this label. Keys can be removed by sending an update with an empty value.
example: {"color": "#ffb3b3", "description": "this is a description"}

View File

@ -1176,6 +1176,26 @@ export interface InlineResponse2004 {
links?: Links; links?: Links;
} }
/**
*
* @export
* @interface InlineResponse2005
*/
export interface InlineResponse2005 {
/**
*
* @type {Array<string>}
* @memberof InlineResponse2005
*/
labels?: Array<string>;
/**
*
* @type {Links}
* @memberof InlineResponse2005
*/
links?: Links;
}
/** /**
* *
* @export * @export
@ -1216,6 +1236,32 @@ export interface Label {
properties?: any; properties?: any;
} }
/**
*
* @export
* @interface LabelRequest
*/
export interface LabelRequest {
/**
*
* @type {string}
* @memberof LabelRequest
*/
resourceID?: string;
/**
*
* @type {string}
* @memberof LabelRequest
*/
name?: string;
/**
* Key/Value pairs associated with this label. Keys can be removed by sending an update with an empty value.
* @type {any}
* @memberof LabelRequest
*/
properties?: any;
}
/** /**
* flux query to be analyzed. * flux query to be analyzed.
* @export * @export
@ -12046,6 +12092,188 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration
options: localVarRequestOptions, options: localVarRequestOptions,
}; };
}, },
/**
*
* @summary list all labels for a task
* @param {string} taskID ID of the task
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsGet(taskID: string, zapTraceSpan?: string, options: any = {}): RequestArgs {
// verify required parameter 'taskID' is not null or undefined
if (taskID === null || taskID === undefined) {
throw new RequiredError('taskID','Required parameter taskID was null or undefined when calling tasksTaskIDLabelsGet.');
}
const localVarPath = `/tasks/{taskID}/labels`
.replace(`{${"taskID"}}`, encodeURIComponent(String(taskID)));
const localVarUrlObj = url.parse(localVarPath, true);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = Object.assign({ method: 'GET' }, baseOptions, options);
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
if (zapTraceSpan !== undefined && zapTraceSpan !== null) {
localVarHeaderParameter['Zap-Trace-Span'] = String(zapTraceSpan);
}
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
delete localVarUrlObj.search;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
return {
url: url.format(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary delete a label from a task
* @param {string} taskID ID of the task
* @param {string} label the label name
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsLabelDelete(taskID: string, label: string, zapTraceSpan?: string, options: any = {}): RequestArgs {
// verify required parameter 'taskID' is not null or undefined
if (taskID === null || taskID === undefined) {
throw new RequiredError('taskID','Required parameter taskID was null or undefined when calling tasksTaskIDLabelsLabelDelete.');
}
// verify required parameter 'label' is not null or undefined
if (label === null || label === undefined) {
throw new RequiredError('label','Required parameter label was null or undefined when calling tasksTaskIDLabelsLabelDelete.');
}
const localVarPath = `/tasks/{taskID}/labels/{label}`
.replace(`{${"taskID"}}`, encodeURIComponent(String(taskID)))
.replace(`{${"label"}}`, encodeURIComponent(String(label)));
const localVarUrlObj = url.parse(localVarPath, true);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = Object.assign({ method: 'DELETE' }, baseOptions, options);
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
if (zapTraceSpan !== undefined && zapTraceSpan !== null) {
localVarHeaderParameter['Zap-Trace-Span'] = String(zapTraceSpan);
}
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
delete localVarUrlObj.search;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
return {
url: url.format(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary update a label from a task
* @param {string} taskID ID of the task
* @param {string} label the label name
* @param {LabelRequest} labelRequest label update to apply
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsLabelPatch(taskID: string, label: string, labelRequest: LabelRequest, zapTraceSpan?: string, options: any = {}): RequestArgs {
// verify required parameter 'taskID' is not null or undefined
if (taskID === null || taskID === undefined) {
throw new RequiredError('taskID','Required parameter taskID was null or undefined when calling tasksTaskIDLabelsLabelPatch.');
}
// verify required parameter 'label' is not null or undefined
if (label === null || label === undefined) {
throw new RequiredError('label','Required parameter label was null or undefined when calling tasksTaskIDLabelsLabelPatch.');
}
// verify required parameter 'labelRequest' is not null or undefined
if (labelRequest === null || labelRequest === undefined) {
throw new RequiredError('labelRequest','Required parameter labelRequest was null or undefined when calling tasksTaskIDLabelsLabelPatch.');
}
const localVarPath = `/tasks/{taskID}/labels/{label}`
.replace(`{${"taskID"}}`, encodeURIComponent(String(taskID)))
.replace(`{${"label"}}`, encodeURIComponent(String(label)));
const localVarUrlObj = url.parse(localVarPath, true);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = Object.assign({ method: 'PATCH' }, baseOptions, options);
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
if (zapTraceSpan !== undefined && zapTraceSpan !== null) {
localVarHeaderParameter['Zap-Trace-Span'] = String(zapTraceSpan);
}
localVarHeaderParameter['Content-Type'] = 'application/json';
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
delete localVarUrlObj.search;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
const needsSerialization = (<any>"LabelRequest" !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.data = needsSerialization ? JSON.stringify(labelRequest || {}) : (labelRequest || "");
return {
url: url.format(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary add a label to a task
* @param {string} taskID ID of the task
* @param {Label} label label to add
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsPost(taskID: string, label: Label, zapTraceSpan?: string, options: any = {}): RequestArgs {
// verify required parameter 'taskID' is not null or undefined
if (taskID === null || taskID === undefined) {
throw new RequiredError('taskID','Required parameter taskID was null or undefined when calling tasksTaskIDLabelsPost.');
}
// verify required parameter 'label' is not null or undefined
if (label === null || label === undefined) {
throw new RequiredError('label','Required parameter label was null or undefined when calling tasksTaskIDLabelsPost.');
}
const localVarPath = `/tasks/{taskID}/labels`
.replace(`{${"taskID"}}`, encodeURIComponent(String(taskID)));
const localVarUrlObj = url.parse(localVarPath, true);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = Object.assign({ method: 'POST' }, baseOptions, options);
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
if (zapTraceSpan !== undefined && zapTraceSpan !== null) {
localVarHeaderParameter['Zap-Trace-Span'] = String(zapTraceSpan);
}
localVarHeaderParameter['Content-Type'] = 'application/json';
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
delete localVarUrlObj.search;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
const needsSerialization = (<any>"Label" !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.data = needsSerialization ? JSON.stringify(label || {}) : (label || "");
return {
url: url.format(localVarUrlObj),
options: localVarRequestOptions,
};
},
/** /**
* *
* @summary Retrieve all logs for a task * @summary Retrieve all logs for a task
@ -12586,6 +12814,70 @@ export const TasksApiFp = function(configuration?: Configuration) {
return axios.request(axiosRequestArgs); return axios.request(axiosRequestArgs);
}; };
}, },
/**
*
* @summary list all labels for a task
* @param {string} taskID ID of the task
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsGet(taskID: string, zapTraceSpan?: string, options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse200> {
const localVarAxiosArgs = TasksApiAxiosParamCreator(configuration).tasksTaskIDLabelsGet(taskID, zapTraceSpan, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs = Object.assign(localVarAxiosArgs.options, {url: basePath + localVarAxiosArgs.url})
return axios.request(axiosRequestArgs);
};
},
/**
*
* @summary delete a label from a task
* @param {string} taskID ID of the task
* @param {string} label the label name
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsLabelDelete(taskID: string, label: string, zapTraceSpan?: string, options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise<Response> {
const localVarAxiosArgs = TasksApiAxiosParamCreator(configuration).tasksTaskIDLabelsLabelDelete(taskID, label, zapTraceSpan, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs = Object.assign(localVarAxiosArgs.options, {url: basePath + localVarAxiosArgs.url})
return axios.request(axiosRequestArgs);
};
},
/**
*
* @summary update a label from a task
* @param {string} taskID ID of the task
* @param {string} label the label name
* @param {LabelRequest} labelRequest label update to apply
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsLabelPatch(taskID: string, label: string, labelRequest: LabelRequest, zapTraceSpan?: string, options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise<Response> {
const localVarAxiosArgs = TasksApiAxiosParamCreator(configuration).tasksTaskIDLabelsLabelPatch(taskID, label, labelRequest, zapTraceSpan, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs = Object.assign(localVarAxiosArgs.options, {url: basePath + localVarAxiosArgs.url})
return axios.request(axiosRequestArgs);
};
},
/**
*
* @summary add a label to a task
* @param {string} taskID ID of the task
* @param {Label} label label to add
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsPost(taskID: string, label: Label, zapTraceSpan?: string, options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2005> {
const localVarAxiosArgs = TasksApiAxiosParamCreator(configuration).tasksTaskIDLabelsPost(taskID, label, zapTraceSpan, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs = Object.assign(localVarAxiosArgs.options, {url: basePath + localVarAxiosArgs.url})
return axios.request(axiosRequestArgs);
};
},
/** /**
* *
* @summary Retrieve all logs for a task * @summary Retrieve all logs for a task
@ -12817,6 +13109,54 @@ export const TasksApiFactory = function (configuration?: Configuration, basePath
tasksTaskIDGet(taskID: string, options?: any) { tasksTaskIDGet(taskID: string, options?: any) {
return TasksApiFp(configuration).tasksTaskIDGet(taskID, options)(axios, basePath); return TasksApiFp(configuration).tasksTaskIDGet(taskID, options)(axios, basePath);
}, },
/**
*
* @summary list all labels for a task
* @param {string} taskID ID of the task
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsGet(taskID: string, zapTraceSpan?: string, options?: any) {
return TasksApiFp(configuration).tasksTaskIDLabelsGet(taskID, zapTraceSpan, options)(axios, basePath);
},
/**
*
* @summary delete a label from a task
* @param {string} taskID ID of the task
* @param {string} label the label name
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsLabelDelete(taskID: string, label: string, zapTraceSpan?: string, options?: any) {
return TasksApiFp(configuration).tasksTaskIDLabelsLabelDelete(taskID, label, zapTraceSpan, options)(axios, basePath);
},
/**
*
* @summary update a label from a task
* @param {string} taskID ID of the task
* @param {string} label the label name
* @param {LabelRequest} labelRequest label update to apply
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsLabelPatch(taskID: string, label: string, labelRequest: LabelRequest, zapTraceSpan?: string, options?: any) {
return TasksApiFp(configuration).tasksTaskIDLabelsLabelPatch(taskID, label, labelRequest, zapTraceSpan, options)(axios, basePath);
},
/**
*
* @summary add a label to a task
* @param {string} taskID ID of the task
* @param {Label} label label to add
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
tasksTaskIDLabelsPost(taskID: string, label: Label, zapTraceSpan?: string, options?: any) {
return TasksApiFp(configuration).tasksTaskIDLabelsPost(taskID, label, zapTraceSpan, options)(axios, basePath);
},
/** /**
* *
* @summary Retrieve all logs for a task * @summary Retrieve all logs for a task
@ -13009,6 +13349,62 @@ export class TasksApi extends BaseAPI {
return TasksApiFp(this.configuration).tasksTaskIDGet(taskID, options)(this.axios, this.basePath); return TasksApiFp(this.configuration).tasksTaskIDGet(taskID, options)(this.axios, this.basePath);
} }
/**
*
* @summary list all labels for a task
* @param {string} taskID ID of the task
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof TasksApi
*/
public tasksTaskIDLabelsGet(taskID: string, zapTraceSpan?: string, options?: any) {
return TasksApiFp(this.configuration).tasksTaskIDLabelsGet(taskID, zapTraceSpan, options)(this.axios, this.basePath);
}
/**
*
* @summary delete a label from a task
* @param {string} taskID ID of the task
* @param {string} label the label name
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof TasksApi
*/
public tasksTaskIDLabelsLabelDelete(taskID: string, label: string, zapTraceSpan?: string, options?: any) {
return TasksApiFp(this.configuration).tasksTaskIDLabelsLabelDelete(taskID, label, zapTraceSpan, options)(this.axios, this.basePath);
}
/**
*
* @summary update a label from a task
* @param {string} taskID ID of the task
* @param {string} label the label name
* @param {LabelRequest} labelRequest label update to apply
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof TasksApi
*/
public tasksTaskIDLabelsLabelPatch(taskID: string, label: string, labelRequest: LabelRequest, zapTraceSpan?: string, options?: any) {
return TasksApiFp(this.configuration).tasksTaskIDLabelsLabelPatch(taskID, label, labelRequest, zapTraceSpan, options)(this.axios, this.basePath);
}
/**
*
* @summary add a label to a task
* @param {string} taskID ID of the task
* @param {Label} label label to add
* @param {string} [zapTraceSpan] OpenTracing span context
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof TasksApi
*/
public tasksTaskIDLabelsPost(taskID: string, label: Label, zapTraceSpan?: string, options?: any) {
return TasksApiFp(this.configuration).tasksTaskIDLabelsPost(taskID, label, zapTraceSpan, options)(this.axios, this.basePath);
}
/** /**
* *
* @summary Retrieve all logs for a task * @summary Retrieve all logs for a task

View File

@ -16,7 +16,7 @@ import {
IconFont, IconFont,
} from 'src/clockface' } from 'src/clockface'
import ImportDashboardOverlay from 'src/dashboards/components/ImportDashboardOverlay' import ImportDashboardOverlay from 'src/dashboards/components/ImportDashboardOverlay'
import EditDashboardLabelsOverlay from 'src/dashboards/components/EditDashboardLabelsOverlay' import EditLabelsOverlay from 'src/shared/components/EditLabelsOverlay'
// Utils // Utils
import {getDeep} from 'src/utils/wrappers' import {getDeep} from 'src/utils/wrappers'
@ -288,11 +288,11 @@ class DashboardIndex extends PureComponent<Props, State> {
return ( return (
<OverlayTechnology visible={isEditingDashboardLabels}> <OverlayTechnology visible={isEditingDashboardLabels}>
<EditDashboardLabelsOverlay <EditLabelsOverlay<Dashboard>
dashboard={dashboardLabelsEdit} resource={dashboardLabelsEdit}
onDismissOverlay={this.handleStopEditingLabels} onDismissOverlay={this.handleStopEditingLabels}
onAddDashboardLabels={onAddDashboardLabels} onAddLabels={onAddDashboardLabels}
onRemoveDashboardLabels={onRemoveDashboardLabels} onRemoveLabels={onRemoveDashboardLabels}
/> />
</OverlayTechnology> </OverlayTechnology>
) )

View File

@ -17,25 +17,21 @@ import {
} from 'src/clockface' } from 'src/clockface'
import FetchLabels from 'src/shared/components/FetchLabels' import FetchLabels from 'src/shared/components/FetchLabels'
// Actions
import {
addDashboardLabelsAsync,
removeDashboardLabelsAsync,
} from 'src/dashboards/actions/v2'
// Decorators // Decorators
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
// Types // Types
import {Dashboard} from 'src/types/v2'
import {Label} from 'src/api' import {Label} from 'src/api'
import {RemoteDataState} from 'src/types' import {RemoteDataState} from 'src/types'
interface Props { // Utils
dashboard: Dashboard import {getDeep} from 'src/utils/wrappers'
interface Props<T> {
resource: T
onDismissOverlay: () => void onDismissOverlay: () => void
onAddDashboardLabels: typeof addDashboardLabelsAsync onAddLabels: (resourceID: string, labels: Label[]) => void
onRemoveDashboardLabels: typeof removeDashboardLabelsAsync onRemoveLabels: (resourceID: string, labels: Label[]) => void
} }
interface State { interface State {
@ -44,18 +40,18 @@ interface State {
} }
@ErrorHandling @ErrorHandling
class EditDashboardLabelsOverlay extends PureComponent<Props, State> { class EditLabelsOverlay<T> extends PureComponent<Props<T>, State> {
constructor(props: Props) { constructor(props: Props<T>) {
super(props) super(props)
this.state = { this.state = {
selectedLabels: props.dashboard.labels, selectedLabels: _.get(props, 'resource.labels'),
loading: RemoteDataState.NotStarted, loading: RemoteDataState.NotStarted,
} }
} }
public render() { public render() {
const {onDismissOverlay, dashboard} = this.props const {onDismissOverlay, resource} = this.props
const {selectedLabels} = this.state const {selectedLabels} = this.state
return ( return (
@ -73,7 +69,7 @@ class EditDashboardLabelsOverlay extends PureComponent<Props, State> {
<LabelSelector <LabelSelector
inputSize={ComponentSize.Medium} inputSize={ComponentSize.Medium}
allLabels={labels} allLabels={labels}
resourceType={dashboard.name} resourceType={_.get(resource, 'name')}
selectedLabels={selectedLabels} selectedLabels={selectedLabels}
onAddLabel={this.handleAddLabel} onAddLabel={this.handleAddLabel}
onRemoveLabel={this.handleRemoveLabel} onRemoveLabel={this.handleRemoveLabel}
@ -118,15 +114,12 @@ class EditDashboardLabelsOverlay extends PureComponent<Props, State> {
removedLabels: Label[] removedLabels: Label[]
addedLabels: Label[] addedLabels: Label[]
} { } {
const {dashboard} = this.props const {resource} = this.props
const {selectedLabels} = this.state const {selectedLabels} = this.state
const labels = getDeep<Label[]>(resource, 'labels', [])
const intersection = _.intersectionBy( const intersection = _.intersectionBy(labels, selectedLabels, 'name')
dashboard.labels, const removedLabels = _.differenceBy(labels, intersection, 'name')
selectedLabels,
'name'
)
const removedLabels = _.differenceBy(dashboard.labels, intersection, 'name')
const addedLabels = _.differenceBy(selectedLabels, intersection, 'name') const addedLabels = _.differenceBy(selectedLabels, intersection, 'name')
return { return {
@ -155,23 +148,19 @@ class EditDashboardLabelsOverlay extends PureComponent<Props, State> {
} }
private handleSaveLabels = async (): Promise<void> => { private handleSaveLabels = async (): Promise<void> => {
const { const {onAddLabels, onRemoveLabels, resource, onDismissOverlay} = this.props
onAddDashboardLabels,
onRemoveDashboardLabels,
dashboard,
onDismissOverlay,
} = this.props
const {addedLabels, removedLabels} = this.changes const {addedLabels, removedLabels} = this.changes
const resourceID = _.get(resource, 'id')
this.setState({loading: RemoteDataState.Loading}) this.setState({loading: RemoteDataState.Loading})
if (addedLabels.length) { if (addedLabels.length) {
await onAddDashboardLabels(dashboard.id, addedLabels) await onAddLabels(resourceID, addedLabels)
} }
if (removedLabels.length) { if (removedLabels.length) {
await onRemoveDashboardLabels(dashboard.id, removedLabels) await onRemoveLabels(resourceID, removedLabels)
} }
this.setState({loading: RemoteDataState.Done}) this.setState({loading: RemoteDataState.Done})
@ -180,4 +169,4 @@ class EditDashboardLabelsOverlay extends PureComponent<Props, State> {
} }
} }
export default EditDashboardLabelsOverlay export default EditLabelsOverlay

View File

@ -1,7 +1,8 @@
import {AppState} from 'src/types/v2' // Libraries
import {push} from 'react-router-redux' import {push} from 'react-router-redux'
import _ from 'lodash' import _ from 'lodash'
// APIs
import {Task as TaskAPI, Organization} from 'src/api' import {Task as TaskAPI, Organization} from 'src/api'
import { import {
submitNewTask, submitNewTask,
@ -10,6 +11,8 @@ import {
getTask, getTask,
updateTaskStatus as updateTaskStatusAPI, updateTaskStatus as updateTaskStatusAPI,
deleteTask as deleteTaskAPI, deleteTask as deleteTaskAPI,
addTaskLabels as addTaskLabelsAPI,
removeTaskLabels as removeTaskLabelsAPI,
} from 'src/tasks/api/v2' } from 'src/tasks/api/v2'
import {getMe} from 'src/shared/apis/v2/user' import {getMe} from 'src/shared/apis/v2/user'
import {notify} from 'src/shared/actions/notifications' import {notify} from 'src/shared/actions/notifications'
@ -22,8 +25,13 @@ import {
taskImportFailed, taskImportFailed,
taskImportSuccess, taskImportSuccess,
} from 'src/shared/copy/v2/notifications' } from 'src/shared/copy/v2/notifications'
import {getDeep} from 'src/utils/wrappers'
// Types
import {AppState} from 'src/types/v2'
import {Label} from 'src/api'
// Utils
import {getDeep} from 'src/utils/wrappers'
import { import {
taskOptionsToFluxScript, taskOptionsToFluxScript,
TaskOptionKeys, TaskOptionKeys,
@ -42,6 +50,8 @@ export type Action =
| ClearTask | ClearTask
| SetTaskOption | SetTaskOption
| SetAllTaskOptions | SetAllTaskOptions
| AddTaskLabels
| RemoveTaskLabels
type GetStateFunc = () => AppState type GetStateFunc = () => AppState
interface Task extends TaskAPI { interface Task extends TaskAPI {
@ -124,6 +134,22 @@ export interface SetTaskOption {
} }
} }
export interface AddTaskLabels {
type: 'ADD_TASK_LABELS'
payload: {
taskID: string
labels: Label[]
}
}
export interface RemoveTaskLabels {
type: 'REMOVE_TASK_LABELS'
payload: {
taskID: string
labels: Label[]
}
}
export const setTaskOption = (taskOption: { export const setTaskOption = (taskOption: {
key: TaskOptionKeys key: TaskOptionKeys
value: string value: string
@ -176,6 +202,27 @@ export const setDropdownOrgID = (dropdownOrgID: string): SetDropdownOrgID => ({
payload: {dropdownOrgID}, payload: {dropdownOrgID},
}) })
const addTaskLabels = (taskID: string, labels: Label[]): AddTaskLabels => ({
type: 'ADD_TASK_LABELS',
payload: {
taskID,
labels,
},
})
const removeTaskLabels = (
taskID: string,
labels: Label[]
): RemoveTaskLabels => ({
type: 'REMOVE_TASK_LABELS',
payload: {
taskID,
labels,
},
})
// Thunks
export const updateTaskStatus = (task: Task) => async dispatch => { export const updateTaskStatus = (task: Task) => async dispatch => {
try { try {
await updateTaskStatusAPI(task.id, task.status) await updateTaskStatusAPI(task.id, task.status)
@ -337,3 +384,42 @@ export const importScript = (script: string, fileName: string) => async (
dispatch(notify(taskImportFailed(fileName, message))) dispatch(notify(taskImportFailed(fileName, message)))
} }
} }
export const addTaskLabelsAsync = (taskID: string, labels: Label[]) => async (
dispatch,
getState: GetStateFunc
): Promise<void> => {
try {
const {
tasks: {tasks},
} = await getState()
const task = tasks.find(t => {
return t.id === taskID
})
const labelsToAdd = labels.filter(label => {
if (!task.labels.find(l => l.name === label.name)) {
return label
}
})
const newLabels = await addTaskLabelsAPI(taskID, labelsToAdd)
dispatch(addTaskLabels(taskID, newLabels))
} catch (error) {
console.error(error)
}
}
export const removeTaskLabelsAsync = (
taskID: string,
labels: Label[]
) => async (dispatch): Promise<void> => {
try {
await removeTaskLabelsAPI(taskID, labels)
dispatch(removeTaskLabels(taskID, labels))
} catch (error) {
console.error(error)
}
}

View File

@ -1,4 +1,6 @@
import {Task} from 'src/api' import _ from 'lodash'
import {Task, Label} from 'src/api'
import {taskAPI} from 'src/utils/api' import {taskAPI} from 'src/utils/api'
export const submitNewTask = async ( export const submitNewTask = async (
@ -41,3 +43,30 @@ export const getTask = async (id): Promise<Task> => {
export const deleteTask = (taskID: string) => { export const deleteTask = (taskID: string) => {
return taskAPI.tasksTaskIDDelete(taskID) return taskAPI.tasksTaskIDDelete(taskID)
} }
export const addTaskLabels = async (
taskID: string,
labels: Label[]
): Promise<Label[]> => {
await Promise.all(
labels.map(async label => {
await taskAPI.tasksTaskIDLabelsPost(taskID, label)
})
)
const {data} = await taskAPI.tasksTaskIDLabelsGet(taskID)
return data.labels
}
export const removeTaskLabels = async (
taskID: string,
labels: Label[]
): Promise<void> => {
await Promise.all(
labels.map(async label => {
const name = _.get(label, 'name', '')
await taskAPI.tasksTaskIDLabelsLabelDelete(taskID, name)
})
)
}

View File

@ -18,6 +18,7 @@ const setup = (override = {}) => {
onActivate: jest.fn(), onActivate: jest.fn(),
onDelete: jest.fn(), onDelete: jest.fn(),
onSelect: jest.fn(), onSelect: jest.fn(),
onEditLabels: jest.fn(),
...override, ...override,
} }

View File

@ -31,6 +31,7 @@ interface Props {
onActivate: (task: Task) => void onActivate: (task: Task) => void
onDelete: (task: Task) => void onDelete: (task: Task) => void
onSelect: (task: Task) => void onSelect: (task: Task) => void
onEditLabels: (task: Task) => void
} }
export class TaskRow extends PureComponent<Props & WithRouterProps> { export class TaskRow extends PureComponent<Props & WithRouterProps> {
@ -99,15 +100,26 @@ export class TaskRow extends PureComponent<Props & WithRouterProps> {
private get labels(): JSX.Element { private get labels(): JSX.Element {
const {task} = this.props const {task} = this.props
if (!task.labels.length) { if (!task.labels.length) {
return return (
<Label.Container
limitChildCount={4}
className="index-list--labels"
onEdit={this.handleEditLabels}
/>
)
} }
return ( return (
<Label.Container limitChildCount={4} resourceName="this Task"> <Label.Container
limitChildCount={4}
resourceName="this Task"
onEdit={this.handleEditLabels}
>
{task.labels.map(label => ( {task.labels.map(label => (
<Label <Label
key={label.resourceID} key={label.name}
id={label.resourceID} id={label.resourceID}
colorHex={label.properties.color} colorHex={label.properties.color}
name={label.name} name={label.name}
@ -126,6 +138,12 @@ export class TaskRow extends PureComponent<Props & WithRouterProps> {
return false return false
} }
private handleEditLabels = () => {
const {task, onEditLabels} = this.props
onEditLabels(task)
}
private changeToggle = () => { private changeToggle = () => {
const {task, onActivate} = this.props const {task, onActivate} = this.props
if (task.status === TaskAPI.StatusEnum.Active) { if (task.status === TaskAPI.StatusEnum.Active) {

View File

@ -6,6 +6,8 @@ import _ from 'lodash'
import {IndexList} from 'src/clockface' import {IndexList} from 'src/clockface'
import TaskRow from 'src/tasks/components/TaskRow' import TaskRow from 'src/tasks/components/TaskRow'
import SortingHat from 'src/shared/components/sorting_hat/SortingHat' import SortingHat from 'src/shared/components/sorting_hat/SortingHat'
import {OverlayTechnology} from 'src/clockface'
import EditLabelsOverlay from 'src/shared/components/EditLabelsOverlay'
// Types // Types
import EmptyTasksList from 'src/tasks/components/EmptyTasksList' import EmptyTasksList from 'src/tasks/components/EmptyTasksList'
@ -16,6 +18,7 @@ interface Task extends TaskAPI {
owner?: User owner?: User
} }
import {Sort} from 'src/clockface' import {Sort} from 'src/clockface'
import {addTaskLabelsAsync, removeTaskLabelsAsync} from 'src/tasks/actions/v2'
interface Props { interface Props {
tasks: Task[] tasks: Task[]
@ -25,6 +28,8 @@ interface Props {
onCreate: () => void onCreate: () => void
onSelect: (task: Task) => void onSelect: (task: Task) => void
totalCount: number totalCount: number
onRemoveTaskLabels: typeof removeTaskLabelsAsync
onAddTaskLabels: typeof addTaskLabelsAsync
} }
type SortKey = keyof Task | 'organization.name' type SortKey = keyof Task | 'organization.name'
@ -32,6 +37,8 @@ type SortKey = keyof Task | 'organization.name'
interface State { interface State {
sortKey: SortKey sortKey: SortKey
sortDirection: Sort sortDirection: Sort
taskLabelsEdit: Task
isEditingTaskLabels: boolean
} }
export default class TasksList extends PureComponent<Props, State> { export default class TasksList extends PureComponent<Props, State> {
@ -40,6 +47,8 @@ export default class TasksList extends PureComponent<Props, State> {
this.state = { this.state = {
sortKey: null, sortKey: null,
sortDirection: Sort.Descending, sortDirection: Sort.Descending,
taskLabelsEdit: null,
isEditingTaskLabels: false,
} }
} }
@ -55,51 +64,54 @@ export default class TasksList extends PureComponent<Props, State> {
] ]
return ( return (
<IndexList> <>
<IndexList.Header> <IndexList>
<IndexList.HeaderCell <IndexList.Header>
columnName="Name" <IndexList.HeaderCell
width="20%" columnName="Name"
sortKey={headerKeys[0]} width="20%"
sort={sortKey === headerKeys[0] ? sortDirection : Sort.None} sortKey={headerKeys[0]}
onClick={this.handleClickColumn} sort={sortKey === headerKeys[0] ? sortDirection : Sort.None}
/> onClick={this.handleClickColumn}
<IndexList.HeaderCell
columnName="Active"
width="10%"
sortKey={headerKeys[1]}
sort={sortKey === headerKeys[1] ? sortDirection : Sort.None}
onClick={this.handleClickColumn}
/>
<IndexList.HeaderCell
columnName="Schedule"
width="20%"
sortKey={headerKeys[2]}
sort={sortKey === headerKeys[2] ? sortDirection : Sort.None}
onClick={this.handleClickColumn}
/>
<IndexList.HeaderCell
columnName="Owner"
width="15%"
sortKey={headerKeys[3]}
sort={sortKey === headerKeys[3] ? sortDirection : Sort.None}
onClick={this.handleClickColumn}
/>
<IndexList.HeaderCell columnName="" width="35%" />
</IndexList.Header>
<IndexList.Body
emptyState={
<EmptyTasksList
searchTerm={searchTerm}
onCreate={onCreate}
totalCount={totalCount}
/> />
} <IndexList.HeaderCell
columnCount={5} columnName="Active"
> width="10%"
{this.sortedRows} sortKey={headerKeys[1]}
</IndexList.Body> sort={sortKey === headerKeys[1] ? sortDirection : Sort.None}
</IndexList> onClick={this.handleClickColumn}
/>
<IndexList.HeaderCell
columnName="Schedule"
width="20%"
sortKey={headerKeys[2]}
sort={sortKey === headerKeys[2] ? sortDirection : Sort.None}
onClick={this.handleClickColumn}
/>
<IndexList.HeaderCell
columnName="Owner"
width="15%"
sortKey={headerKeys[3]}
sort={sortKey === headerKeys[3] ? sortDirection : Sort.None}
onClick={this.handleClickColumn}
/>
<IndexList.HeaderCell columnName="" width="35%" />
</IndexList.Header>
<IndexList.Body
emptyState={
<EmptyTasksList
searchTerm={searchTerm}
onCreate={onCreate}
totalCount={totalCount}
/>
}
columnCount={5}
>
{this.sortedRows}
</IndexList.Body>
</IndexList>
{this.renderLabelEditorOverlay}
</>
) )
} }
@ -118,6 +130,7 @@ export default class TasksList extends PureComponent<Props, State> {
onActivate={onActivate} onActivate={onActivate}
onDelete={onDelete} onDelete={onDelete}
onSelect={onSelect} onSelect={onSelect}
onEditLabels={this.handleStartEditingLabels}
/> />
))} ))}
</> </>
@ -143,4 +156,28 @@ export default class TasksList extends PureComponent<Props, State> {
return null return null
} }
private handleStartEditingLabels = (taskLabelsEdit: Task): void => {
this.setState({taskLabelsEdit, isEditingTaskLabels: true})
}
private handleStopEditingLabels = (): void => {
this.setState({isEditingTaskLabels: false})
}
private get renderLabelEditorOverlay(): JSX.Element {
const {onAddTaskLabels, onRemoveTaskLabels} = this.props
const {isEditingTaskLabels, taskLabelsEdit} = this.state
return (
<OverlayTechnology visible={isEditingTaskLabels}>
<EditLabelsOverlay<Task>
resource={taskLabelsEdit}
onDismissOverlay={this.handleStopEditingLabels}
onAddLabels={onAddTaskLabels}
onRemoveLabels={onRemoveTaskLabels}
/>
</OverlayTechnology>
)
}
} }

View File

@ -18,6 +18,12 @@ exports[`Tasks.Components.TaskRow renders 1`] = `
> >
pasdlak pasdlak
</a> </a>
<LabelContainer
className="index-list--labels"
limitChildCount={4}
onEdit={[Function]}
resourceName="this resource"
/>
</ComponentSpacer> </ComponentSpacer>
</IndexListRowCell> </IndexListRowCell>
<IndexListRowCell <IndexListRowCell

View File

@ -1,138 +1,148 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TasksList rendering renders 1`] = ` exports[`TasksList rendering renders 1`] = `
<IndexList> <Fragment>
<IndexListHeader> <IndexList>
<IndexListHeaderCell <IndexListHeader>
alignment="left" <IndexListHeaderCell
columnName="Name" alignment="left"
onClick={[Function]} columnName="Name"
sort="none" onClick={[Function]}
sortKey="name" sort="none"
width="20%" sortKey="name"
/> width="20%"
<IndexListHeaderCell
alignment="left"
columnName="Active"
onClick={[Function]}
sort="none"
sortKey="status"
width="10%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Schedule"
onClick={[Function]}
sort="none"
sortKey="every"
width="20%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Owner"
onClick={[Function]}
sort="none"
sortKey="organization.name"
width="15%"
/>
<IndexListHeaderCell
alignment="left"
columnName=""
width="35%"
/>
</IndexListHeader>
<IndexListBody
columnCount={5}
emptyState={
<EmptyTasksLists
onCreate={[Function]}
searchTerm=""
/> />
} <IndexListHeaderCell
> alignment="left"
<SortingHat columnName="Active"
direction="desc" onClick={[Function]}
list={ sort="none"
Array [ sortKey="status"
Object { width="10%"
"cron": "2 0 * * *", />
"flux": "option task = { <IndexListHeaderCell
alignment="left"
columnName="Schedule"
onClick={[Function]}
sort="none"
sortKey="every"
width="20%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Owner"
onClick={[Function]}
sort="none"
sortKey="organization.name"
width="15%"
/>
<IndexListHeaderCell
alignment="left"
columnName=""
width="35%"
/>
</IndexListHeader>
<IndexListBody
columnCount={5}
emptyState={
<EmptyTasksLists
onCreate={[Function]}
searchTerm=""
/>
}
>
<SortingHat
direction="desc"
list={
Array [
Object {
"cron": "2 0 * * *",
"flux": "option task = {
name: \\"pasdlak\\", name: \\"pasdlak\\",
cron: \\"2 0 * * *\\" cron: \\"2 0 * * *\\"
} }
from(bucket: \\"inbucket\\") from(bucket: \\"inbucket\\")
|> range(start: -1h)", |> range(start: -1h)",
"id": "02ef9deff2141000", "id": "02ef9deff2141000",
"labels": Array [], "labels": Array [],
"name": "pasdlak", "name": "pasdlak",
"orgID": "02ee9e2a29d73000", "orgID": "02ee9e2a29d73000",
"organization": Object { "organization": Object {
"id": "02ee9e2a29d73000", "id": "02ee9e2a29d73000",
"links": Object { "links": Object {
"buckets": "/api/v2/buckets?org=RadicalOrganization", "buckets": "/api/v2/buckets?org=RadicalOrganization",
"dashboards": "/api/v2/dashboards?org=RadicalOrganization", "dashboards": "/api/v2/dashboards?org=RadicalOrganization",
"self": "/api/v2/orgs/02ee9e2a29d73000", "self": "/api/v2/orgs/02ee9e2a29d73000",
"tasks": "/api/v2/tasks?org=RadicalOrganization", "tasks": "/api/v2/tasks?org=RadicalOrganization",
},
"name": "RadicalOrganization",
}, },
"name": "RadicalOrganization", "owner": Object {
"id": "02ee9e2a19d73000",
"name": "",
},
"status": "active",
}, },
"owner": Object { Object {
"id": "02ee9e2a19d73000", "every": "1m0s",
"name": "", "flux": "option task = {
},
"status": "active",
},
Object {
"every": "1m0s",
"flux": "option task = {
name: \\"somename\\", name: \\"somename\\",
every: 1m, every: 1m,
} }
from(bucket: \\"inbucket\\") from(bucket: \\"inbucket\\")
|> range(start: -task.every)", |> range(start: -task.every)",
"id": "02f12c50dba72000", "id": "02f12c50dba72000",
"labels": Array [ "labels": Array [
Object { Object {
"name": "Trogdor", "name": "Trogdor",
"properties": Object { "properties": Object {
"color": "#44ffcc", "color": "#44ffcc",
"description": "Burninating the countryside", "description": "Burninating the countryside",
},
"resourceID": "dashboard-mock-label-a",
}, },
"resourceID": "dashboard-mock-label-a", Object {
}, "name": "Strawberry",
Object { "properties": Object {
"name": "Strawberry", "color": "#ff0054",
"properties": Object { "description": "It is a great fruit",
"color": "#ff0054", },
"description": "It is a great fruit", "resourceID": "dashboard-mock-label-b",
}, },
"resourceID": "dashboard-mock-label-b", ],
"name": "somename",
"orgID": "02ee9e2a29d73000",
"organization": Object {
"id": "02ee9e2a29d73000",
"links": Object {
"buckets": "/api/v2/buckets?org=RadicalOrganization",
"dashboards": "/api/v2/dashboards?org=RadicalOrganization",
"self": "/api/v2/orgs/02ee9e2a29d73000",
"tasks": "/api/v2/tasks?org=RadicalOrganization",
},
"name": "RadicalOrganization",
}, },
], "owner": Object {
"name": "somename", "id": "02ee9e2a19d73000",
"orgID": "02ee9e2a29d73000", "name": "",
"organization": Object {
"id": "02ee9e2a29d73000",
"links": Object {
"buckets": "/api/v2/buckets?org=RadicalOrganization",
"dashboards": "/api/v2/dashboards?org=RadicalOrganization",
"self": "/api/v2/orgs/02ee9e2a29d73000",
"tasks": "/api/v2/tasks?org=RadicalOrganization",
}, },
"name": "RadicalOrganization", "status": "active",
}, },
"owner": Object { ]
"id": "02ee9e2a19d73000", }
"name": "", sortKey={null}
}, >
"status": "active", <Component />
}, </SortingHat>
] </IndexListBody>
} </IndexList>
sortKey={null} <OverlayTechnology
> visible={false}
<Component /> >
</SortingHat> <EditLabelsOverlay
</IndexListBody> onDismissOverlay={[Function]}
</IndexList> resource={null}
/>
</OverlayTechnology>
</Fragment>
`; `;

View File

@ -21,6 +21,8 @@ import {
setShowInactive as setShowInactiveAction, setShowInactive as setShowInactiveAction,
setDropdownOrgID as setDropdownOrgIDAction, setDropdownOrgID as setDropdownOrgIDAction,
importScript, importScript,
addTaskLabelsAsync,
removeTaskLabelsAsync,
} from 'src/tasks/actions/v2' } from 'src/tasks/actions/v2'
// Constants // Constants
@ -48,6 +50,8 @@ interface ConnectedDispatchProps {
setShowInactive: typeof setShowInactiveAction setShowInactive: typeof setShowInactiveAction
setDropdownOrgID: typeof setDropdownOrgIDAction setDropdownOrgID: typeof setDropdownOrgIDAction
importScript: typeof importScript importScript: typeof importScript
onAddTaskLabels: typeof addTaskLabelsAsync
onRemoveTaskLabels: typeof removeTaskLabelsAsync
} }
interface ConnectedStateProps { interface ConnectedStateProps {
@ -61,7 +65,8 @@ interface ConnectedStateProps {
type Props = ConnectedDispatchProps & PassedInProps & ConnectedStateProps type Props = ConnectedDispatchProps & PassedInProps & ConnectedStateProps
interface State { interface State {
isOverlayVisible: boolean isImportOverlayVisible: boolean
taskLabelsEdit: Task
} }
@ErrorHandling @ErrorHandling
@ -75,7 +80,10 @@ class TasksPage extends PureComponent<Props, State> {
} }
props.setDropdownOrgID(null) props.setDropdownOrgID(null)
this.state = {isOverlayVisible: false} this.state = {
isImportOverlayVisible: false,
taskLabelsEdit: null,
}
} }
public render(): JSX.Element { public render(): JSX.Element {
@ -84,6 +92,8 @@ class TasksPage extends PureComponent<Props, State> {
searchTerm, searchTerm,
setShowInactive, setShowInactive,
showInactive, showInactive,
onAddTaskLabels,
onRemoveTaskLabels,
} = this.props } = this.props
return ( return (
@ -106,6 +116,8 @@ class TasksPage extends PureComponent<Props, State> {
onDelete={this.handleDelete} onDelete={this.handleDelete}
onCreate={this.handleCreateTask} onCreate={this.handleCreateTask}
onSelect={this.props.selectTask} onSelect={this.props.selectTask}
onAddTaskLabels={onAddTaskLabels}
onRemoveTaskLabels={onRemoveTaskLabels}
/> />
{this.hiddenTaskAlert} {this.hiddenTaskAlert}
</div> </div>
@ -134,7 +146,7 @@ class TasksPage extends PureComponent<Props, State> {
} }
private handleToggleOverlay = () => { private handleToggleOverlay = () => {
this.setState({isOverlayVisible: !this.state.isOverlayVisible}) this.setState({isImportOverlayVisible: !this.state.isImportOverlayVisible})
} }
private handleSave = (script: string, fileName: string) => { private handleSave = (script: string, fileName: string) => {
@ -142,10 +154,10 @@ class TasksPage extends PureComponent<Props, State> {
} }
private get renderImportOverlay(): JSX.Element { private get renderImportOverlay(): JSX.Element {
const {isOverlayVisible} = this.state const {isImportOverlayVisible} = this.state
return ( return (
<OverlayTechnology visible={isOverlayVisible}> <OverlayTechnology visible={isImportOverlayVisible}>
<ImportTaskOverlay <ImportTaskOverlay
onDismissOverlay={this.handleToggleOverlay} onDismissOverlay={this.handleToggleOverlay}
onSave={this.handleSave} onSave={this.handleSave}
@ -224,6 +236,8 @@ const mdtp: ConnectedDispatchProps = {
setShowInactive: setShowInactiveAction, setShowInactive: setShowInactiveAction,
setDropdownOrgID: setDropdownOrgIDAction, setDropdownOrgID: setDropdownOrgIDAction,
importScript, importScript,
onRemoveTaskLabels: removeTaskLabelsAsync,
onAddTaskLabels: addTaskLabelsAsync,
} }
export default connect< export default connect<

View File

@ -1,138 +1,148 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TasksList rendering renders 1`] = ` exports[`TasksList rendering renders 1`] = `
<IndexList> <Fragment>
<IndexListHeader> <IndexList>
<IndexListHeaderCell <IndexListHeader>
alignment="left" <IndexListHeaderCell
columnName="Name" alignment="left"
onClick={[Function]} columnName="Name"
sort="none" onClick={[Function]}
sortKey="name" sort="none"
width="20%" sortKey="name"
/> width="20%"
<IndexListHeaderCell
alignment="left"
columnName="Active"
onClick={[Function]}
sort="none"
sortKey="status"
width="10%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Schedule"
onClick={[Function]}
sort="none"
sortKey="every"
width="20%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Owner"
onClick={[Function]}
sort="none"
sortKey="organization.name"
width="15%"
/>
<IndexListHeaderCell
alignment="left"
columnName=""
width="35%"
/>
</IndexListHeader>
<IndexListBody
columnCount={5}
emptyState={
<EmptyTasksLists
onCreate={[Function]}
searchTerm=""
/> />
} <IndexListHeaderCell
> alignment="left"
<SortingHat columnName="Active"
direction="desc" onClick={[Function]}
list={ sort="none"
Array [ sortKey="status"
Object { width="10%"
"cron": "2 0 * * *", />
"flux": "option task = { <IndexListHeaderCell
alignment="left"
columnName="Schedule"
onClick={[Function]}
sort="none"
sortKey="every"
width="20%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Owner"
onClick={[Function]}
sort="none"
sortKey="organization.name"
width="15%"
/>
<IndexListHeaderCell
alignment="left"
columnName=""
width="35%"
/>
</IndexListHeader>
<IndexListBody
columnCount={5}
emptyState={
<EmptyTasksLists
onCreate={[Function]}
searchTerm=""
/>
}
>
<SortingHat
direction="desc"
list={
Array [
Object {
"cron": "2 0 * * *",
"flux": "option task = {
name: \\"pasdlak\\", name: \\"pasdlak\\",
cron: \\"2 0 * * *\\" cron: \\"2 0 * * *\\"
} }
from(bucket: \\"inbucket\\") from(bucket: \\"inbucket\\")
|> range(start: -1h)", |> range(start: -1h)",
"id": "02ef9deff2141000", "id": "02ef9deff2141000",
"labels": Array [], "labels": Array [],
"name": "pasdlak", "name": "pasdlak",
"orgID": "02ee9e2a29d73000", "orgID": "02ee9e2a29d73000",
"organization": Object { "organization": Object {
"id": "02ee9e2a29d73000", "id": "02ee9e2a29d73000",
"links": Object { "links": Object {
"buckets": "/api/v2/buckets?org=RadicalOrganization", "buckets": "/api/v2/buckets?org=RadicalOrganization",
"dashboards": "/api/v2/dashboards?org=RadicalOrganization", "dashboards": "/api/v2/dashboards?org=RadicalOrganization",
"self": "/api/v2/orgs/02ee9e2a29d73000", "self": "/api/v2/orgs/02ee9e2a29d73000",
"tasks": "/api/v2/tasks?org=RadicalOrganization", "tasks": "/api/v2/tasks?org=RadicalOrganization",
},
"name": "RadicalOrganization",
}, },
"name": "RadicalOrganization", "owner": Object {
"id": "02ee9e2a19d73000",
"name": "",
},
"status": "active",
}, },
"owner": Object { Object {
"id": "02ee9e2a19d73000", "every": "1m0s",
"name": "", "flux": "option task = {
},
"status": "active",
},
Object {
"every": "1m0s",
"flux": "option task = {
name: \\"somename\\", name: \\"somename\\",
every: 1m, every: 1m,
} }
from(bucket: \\"inbucket\\") from(bucket: \\"inbucket\\")
|> range(start: -task.every)", |> range(start: -task.every)",
"id": "02f12c50dba72000", "id": "02f12c50dba72000",
"labels": Array [ "labels": Array [
Object { Object {
"name": "Trogdor", "name": "Trogdor",
"properties": Object { "properties": Object {
"color": "#44ffcc", "color": "#44ffcc",
"description": "Burninating the countryside", "description": "Burninating the countryside",
},
"resourceID": "dashboard-mock-label-a",
}, },
"resourceID": "dashboard-mock-label-a", Object {
}, "name": "Strawberry",
Object { "properties": Object {
"name": "Strawberry", "color": "#ff0054",
"properties": Object { "description": "It is a great fruit",
"color": "#ff0054", },
"description": "It is a great fruit", "resourceID": "dashboard-mock-label-b",
}, },
"resourceID": "dashboard-mock-label-b", ],
"name": "somename",
"orgID": "02ee9e2a29d73000",
"organization": Object {
"id": "02ee9e2a29d73000",
"links": Object {
"buckets": "/api/v2/buckets?org=RadicalOrganization",
"dashboards": "/api/v2/dashboards?org=RadicalOrganization",
"self": "/api/v2/orgs/02ee9e2a29d73000",
"tasks": "/api/v2/tasks?org=RadicalOrganization",
},
"name": "RadicalOrganization",
}, },
], "owner": Object {
"name": "somename", "id": "02ee9e2a19d73000",
"orgID": "02ee9e2a29d73000", "name": "",
"organization": Object {
"id": "02ee9e2a29d73000",
"links": Object {
"buckets": "/api/v2/buckets?org=RadicalOrganization",
"dashboards": "/api/v2/dashboards?org=RadicalOrganization",
"self": "/api/v2/orgs/02ee9e2a29d73000",
"tasks": "/api/v2/tasks?org=RadicalOrganization",
}, },
"name": "RadicalOrganization", "status": "active",
}, },
"owner": Object { ]
"id": "02ee9e2a19d73000", }
"name": "", sortKey={null}
}, >
"status": "active", <Component />
}, </SortingHat>
] </IndexListBody>
} </IndexList>
sortKey={null} <OverlayTechnology
> visible={false}
<Component /> >
</SortingHat> <EditLabelsOverlay
</IndexListBody> onDismissOverlay={[Function]}
</IndexList> resource={null}
/>
</OverlayTechnology>
</Fragment>
`; `;

View File

@ -93,6 +93,35 @@ export default (state: State = defaultState, action: Action): State => {
case 'SET_DROPDOWN_ORG_ID': case 'SET_DROPDOWN_ORG_ID':
const {dropdownOrgID} = action.payload const {dropdownOrgID} = action.payload
return {...state, dropdownOrgID} return {...state, dropdownOrgID}
case 'ADD_TASK_LABELS':
const {taskID, labels} = action.payload
const updatedTasks = state.tasks.map(t => {
if (t.id === taskID) {
return {...t, labels: [...labels]}
}
return t
})
return {...state, tasks: [...updatedTasks]}
case 'REMOVE_TASK_LABELS': {
const {taskID, labels} = action.payload
const updatedTasks = state.tasks.map(t => {
if (t.id === taskID) {
const updatedLabels = t.labels.filter(l => {
if (!labels.find(label => label.name === l.name)) {
return l
}
})
return {...t, labels: updatedLabels}
}
return t
})
return {...state, tasks: [...updatedTasks]}
}
default: default:
return state return state
} }