From d9a3b6d76c840179196e2972024533963002728a Mon Sep 17 00:00:00 2001 From: Alirie Gray Date: Tue, 15 Jan 2019 17:02:32 -0800 Subject: [PATCH] Feat/edit labels on tasks (#11122) * Replace EditDashboardLabelsOverlay with generic EditLabelsOverlay * Add ability to add/remove labels from a task --- http/cur_swagger.yml | 157 ++++++- ui/src/api/api.ts | 396 ++++++++++++++++++ .../dashboard_index/DashboardsIndex.tsx | 10 +- .../components/EditLabelsOverlay.tsx} | 53 +-- ui/src/tasks/actions/v2/index.ts | 90 +++- ui/src/tasks/api/v2/index.ts | 31 +- ui/src/tasks/components/TaskRow.test.tsx | 1 + ui/src/tasks/components/TaskRow.tsx | 24 +- ui/src/tasks/components/TasksList.tsx | 125 ++++-- .../__snapshots__/TaskRow.test.tsx.snap | 6 + .../__snapshots__/TasksList.test.tsx.snap | 242 ++++++----- ui/src/tasks/containers/TasksPage.tsx | 24 +- .../__snapshots__/TasksPage.test.tsx.snap | 242 ++++++----- ui/src/tasks/reducers/v2/index.ts | 29 ++ 14 files changed, 1105 insertions(+), 325 deletions(-) rename ui/src/{dashboards/components/EditDashboardLabelsOverlay.tsx => shared/components/EditLabelsOverlay.tsx} (77%) diff --git a/http/cur_swagger.yml b/http/cur_swagger.yml index e62104c383..9bcae0bf9b 100644 --- a/http/cur_swagger.yml +++ b/http/cur_swagger.yml @@ -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"} + \ No newline at end of file diff --git a/ui/src/api/api.ts b/ui/src/api/api.ts index c21722645d..f8d9d3663c 100644 --- a/ui/src/api/api.ts +++ b/ui/src/api/api.ts @@ -1176,6 +1176,26 @@ export interface InlineResponse2004 { links?: Links; } +/** + * + * @export + * @interface InlineResponse2005 + */ +export interface InlineResponse2005 { + /** + * + * @type {Array} + * @memberof InlineResponse2005 + */ + labels?: Array; + /** + * + * @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 = ("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 = ("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 { + 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 { + 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 { + 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 { + 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 diff --git a/ui/src/dashboards/components/dashboard_index/DashboardsIndex.tsx b/ui/src/dashboards/components/dashboard_index/DashboardsIndex.tsx index 08429ff915..1e545fc8e3 100644 --- a/ui/src/dashboards/components/dashboard_index/DashboardsIndex.tsx +++ b/ui/src/dashboards/components/dashboard_index/DashboardsIndex.tsx @@ -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 { return ( - + resource={dashboardLabelsEdit} onDismissOverlay={this.handleStopEditingLabels} - onAddDashboardLabels={onAddDashboardLabels} - onRemoveDashboardLabels={onRemoveDashboardLabels} + onAddLabels={onAddDashboardLabels} + onRemoveLabels={onRemoveDashboardLabels} /> ) diff --git a/ui/src/dashboards/components/EditDashboardLabelsOverlay.tsx b/ui/src/shared/components/EditLabelsOverlay.tsx similarity index 77% rename from ui/src/dashboards/components/EditDashboardLabelsOverlay.tsx rename to ui/src/shared/components/EditLabelsOverlay.tsx index c71b49dfaf..5261a8f5aa 100644 --- a/ui/src/dashboards/components/EditDashboardLabelsOverlay.tsx +++ b/ui/src/shared/components/EditLabelsOverlay.tsx @@ -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 { + 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 { - constructor(props: Props) { +class EditLabelsOverlay extends PureComponent, State> { + constructor(props: Props) { 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 { { removedLabels: Label[] addedLabels: Label[] } { - const {dashboard} = this.props + const {resource} = this.props const {selectedLabels} = this.state + const labels = getDeep(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 { } private handleSaveLabels = async (): Promise => { - 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 { } } -export default EditDashboardLabelsOverlay +export default EditLabelsOverlay diff --git a/ui/src/tasks/actions/v2/index.ts b/ui/src/tasks/actions/v2/index.ts index 1f827d33c9..adde58cd32 100644 --- a/ui/src/tasks/actions/v2/index.ts +++ b/ui/src/tasks/actions/v2/index.ts @@ -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 => { + 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 => { + try { + await removeTaskLabelsAPI(taskID, labels) + dispatch(removeTaskLabels(taskID, labels)) + } catch (error) { + console.error(error) + } +} diff --git a/ui/src/tasks/api/v2/index.ts b/ui/src/tasks/api/v2/index.ts index 3e5b2d7300..0c517f7929 100644 --- a/ui/src/tasks/api/v2/index.ts +++ b/ui/src/tasks/api/v2/index.ts @@ -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 => { export const deleteTask = (taskID: string) => { return taskAPI.tasksTaskIDDelete(taskID) } + +export const addTaskLabels = async ( + taskID: string, + labels: Label[] +): Promise => { + 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 => { + await Promise.all( + labels.map(async label => { + const name = _.get(label, 'name', '') + await taskAPI.tasksTaskIDLabelsLabelDelete(taskID, name) + }) + ) +} diff --git a/ui/src/tasks/components/TaskRow.test.tsx b/ui/src/tasks/components/TaskRow.test.tsx index 788bf941a2..f823f1037d 100644 --- a/ui/src/tasks/components/TaskRow.test.tsx +++ b/ui/src/tasks/components/TaskRow.test.tsx @@ -18,6 +18,7 @@ const setup = (override = {}) => { onActivate: jest.fn(), onDelete: jest.fn(), onSelect: jest.fn(), + onEditLabels: jest.fn(), ...override, } diff --git a/ui/src/tasks/components/TaskRow.tsx b/ui/src/tasks/components/TaskRow.tsx index bdef2886ad..d5c586af5a 100644 --- a/ui/src/tasks/components/TaskRow.tsx +++ b/ui/src/tasks/components/TaskRow.tsx @@ -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 { @@ -99,15 +100,26 @@ export class TaskRow extends PureComponent { private get labels(): JSX.Element { const {task} = this.props + if (!task.labels.length) { - return + return ( + + ) } return ( - + {task.labels.map(label => (