Skip to content

Commit

Permalink
feat(ui,api): workflow template show diff in update modal (#3743)
Browse files Browse the repository at this point in the history
* feat(api): Create endpoint to get latest audit for given template version
* feat(ui): show template diff in update modal
* feat(ui,api): Store change message on template audits, display changes in update modal
* feat(ui): work in progress adding rollback on workflow template
* feat(ui): Allow to rollback a template from audit
  • Loading branch information
richardlt authored and yesnault committed Dec 20, 2018
1 parent db2a59e commit a16fac9
Show file tree
Hide file tree
Showing 39 changed files with 352 additions and 219 deletions.
Binary file modified docs/static/images/tutorials/npm-audit-parser/click_edit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions engine/api/event/publish_workflow_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ func PublishWorkflowTemplateAdd(wt sdk.WorkflowTemplate, u *sdk.User) {
}

// PublishWorkflowTemplateUpdate publishes an event for the update of the given workflow template.
func PublishWorkflowTemplateUpdate(new, old sdk.WorkflowTemplate, u *sdk.User) {
func PublishWorkflowTemplateUpdate(old, new sdk.WorkflowTemplate, changeMessage string, u *sdk.User) {
publishWorkflowTemplateEvent(sdk.EventWorkflowTemplateUpdate{
NewWorkflowTemplate: new,
OldWorkflowTemplate: old,
NewWorkflowTemplate: new,
ChangeMessage: changeMessage,
}, u)
}

Expand All @@ -43,9 +44,9 @@ func PublishWorkflowTemplateInstanceAdd(wti sdk.WorkflowTemplateInstance, u *sdk
}

// PublishWorkflowTemplateInstanceUpdate publishes an event for the update of the given workflow template instance.
func PublishWorkflowTemplateInstanceUpdate(new, old sdk.WorkflowTemplateInstance, u *sdk.User) {
func PublishWorkflowTemplateInstanceUpdate(old, new sdk.WorkflowTemplateInstance, u *sdk.User) {
publishWorkflowTemplateEvent(sdk.EventWorkflowTemplateInstanceUpdate{
NewWorkflowTemplateInstance: new,
OldWorkflowTemplateInstance: old,
NewWorkflowTemplateInstance: new,
}, u)
}
16 changes: 13 additions & 3 deletions engine/api/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"io/ioutil"
"net/http"
"strconv"

"github.com/gorilla/mux"
yaml "gopkg.in/yaml.v2"
Expand Down Expand Up @@ -235,7 +236,7 @@ func (api *API) putTemplateHandler() service.Handler {
return err
}

event.PublishWorkflowTemplateUpdate(*old, new, u)
event.PublishWorkflowTemplateUpdate(*old, new, data.ChangeMessage, u)

if err := group.AggregateOnWorkflowTemplate(api.mustDB(), &new); err != nil {
return err
Expand Down Expand Up @@ -564,8 +565,17 @@ func (api *API) getTemplateAuditsHandler() service.Handler {
}
t := getWorkflowTemplate(ctx)

as, err := workflowtemplate.GetAuditsByTemplateIDsAndEventTypes(api.mustDB(),
[]int64{t.ID}, []string{"WorkflowTemplateAdd", "WorkflowTemplateUpdate"})
since := r.FormValue("sinceVersion")
var version int64
if since != "" {
version, err = strconv.ParseInt(since, 10, 64)
if err != nil || version < 0 {
return sdk.NewError(sdk.ErrWrongRequest, err)
}
}

as, err := workflowtemplate.GetAuditsByTemplateIDsAndEventTypesAndVersionGTE(api.mustDB(),
[]int64{t.ID}, []string{"WorkflowTemplateAdd", "WorkflowTemplateUpdate"}, version)
if err != nil {
return err
}
Expand Down
9 changes: 5 additions & 4 deletions engine/api/workflowtemplate/aggregate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (

// AggregateAuditsOnWorkflowTemplate set audits for each workflow template.
func AggregateAuditsOnWorkflowTemplate(db gorp.SqlExecutor, wts ...*sdk.WorkflowTemplate) error {
as, err := GetAuditsByTemplateIDsAndEventTypes(db, sdk.WorkflowTemplatesToIDs(wts), []string{"WorkflowTemplateAdd", "WorkflowTemplateUpdate"})
as, err := GetAuditsByTemplateIDsAndEventTypesAndVersionGTE(db, sdk.WorkflowTemplatesToIDs(wts),
[]string{"WorkflowTemplateAdd", "WorkflowTemplateUpdate"}, 0)
if err != nil {
return err
}
Expand All @@ -21,11 +22,11 @@ func AggregateAuditsOnWorkflowTemplate(db gorp.SqlExecutor, wts ...*sdk.Workflow
m[a.WorkflowTemplateID] = append(m[a.WorkflowTemplateID], a)
}

// assume that audits are sorted by creation date with GetAudits
// assume that audits are sorted by creation date desc by GetAudits
for _, wt := range wts {
if as, ok := m[wt.ID]; ok {
wt.FirstAudit = &as[0]
wt.LastAudit = &as[len(as)-1]
wt.FirstAudit = &as[len(as)-1]
wt.LastAudit = &as[0]
}
}

Expand Down
20 changes: 10 additions & 10 deletions engine/api/workflowtemplate/aggregate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,8 @@ func TestAggregateAuditsOnWorkflowTemplate(t *testing.T) {
awts := i.(*[]sdk.AuditWorkflowTemplate)
*awts = append(*awts, sdk.AuditWorkflowTemplate{
AuditCommon: sdk.AuditCommon{
ID: 1,
EventType: "EventWorkflowTemplateAdd",
},
WorkflowTemplateID: 1,
}, sdk.AuditWorkflowTemplate{
AuditCommon: sdk.AuditCommon{
ID: 2,
EventType: "EventWorkflowTemplateAdd",
ID: 4,
EventType: "EventWorkflowTemplateUpdate",
},
WorkflowTemplateID: 2,
}, sdk.AuditWorkflowTemplate{
Expand All @@ -34,10 +28,16 @@ func TestAggregateAuditsOnWorkflowTemplate(t *testing.T) {
WorkflowTemplateID: 1,
}, sdk.AuditWorkflowTemplate{
AuditCommon: sdk.AuditCommon{
ID: 4,
EventType: "EventWorkflowTemplateUpdate",
ID: 2,
EventType: "EventWorkflowTemplateAdd",
},
WorkflowTemplateID: 2,
}, sdk.AuditWorkflowTemplate{
AuditCommon: sdk.AuditCommon{
ID: 1,
EventType: "EventWorkflowTemplateAdd",
},
WorkflowTemplateID: 1,
})
}

Expand Down
9 changes: 5 additions & 4 deletions engine/api/workflowtemplate/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ func (a updateWorkflowTemplateAudit) Compute(db gorp.SqlExecutor, e sdk.Event) e
return sdk.WrapError(err, "Unable to decode payload")
}

before, err := json.Marshal(wtEvent.NewWorkflowTemplate)
before, err := json.Marshal(wtEvent.OldWorkflowTemplate)
if err != nil {
return sdk.WrapError(err, "Unable to marshal workflow template")
}

after, err := json.Marshal(wtEvent.OldWorkflowTemplate)
after, err := json.Marshal(wtEvent.NewWorkflowTemplate)
if err != nil {
return sdk.WrapError(err, "Unable to marshal workflow template")
}
Expand All @@ -103,6 +103,7 @@ func (a updateWorkflowTemplateAudit) Compute(db gorp.SqlExecutor, e sdk.Event) e
DataType: "json",
},
WorkflowTemplateID: wtEvent.NewWorkflowTemplate.ID,
ChangeMessage: wtEvent.ChangeMessage,
})
}

Expand Down Expand Up @@ -139,12 +140,12 @@ func (a updateWorkflowTemplateInstanceAudit) Compute(db gorp.SqlExecutor, e sdk.
return sdk.WrapError(err, "Unable to decode payload")
}

before, err := json.Marshal(wtEvent.NewWorkflowTemplateInstance)
before, err := json.Marshal(wtEvent.OldWorkflowTemplateInstance)
if err != nil {
return sdk.WrapError(err, "Unable to marshal workflow template instance")
}

after, err := json.Marshal(wtEvent.OldWorkflowTemplateInstance)
after, err := json.Marshal(wtEvent.NewWorkflowTemplateInstance)
if err != nil {
return sdk.WrapError(err, "Unable to marshal workflow template instance")
}
Expand Down
9 changes: 5 additions & 4 deletions engine/api/workflowtemplate/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,17 @@ func InsertAudit(db gorp.SqlExecutor, awt *sdk.AuditWorkflowTemplate) error {
return sdk.WrapError(gorpmapping.Insert(db, awt), "Unable to insert audit for workflow template %d", awt.WorkflowTemplateID)
}

// GetAuditsByTemplateIDsAndEventTypes returns all workflow template audits by template ids and event types.
func GetAuditsByTemplateIDsAndEventTypes(db gorp.SqlExecutor, templateIDs []int64, eventTypes []string) ([]sdk.AuditWorkflowTemplate, error) {
// GetAuditsByTemplateIDsAndEventTypesAndVersionGTE returns all workflow template audits by template ids, event types and version greater or equal.
func GetAuditsByTemplateIDsAndEventTypesAndVersionGTE(db gorp.SqlExecutor, templateIDs []int64, eventTypes []string, version int64) ([]sdk.AuditWorkflowTemplate, error) {
awts := []sdk.AuditWorkflowTemplate{}

if _, err := db.Select(&awts,
`SELECT * FROM workflow_template_audit
WHERE workflow_template_id = ANY(string_to_array($1, ',')::int[])
AND event_type = ANY(string_to_array($2, ',')::text[])
ORDER BY created ASC`,
gorpmapping.IDsToQueryString(templateIDs), strings.Join(eventTypes, ","),
AND (data_after->>'version')::int >= $3
ORDER BY created DESC`,
gorpmapping.IDsToQueryString(templateIDs), strings.Join(eventTypes, ","), version,
); err != nil {
return nil, sdk.WrapError(err, "Cannot get workflow template audits")
}
Expand Down
2 changes: 1 addition & 1 deletion engine/api/workflowtemplate/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func Push(db gorp.SqlExecutor, u *sdk.User, tr *tar.Reader) ([]sdk.Message, *sdk
return nil, nil, err
}

event.PublishWorkflowTemplateUpdate(*old, new, u)
event.PublishWorkflowTemplateUpdate(*old, new, "", u)

return []sdk.Message{sdk.NewMessage(sdk.MsgWorkflowTemplateImportedUpdated, grp.Name, new.Slug)}, &new, nil
}
11 changes: 11 additions & 0 deletions engine/sql/145_template_audit_json.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- +migrate Up

ALTER TABLE workflow_template_audit ALTER COLUMN data_before TYPE JSONB USING data_after::JSONB;
ALTER TABLE workflow_template_audit ALTER COLUMN data_after TYPE JSONB USING data_after::JSONB;
ALTER TABLE workflow_template_audit ADD COLUMN change_message TEXT NOT NULL DEFAULT '';

-- +migrate Down

ALTER TABLE workflow_template_audit ALTER COLUMN data_before TYPE TEXT USING data_after::TEXT;
ALTER TABLE workflow_template_audit ALTER COLUMN data_after TYPE TEXT USING data_after::TEXT;
ALTER TABLE workflow_template_audit DROP COLUMN change_message;
3 changes: 2 additions & 1 deletion sdk/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ type Audit interface {
// AuditWorkflowTemplate represents an audit data on a workflow template.
type AuditWorkflowTemplate struct {
AuditCommon
WorkflowTemplateID int64 `json:"workflow_template_id" db:"workflow_template_id"`
WorkflowTemplateID int64 `json:"workflow_template_id" db:"workflow_template_id"`
ChangeMessage string `json:"change_message,omitempty" db:"change_message"`
}

// AuditWorkflowTemplateInstance represents an audit data on a workflow template instance.
Expand Down
5 changes: 3 additions & 2 deletions sdk/event_workflow_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ type EventWorkflowTemplateAdd struct {

// EventWorkflowTemplateUpdate represents the event when updating a workflow template.
type EventWorkflowTemplateUpdate struct {
NewWorkflowTemplate WorkflowTemplate `json:"new_workflow_template"`
OldWorkflowTemplate WorkflowTemplate `json:"old_workflow_template"`
NewWorkflowTemplate WorkflowTemplate `json:"new_workflow_template"`
ChangeMessage string `json:"change_message"`
}

// EventWorkflowTemplateInstanceAdd represents the event when adding a workflow template instance.
Expand All @@ -18,6 +19,6 @@ type EventWorkflowTemplateInstanceAdd struct {

// EventWorkflowTemplateInstanceUpdate represents the event when updating a workflow template instance.
type EventWorkflowTemplateInstanceUpdate struct {
NewWorkflowTemplateInstance WorkflowTemplateInstance `json:"new_workflow_template_instance"`
OldWorkflowTemplateInstance WorkflowTemplateInstance `json:"old_workflow_template_instance"`
NewWorkflowTemplateInstance WorkflowTemplateInstance `json:"new_workflow_template_instance"`
}
9 changes: 5 additions & 4 deletions sdk/workflow_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ type WorkflowTemplate struct {
Environments EnvironmentTemplates `json:"environments" db:"environments"`
Version int64 `json:"version" db:"version"`
// aggregates
Group *Group `json:"group,omitempty" db:"-"`
FirstAudit *AuditWorkflowTemplate `json:"first_audit,omitempty" db:"-"`
LastAudit *AuditWorkflowTemplate `json:"last_audit,omitempty" db:"-"`
Editable bool `json:"editable,omitempty" db:"-"`
Group *Group `json:"group,omitempty" db:"-"`
FirstAudit *AuditWorkflowTemplate `json:"first_audit,omitempty" db:"-"`
LastAudit *AuditWorkflowTemplate `json:"last_audit,omitempty" db:"-"`
Editable bool `json:"editable,omitempty" db:"-"`
ChangeMessage string `json:"change_message,omitempty" db:"-"`
}

// IsValid returns workflow template validity.
Expand Down
1 change: 1 addition & 0 deletions ui/src/app/model/audit.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export class AuditWorkflow extends Audit {

export class AuditWorkflowTemplate extends Audit {
workflow_template_id: string;
change_message: string;
}
1 change: 1 addition & 0 deletions ui/src/app/model/workflow-template.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class WorkflowTemplate {
first_audit: AuditWorkflowTemplate;
last_audit: AuditWorkflowTemplate;
editable: boolean;
change_message: string;
}

export class WorkflowTemplateParameter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export class WorkflowTemplateService {
return this._http.get<WorkflowTemplateInstance>('/project/' + projectKey + '/workflow/' + workflowName + '/templateInstance');
}

getAudits(groupName: string, templateSlug: string): Observable<Array<AuditWorkflowTemplate>> {
return this._http.get<Array<AuditWorkflowTemplate>>('/template/' + groupName + '/' + templateSlug + '/audit');
getAudits(groupName: string, templateSlug: string, version?: number): Observable<Array<AuditWorkflowTemplate>> {
let url = '/template/' + groupName + '/' + templateSlug + '/audit' + (version ? '?sinceVersion=' + version : '');
return this._http.get<Array<AuditWorkflowTemplate>>(url);
}
}
2 changes: 1 addition & 1 deletion ui/src/app/shared/audit/list/audit.list.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ <h3>{{ 'audit_title' | translate }}</h3>
</td>
<td class="middle-aligned border">
<ng-container *ngIf="rollback">
<app-confirm-button (event)="rollback.emit(a.id)" title="Rollback">
<app-confirm-button (event)="rollback.emit(a.id)" title="common_rollback">
</app-confirm-button>
</ng-container>
</td>
Expand Down
7 changes: 4 additions & 3 deletions ui/src/app/shared/button/confirm/confirm.button.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<button class="ui {{color}} button {{class}}" [class.loading]="loading" [class.disabled]="loading || disabled" (click)="showConfirmation = true" *ngIf="!showConfirmation" [disabled]="disabled">
<button class="ui {{color}} button {{class}}" [class.loading]="loading" [class.disabled]="loading || disabled" (click)="showConfirmation = true"
*ngIf="!showConfirmation" [disabled]="disabled">
<i *ngIf="icon" class="{{icon}} icon"></i>
<span>{{title}}</span>
<span>{{title | translate}}</span>
</button>
<div class="ui buttons {{class}}" *ngIf="showConfirmation">
<button class="ui grey button" [class.disabled]="loading" (click)="showConfirmation = false"><i class="ban icon"></i></button>
<div class="or"></div>
<button class="ui green button active" [class.disabled]="loading" (click)="confirmEvent()"><i class="check icon"></i></button>
</div>
</div>
1 change: 1 addition & 0 deletions ui/src/app/shared/button/confirm/confirm.button.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import '../../../../common';
5 changes: 2 additions & 3 deletions ui/src/app/shared/button/confirm/confirm.button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@ import {Component, EventEmitter, Input, Output} from '@angular/core';
styleUrls: ['./confirm.button.scss']
})
export class ConfirmButtonComponent {

@Input() loading = false;
@Input() icon = '';
@Input() disabled = false;
@Input() color = 'primary';
@Input() class: string;
@Output() event = new EventEmitter();
@Input() title: string;
@Output() event = new EventEmitter<boolean>();

showConfirmation = false;

constructor() {}

confirmEvent() {
this.event.emit(true);
this.event.emit();
this.reset();
}

Expand Down
Loading

0 comments on commit a16fac9

Please sign in to comment.