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:
schema:
$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:
get:
x-generated: true
@ -5947,4 +6091,15 @@ components:
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"}
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;
}
/**
*
* @export
* @interface InlineResponse2005
*/
export interface InlineResponse2005 {
/**
*
* @type {Array<string>}
* @memberof InlineResponse2005
*/
labels?: Array<string>;
/**
*
* @type {Links}
* @memberof InlineResponse2005
*/
links?: Links;
}
/**
*
* @export
@ -1216,6 +1236,32 @@ export interface Label {
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.
* @export
@ -12046,6 +12092,188 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration
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
@ -12586,6 +12814,70 @@ export const TasksApiFp = function(configuration?: Configuration) {
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
@ -12817,6 +13109,54 @@ export const TasksApiFactory = function (configuration?: Configuration, basePath
tasksTaskIDGet(taskID: string, options?: any) {
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
@ -13009,6 +13349,62 @@ export class TasksApi extends BaseAPI {
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

View File

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

View File

@ -17,25 +17,21 @@ import {
} from 'src/clockface'
import FetchLabels from 'src/shared/components/FetchLabels'
// Actions
import {
addDashboardLabelsAsync,
removeDashboardLabelsAsync,
} from 'src/dashboards/actions/v2'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
// Types
import {Dashboard} from 'src/types/v2'
import {Label} from 'src/api'
import {RemoteDataState} from 'src/types'
interface Props {
dashboard: Dashboard
// Utils
import {getDeep} from 'src/utils/wrappers'
interface Props<T> {
resource: T
onDismissOverlay: () => void
onAddDashboardLabels: typeof addDashboardLabelsAsync
onRemoveDashboardLabels: typeof removeDashboardLabelsAsync
onAddLabels: (resourceID: string, labels: Label[]) => void
onRemoveLabels: (resourceID: string, labels: Label[]) => void
}
interface State {
@ -44,18 +40,18 @@ interface State {
}
@ErrorHandling
class EditDashboardLabelsOverlay extends PureComponent<Props, State> {
constructor(props: Props) {
class EditLabelsOverlay<T> extends PureComponent<Props<T>, State> {
constructor(props: Props<T>) {
super(props)
this.state = {
selectedLabels: props.dashboard.labels,
selectedLabels: _.get(props, 'resource.labels'),
loading: RemoteDataState.NotStarted,
}
}
public render() {
const {onDismissOverlay, dashboard} = this.props
const {onDismissOverlay, resource} = this.props
const {selectedLabels} = this.state
return (
@ -73,7 +69,7 @@ class EditDashboardLabelsOverlay extends PureComponent<Props, State> {
<LabelSelector
inputSize={ComponentSize.Medium}
allLabels={labels}
resourceType={dashboard.name}
resourceType={_.get(resource, 'name')}
selectedLabels={selectedLabels}
onAddLabel={this.handleAddLabel}
onRemoveLabel={this.handleRemoveLabel}
@ -118,15 +114,12 @@ class EditDashboardLabelsOverlay extends PureComponent<Props, State> {
removedLabels: Label[]
addedLabels: Label[]
} {
const {dashboard} = this.props
const {resource} = this.props
const {selectedLabels} = this.state
const labels = getDeep<Label[]>(resource, 'labels', [])
const intersection = _.intersectionBy(
dashboard.labels,
selectedLabels,
'name'
)
const removedLabels = _.differenceBy(dashboard.labels, intersection, 'name')
const intersection = _.intersectionBy(labels, selectedLabels, 'name')
const removedLabels = _.differenceBy(labels, intersection, 'name')
const addedLabels = _.differenceBy(selectedLabels, intersection, 'name')
return {
@ -155,23 +148,19 @@ class EditDashboardLabelsOverlay extends PureComponent<Props, State> {
}
private handleSaveLabels = async (): Promise<void> => {
const {
onAddDashboardLabels,
onRemoveDashboardLabels,
dashboard,
onDismissOverlay,
} = this.props
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 onAddDashboardLabels(dashboard.id, addedLabels)
await onAddLabels(resourceID, addedLabels)
}
if (removedLabels.length) {
await onRemoveDashboardLabels(dashboard.id, removedLabels)
await onRemoveLabels(resourceID, removedLabels)
}
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 _ from 'lodash'
// APIs
import {Task as TaskAPI, Organization} from 'src/api'
import {
submitNewTask,
@ -10,6 +11,8 @@ import {
getTask,
updateTaskStatus as updateTaskStatusAPI,
deleteTask as deleteTaskAPI,
addTaskLabels as addTaskLabelsAPI,
removeTaskLabels as removeTaskLabelsAPI,
} from 'src/tasks/api/v2'
import {getMe} from 'src/shared/apis/v2/user'
import {notify} from 'src/shared/actions/notifications'
@ -22,8 +25,13 @@ import {
taskImportFailed,
taskImportSuccess,
} 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 {
taskOptionsToFluxScript,
TaskOptionKeys,
@ -42,6 +50,8 @@ export type Action =
| ClearTask
| SetTaskOption
| SetAllTaskOptions
| AddTaskLabels
| RemoveTaskLabels
type GetStateFunc = () => AppState
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: {
key: TaskOptionKeys
value: string
@ -176,6 +202,27 @@ export const setDropdownOrgID = (dropdownOrgID: string): SetDropdownOrgID => ({
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 => {
try {
await updateTaskStatusAPI(task.id, task.status)
@ -337,3 +384,42 @@ export const importScript = (script: string, fileName: string) => async (
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'
export const submitNewTask = async (
@ -41,3 +43,30 @@ export const getTask = async (id): Promise<Task> => {
export const deleteTask = (taskID: string) => {
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(),
onDelete: jest.fn(),
onSelect: jest.fn(),
onEditLabels: jest.fn(),
...override,
}

View File

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

View File

@ -6,6 +6,8 @@ import _ from 'lodash'
import {IndexList} from 'src/clockface'
import TaskRow from 'src/tasks/components/TaskRow'
import SortingHat from 'src/shared/components/sorting_hat/SortingHat'
import {OverlayTechnology} from 'src/clockface'
import EditLabelsOverlay from 'src/shared/components/EditLabelsOverlay'
// Types
import EmptyTasksList from 'src/tasks/components/EmptyTasksList'
@ -16,6 +18,7 @@ interface Task extends TaskAPI {
owner?: User
}
import {Sort} from 'src/clockface'
import {addTaskLabelsAsync, removeTaskLabelsAsync} from 'src/tasks/actions/v2'
interface Props {
tasks: Task[]
@ -25,6 +28,8 @@ interface Props {
onCreate: () => void
onSelect: (task: Task) => void
totalCount: number
onRemoveTaskLabels: typeof removeTaskLabelsAsync
onAddTaskLabels: typeof addTaskLabelsAsync
}
type SortKey = keyof Task | 'organization.name'
@ -32,6 +37,8 @@ type SortKey = keyof Task | 'organization.name'
interface State {
sortKey: SortKey
sortDirection: Sort
taskLabelsEdit: Task
isEditingTaskLabels: boolean
}
export default class TasksList extends PureComponent<Props, State> {
@ -40,6 +47,8 @@ export default class TasksList extends PureComponent<Props, State> {
this.state = {
sortKey: null,
sortDirection: Sort.Descending,
taskLabelsEdit: null,
isEditingTaskLabels: false,
}
}
@ -55,6 +64,7 @@ export default class TasksList extends PureComponent<Props, State> {
]
return (
<>
<IndexList>
<IndexList.Header>
<IndexList.HeaderCell
@ -100,6 +110,8 @@ export default class TasksList extends PureComponent<Props, State> {
{this.sortedRows}
</IndexList.Body>
</IndexList>
{this.renderLabelEditorOverlay}
</>
)
}
@ -118,6 +130,7 @@ export default class TasksList extends PureComponent<Props, State> {
onActivate={onActivate}
onDelete={onDelete}
onSelect={onSelect}
onEditLabels={this.handleStartEditingLabels}
/>
))}
</>
@ -143,4 +156,28 @@ export default class TasksList extends PureComponent<Props, State> {
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
</a>
<LabelContainer
className="index-list--labels"
limitChildCount={4}
onEdit={[Function]}
resourceName="this resource"
/>
</ComponentSpacer>
</IndexListRowCell>
<IndexListRowCell

View File

@ -1,7 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TasksList rendering renders 1`] = `
<IndexList>
<Fragment>
<IndexList>
<IndexListHeader>
<IndexListHeaderCell
alignment="left"
@ -134,5 +135,14 @@ from(bucket: \\"inbucket\\")
<Component />
</SortingHat>
</IndexListBody>
</IndexList>
</IndexList>
<OverlayTechnology
visible={false}
>
<EditLabelsOverlay
onDismissOverlay={[Function]}
resource={null}
/>
</OverlayTechnology>
</Fragment>
`;

View File

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

View File

@ -1,7 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TasksList rendering renders 1`] = `
<IndexList>
<Fragment>
<IndexList>
<IndexListHeader>
<IndexListHeaderCell
alignment="left"
@ -134,5 +135,14 @@ from(bucket: \\"inbucket\\")
<Component />
</SortingHat>
</IndexListBody>
</IndexList>
</IndexList>
<OverlayTechnology
visible={false}
>
<EditLabelsOverlay
onDismissOverlay={[Function]}
resource={null}
/>
</OverlayTechnology>
</Fragment>
`;

View File

@ -93,6 +93,35 @@ export default (state: State = defaultState, action: Action): State => {
case 'SET_DROPDOWN_ORG_ID':
const {dropdownOrgID} = action.payload
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:
return state
}