diff --git a/engine/api/api_routes.go b/engine/api/api_routes.go index c5cf68bd90..a84846355c 100644 --- a/engine/api/api_routes.go +++ b/engine/api/api_routes.go @@ -447,6 +447,7 @@ func (api *API) InitRouter() { r.Handle("/template/{groupName}/{templateSlug}/apply", r.POST(api.applyTemplateHandler)) r.Handle("/template/{groupName}/{templateSlug}/instance", r.GET(api.getTemplateInstancesHandler)) r.Handle("/template/{groupName}/{templateSlug}/audit", r.GET(api.getTemplateAuditsHandler)) + r.Handle("/template/{groupName}/{templateSlug}/usage", r.GET(api.getTemplateUsageHandler)) r.Handle("/project/{key}/workflow/{permWorkflowName}/templateInstance", r.GET(api.getTemplateInstanceHandler)) //Not Found handler diff --git a/engine/api/templates.go b/engine/api/templates.go index 89d8ec4edb..7c7d41764e 100644 --- a/engine/api/templates.go +++ b/engine/api/templates.go @@ -583,3 +583,20 @@ func (api *API) getTemplateAuditsHandler() service.Handler { return service.WriteJSON(w, as, http.StatusOK) } } + +func (api *API) getTemplateUsageHandler() service.Handler { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + ctx, err := api.middlewareTemplate(false)(ctx, w, r) + if err != nil { + return err + } + wfTmpl := getWorkflowTemplate(ctx) + + wfs, err := workflow.LoadByWorkflowTemplateID(ctx, api.mustDB(), wfTmpl.ID) + if err != nil { + return sdk.WrapError(err, "Cannot load templates") + } + + return service.WriteJSON(w, wfs, http.StatusOK) + } +} diff --git a/engine/api/workflow/dao.go b/engine/api/workflow/dao.go index a0647acd57..67f76a9bac 100644 --- a/engine/api/workflow/dao.go +++ b/engine/api/workflow/dao.go @@ -409,6 +409,35 @@ func LoadByEnvName(db gorp.SqlExecutor, projectKey string, envName string) ([]sd return res, nil } +// LoadByWorkflowTemplateID load all workflows linked to a workflow template but without loading workflow details +func LoadByWorkflowTemplateID(ctx context.Context, db gorp.SqlExecutor, templateID int64) ([]sdk.Workflow, error) { + var dbRes []Workflow + query := ` + SELECT workflow.* + FROM workflow + JOIN workflow_template_instance ON workflow_template_instance.workflow_id = workflow.id + WHERE workflow_template_instance.workflow_template_id = $1 AND workflow.to_delete = false + ` + if _, err := db.Select(&dbRes, query, templateID); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + + workflows := make([]sdk.Workflow, len(dbRes)) + for i, wf := range dbRes { + var err error + wf.ProjectKey, err = db.SelectStr("SELECT projectkey FROM project WHERE id = $1", wf.ProjectID) + if err != nil { + return nil, sdk.WrapError(err, "cannot load project key for workflow %s and project_id %d", wf.Name, wf.ProjectID) + } + workflows[i] = sdk.Workflow(wf) + } + + return workflows, nil +} + func load(ctx context.Context, db gorp.SqlExecutor, store cache.Store, proj *sdk.Project, opts LoadOptions, u *sdk.User, query string, args ...interface{}) (*sdk.Workflow, error) { t0 := time.Now() dbRes := Workflow{} diff --git a/ui/src/app/service/workflow-template/workflow-template.service.ts b/ui/src/app/service/workflow-template/workflow-template.service.ts index 47e4422cee..251ff7d9f0 100644 --- a/ui/src/app/service/workflow-template/workflow-template.service.ts +++ b/ui/src/app/service/workflow-template/workflow-template.service.ts @@ -9,6 +9,7 @@ import { WorkflowTemplateInstance, WorkflowTemplateRequest } from '../../model/workflow-template.model'; +import { Workflow } from '../../model/workflow.model'; @Injectable() export class WorkflowTemplateService { @@ -54,4 +55,8 @@ export class WorkflowTemplateService { let url = '/template/' + groupName + '/' + templateSlug + '/audit' + (version ? '?sinceVersion=' + version : ''); return this._http.get>(url); } + + getWorkflowTemplateUsage(groupName: string, templateSlug: string): Observable> { + return this._http.get>('/template/' + groupName + '/' + templateSlug + '/usage'); + } } diff --git a/ui/src/app/shared/usage/workflows/usage.workflows.html b/ui/src/app/shared/usage/workflows/usage.workflows.html index eff836d66a..1874efe9cf 100644 --- a/ui/src/app/shared/usage/workflows/usage.workflows.html +++ b/ui/src/app/shared/usage/workflows/usage.workflows.html @@ -4,7 +4,12 @@

{{'usage_workflow_list' | translate}} diff --git a/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts b/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts index d8dea08873..a5511bef63 100644 --- a/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts +++ b/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts @@ -2,9 +2,11 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { finalize } from 'rxjs/internal/operators/finalize'; +import { first } from 'rxjs/operators'; import { AuditWorkflowTemplate } from '../../../../model/audit.model'; import { Group } from '../../../../model/group.model'; import { WorkflowTemplate } from '../../../../model/workflow-template.model'; +import { Workflow } from '../../../../model/workflow.model'; import { GroupService } from '../../../../service/services.module'; import { WorkflowTemplateService } from '../../../../service/workflow-template/workflow-template.service'; import { PathItem } from '../../../../shared/breadcrumb/breadcrumb.component'; @@ -24,13 +26,17 @@ export class WorkflowTemplateEditComponent implements OnInit { workflowTemplate: WorkflowTemplate; groups: Array; audits: Array; + workflowsLinked: Array; loading: boolean; loadingAudits: boolean; + loadingUsage: boolean; path: Array; tabs: Array; selectedTab: Tab; columns: Array; diffItems: Array; + groupName: string; + templateSlug: string; constructor( private _workflowTemplateService: WorkflowTemplateService, @@ -51,6 +57,10 @@ export class WorkflowTemplateEditComponent implements OnInit { translate: 'common_audit', icon: 'history', key: 'audits' + }, { + translate: 'common_usage', + icon: 'map signs', + key: 'usage' }]; this.columns = [ @@ -79,10 +89,10 @@ export class WorkflowTemplateEditComponent implements OnInit { ]; this._route.params.subscribe(params => { - const groupName = params['groupName']; - const templateSlug = params['templateSlug']; - this.getTemplate(groupName, templateSlug); - this.getAudits(groupName, templateSlug); + this.groupName = params['groupName']; + this.templateSlug = params['templateSlug']; + this.getTemplate(this.groupName, this.templateSlug); + this.getAudits(this.groupName, this.templateSlug); }); this.getGroups(); @@ -160,6 +170,9 @@ export class WorkflowTemplateEditComponent implements OnInit { } selectTab(tab: Tab): void { + if (tab.key === 'usage') { + this.getUsage(); + } this.selectedTab = tab; } @@ -192,4 +205,15 @@ export class WorkflowTemplateEditComponent implements OnInit { } this.saveWorkflowTemplate(); } + + getUsage() { + if (this.workflowsLinked) { + return; + } + this.loadingUsage = true; + this._workflowTemplateService.getWorkflowTemplateUsage(this.groupName, this.templateSlug) + .pipe(first()) + .pipe(finalize(() => this.loadingUsage = false)) + .subscribe((workflows) => this.workflowsLinked = workflows); + } } diff --git a/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.html b/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.html index acb4a25907..048c791e19 100644 --- a/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.html +++ b/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.html @@ -32,5 +32,13 @@

{{ "common_last_modified" | translate }}

+
+
+
+ + + +
+