Merge pull request #13085 from AllForNothing/replication

Add disable/enable function to replication rules
This commit is contained in:
Will Sun 2020-09-24 17:35:51 +08:00 committed by GitHub
commit 18f41bad88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 299 additions and 95 deletions

View File

@ -616,7 +616,15 @@
"NAMESPACE_TOOLTIP": "Namespace name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
"TAG": "Tag",
"LABEL": "Label",
"RESOURCE": "Resource"
"RESOURCE": "Resource",
"ENABLE_TITLE": "Enable rule",
"ENABLE_SUMMARY": "Do you want to enable rule {{param}}?",
"DISABLE_TITLE": "Disable rule",
"DISABLE_SUMMARY": "Do you want to disable rule {{param}}?",
"ENABLE_SUCCESS": "Enabled rule successfully",
"ENABLE_FAILED": "Enabling rule failed",
"DISABLE_SUCCESS": "Disabled rule successfully",
"DISABLE_FAILED": "Disabling rule failed"
},
"DESTINATION": {
"NEW_ENDPOINT": "New Endpoint",

View File

@ -617,7 +617,15 @@
"NAMESPACE_TOOLTIP": "Namespace name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
"TAG": "Tag",
"LABEL": "Label",
"RESOURCE": "Resource"
"RESOURCE": "Resource",
"ENABLE_TITLE": "Enable rule",
"ENABLE_SUMMARY": "Do you want to enable rule {{param}}?",
"DISABLE_TITLE": "Disable rule",
"DISABLE_SUMMARY": "Do you want to disable rule {{param}}?",
"ENABLE_SUCCESS": "Enabled rule successfully",
"ENABLE_FAILED": "Enabling rule failed",
"DISABLE_SUCCESS": "Disabled rule successfully",
"DISABLE_FAILED": "Disabling rule failed"
},
"DESTINATION": {
"NEW_ENDPOINT": "Nuevo Endpoint",

View File

@ -606,7 +606,15 @@
"NAMESPACE_TOOLTIP": "Namespace name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
"TAG": "Tag",
"LABEL": "Label",
"RESOURCE": "Resource"
"RESOURCE": "Resource",
"ENABLE_TITLE": "Enable rule",
"ENABLE_SUMMARY": "Do you want to enable rule {{param}}?",
"DISABLE_TITLE": "Disable rule",
"DISABLE_SUMMARY": "Do you want to disable rule {{param}}?",
"ENABLE_SUCCESS": "Enabled rule successfully",
"ENABLE_FAILED": "Enabling rule failed",
"DISABLE_SUCCESS": "Disabled rule successfully",
"DISABLE_FAILED": "Disabling rule failed"
},
"DESTINATION": {
"NEW_ENDPOINT": "Nouveau Point Final",

View File

@ -616,7 +616,15 @@
"NAMESPACE_TOOLTIP": "Namespace name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
"TAG": "Tag",
"LABEL": "Label",
"RESOURCE": "Resource"
"RESOURCE": "Resource",
"ENABLE_TITLE": "Enable rule",
"ENABLE_SUMMARY": "Do you want to enable rule {{param}}?",
"DISABLE_TITLE": "Disable rule",
"DISABLE_SUMMARY": "Do you want to disable rule {{param}}?",
"ENABLE_SUCCESS": "Enabled rule successfully",
"ENABLE_FAILED": "Enabling rule failed",
"DISABLE_SUCCESS": "Disabled rule successfully",
"DISABLE_FAILED": "Disabling rule failed"
},
"DESTINATION": {
"NEW_ENDPOINT": "Novo Endpoint",

View File

@ -616,7 +616,15 @@
"NAMESPACE_TOOLTIP": "İsim alanı ismi en az 2 karakter uzunluğunda, küçük harfli karakterler, sayılar ve ._- ile başlamalı ve karakter veya sayılarla başlamalıdır. Ad alanı adı en az 2 karakter uzunluğunda, küçük harf, rakam ve ._- ile başlamalı ve karakter veya rakamlarla başlamalıdır.",
"TAG": "Etiketlemek",
"LABEL": "Etiket",
"RESOURCE": "Kaynak"
"RESOURCE": "Kaynak",
"ENABLE_TITLE": "Enable rule",
"ENABLE_SUMMARY": "Do you want to enable rule {{param}}?",
"DISABLE_TITLE": "Disable rule",
"DISABLE_SUMMARY": "Do you want to disable rule {{param}}?",
"ENABLE_SUCCESS": "Enabled rule successfully",
"ENABLE_FAILED": "Enabling rule failed",
"DISABLE_SUCCESS": "Disabled rule successfully",
"DISABLE_FAILED": "Disabling rule failed"
},
"DESTINATION": {
"NEW_ENDPOINT": "Yeni Uç Nokta",

View File

@ -617,7 +617,15 @@
"NAMESPACE_TOOLTIP": "Namespace名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。",
"TAG": "Tag",
"LABEL": "标签",
"RESOURCE": "资源"
"RESOURCE": "资源",
"ENABLE_TITLE": "启用规则",
"ENABLE_SUMMARY": "确定启用规则 {{param}}?",
"DISABLE_TITLE": "禁用规则",
"DISABLE_SUMMARY": "确认禁用规则 {{param}}?",
"ENABLE_SUCCESS": "启用规则成功",
"ENABLE_FAILED": "启用规则失败",
"DISABLE_SUCCESS": "禁用规则成功",
"DISABLE_FAILED": "禁用规则失败"
},
"DESTINATION": {
"NEW_ENDPOINT": "新建目标",

View File

@ -613,7 +613,15 @@
"NAMESPACE_TOOLTIP": "Namespace名稱由小寫字符、數字和._-組成且至少2個字符並以字符或者數字開頭。",
"TAG":"標籤",
"LABEL": "標籤",
"RESOURCE": "資源"
"RESOURCE": "資源",
"ENABLE_TITLE": "Enable rule",
"ENABLE_SUMMARY": "Do you want to enable rule {{param}}?",
"DISABLE_TITLE": "Disable rule",
"DISABLE_SUMMARY": "Do you want to disable rule {{param}}?",
"ENABLE_SUCCESS": "Enabled rule successfully",
"ENABLE_FAILED": "Enabling rule failed",
"DISABLE_SUCCESS": "Disabled rule successfully",
"DISABLE_FAILED": "Disabling rule failed"
},
"DESTINATION":{
"NEW_ENDPOINT": "新建目標",

View File

@ -2,9 +2,44 @@
<clr-datagrid [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedRow" (clrDgSingleSelectedChange)="selectRule($event)" [clrDgRowSelection]="true">
<clr-dg-action-bar>
<button type="button" id="new_replication_rule_id" class="btn btn-secondary" *ngIf="hasCreateReplicationPermission" (click)="openModal()"><clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'REPLICATION.NEW_REPLICATION_RULE' | translate}}</button>
<button type="button" id="edit_replication_rule_id" class="btn btn-secondary" *ngIf="hasUpdateReplicationPermission" [disabled]="!selectedRow" (click)="editRule(selectedRow)"><clr-icon shape="pencil" size="16"></clr-icon>&nbsp;{{'REPLICATION.EDIT_POLICY' | translate}}</button>
<button type="button" id="delete_replication_rule_id" class="btn btn-secondary" *ngIf="hasDeleteReplicationPermission" [disabled]="!selectedRow" (click)="deleteRule(selectedRow)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPLICATION.DELETE_POLICY' | translate}}</button>
<button type="button" id="replication_exe_id" class="btn btn-secondary" *ngIf="hasExecuteReplicationPermission" [disabled]="!selectedRow" (click)="replicateRule(selectedRow)"><clr-icon shape="export" size="16"></clr-icon>&nbsp;{{'REPLICATION.REPLICATE' | translate}}</button>
<clr-dropdown
[clrCloseMenuOnItemClick]="false"
class="btn btn-link"
clrDropdownTrigger>
<span id="rule-action">{{ 'BUTTON.ACTIONS' | translate}}<clr-icon shape="caret down"></clr-icon></span>
<clr-dropdown-menu *clrIfOpen>
<clr-dropdown>
<button type="button" class="btn btn-secondary" (click)="editRule(selectedRow)"
[disabled]="!selectedRow">
<clr-icon shape="edit" size="16"></clr-icon>&nbsp;
<span id="edit_replication_rule_id">{{'REPLICATION.EDIT_POLICY' | translate}}</span>
</button>
<button type="button" class="btn btn-secondary" (click)="operateRule('enable', selectedRow)"
[disabled]="!(selectedRow && !selectedRow.enabled)">
<clr-icon shape="connect" size="16"></clr-icon>&nbsp;
<span id="rule-enable">{{'DISTRIBUTION.ENABLE_ACTION' | translate}}</span>
</button>
<button
type="button"
class="btn btn-secondary"
(click)="operateRule('disable', selectedRow)"
[disabled]="!(selectedRow && selectedRow.enabled)">
<clr-icon shape="disconnect" size="16"></clr-icon>&nbsp;
<span id="rule-disable">{{'DISTRIBUTION.DISABLE_ACTION' | translate}}</span>
</button>
<div class="dropdown-divider"></div>
<button
type="button"
class="btn btn-secondary"
(click)="deleteRule(selectedRow)"
[disabled]="!selectedRow">
<clr-icon shape="window-close" size="16"></clr-icon>&nbsp;
<span id="delete_replication_rule_id">{{'REPLICATION.DELETE_POLICY' | translate}}</span>
</button>
</clr-dropdown>
</clr-dropdown-menu>
</clr-dropdown>
</clr-dg-action-bar>
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'status'" class="status-width">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>

View File

@ -12,92 +12,144 @@ import { ReplicationRule } from '../../services/interface';
import { ErrorHandler } from '../../utils/error-handler/error-handler';
import { SERVICE_CONFIG, IServiceConfig } from '../../entities/service.config';
import { ReplicationService, ReplicationDefaultService } from '../../services/replication.service';
import { ReplicationService } from '../../services/replication.service';
import { OperationService } from "../operation/operation.service";
import { of } from 'rxjs';
import { CURRENT_BASE_HREF } from "../../utils/utils";
import { delay } from "rxjs/operators";
describe('ListReplicationRuleComponent (inline template)', () => {
let mockRules: ReplicationRule[] = [
{
"id": 1,
"name": "sync_01",
"description": "",
"filters": null,
"trigger": {"type": "Manual", "trigger_settings": null},
"error_job_count": 2,
"deletion": false,
"src_namespaces": ["name1", "name2"],
"src_registry": {id: 3},
"enabled": true,
"override": true
},
{
"id": 2,
"name": "sync_02",
"description": "",
"filters": null,
"trigger": {"type": "Manual", "trigger_settings": null},
"error_job_count": 2,
"deletion": false,
"src_namespaces": ["name1", "name2"],
"dest_registry": {id: 3},
"enabled": true,
"override": true
},
];
let mockRules: ReplicationRule[] = [
{
"id": 1,
"name": "sync_01",
"description": "",
"filters": null,
"trigger": {"type": "Manual", "trigger_settings": null},
"error_job_count": 2,
"deletion": false,
"src_namespaces": ["name1", "name2"],
"src_registry": {id: 3},
"enabled": true,
"override": true
},
{
"id": 2,
"name": "sync_02",
"description": "",
"filters": null,
"trigger": {"type": "Manual", "trigger_settings": null},
"error_job_count": 2,
"deletion": false,
"src_namespaces": ["name1", "name2"],
"dest_registry": {id: 3},
"enabled": true,
"override": true
},
];
let fixture: ComponentFixture<ListReplicationRuleComponent>;
let fixture: ComponentFixture<ListReplicationRuleComponent>;
let comp: ListReplicationRuleComponent;
let comp: ListReplicationRuleComponent;
let replicationService: ReplicationService;
let replicationService: ReplicationService;
let spyRules: jasmine.Spy;
let spyRules: jasmine.Spy;
let config: IServiceConfig = {
replicationRuleEndpoint: CURRENT_BASE_HREF + '/policies/replication/testing'
};
let config: IServiceConfig = {
replicationRuleEndpoint: CURRENT_BASE_HREF + '/policies/replication/testing'
};
const fakedReplicationService = {
getReplicationRules() {
return of(mockRules).pipe(delay(0));
},
updateReplicationRule() {
return of(true).pipe(delay(0));
}
};
const fakedOperationService = {
publishInfo() {
return undefined;
}
};
const fakedErrorHandler = {
info() {
return undefined;
}
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
NoopAnimationsModule
],
declarations: [
ListReplicationRuleComponent,
ConfirmationDialogComponent
],
providers: [
ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: ReplicationService, useClass: ReplicationDefaultService },
{ provide: OperationService }
]
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
NoopAnimationsModule
],
declarations: [
ListReplicationRuleComponent,
ConfirmationDialogComponent
],
providers: [
{provide: ErrorHandler, useValue: fakedErrorHandler},
{provide: SERVICE_CONFIG, useValue: config},
{provide: ReplicationService, useValue: fakedReplicationService},
{provide: OperationService, useValue: fakedOperationService}
]
});
}));
beforeEach(() => {
fixture = TestBed.createComponent(ListReplicationRuleComponent);
comp = fixture.componentInstance;
replicationService = fixture.debugElement.injector.get(ReplicationService);
spyRules = spyOn(replicationService, 'getReplicationRules').and.returnValues(of(mockRules));
fixture.detectChanges();
});
}));
beforeEach(() => {
fixture = TestBed.createComponent(ListReplicationRuleComponent);
comp = fixture.componentInstance;
replicationService = fixture.debugElement.injector.get(ReplicationService);
spyRules = spyOn(replicationService, 'getReplicationRules').and.returnValues(of(mockRules));
fixture.detectChanges();
});
it('Should load and render data', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let de: DebugElement = fixture.debugElement.query(By.css('datagrid-cell'));
expect(de).toBeTruthy();
fixture.detectChanges();
let el: HTMLElement = de.nativeElement;
expect(el.textContent.trim()).toEqual('sync_01');
it('Should load and render data', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let de: DebugElement = fixture.debugElement.query(By.css('datagrid-cell'));
expect(de).toBeTruthy();
fixture.detectChanges();
let el: HTMLElement = de.nativeElement;
expect(el.textContent.trim()).toEqual('sync_01');
});
}));
it('should disable rule', () => {
fixture.detectChanges();
comp.selectedRow = comp.rules[0];
comp.selectedRow.enabled = true;
fixture.detectChanges();
const action: HTMLElement = fixture.nativeElement.querySelector("#rule-action");
action.click();
fixture.detectChanges();
const disable: HTMLElement = fixture.nativeElement.querySelector("#rule-disable");
disable.click();
fixture.detectChanges();
const button: HTMLElement = fixture.nativeElement.querySelector("#dialog-action-disable");
button.click();
fixture.detectChanges();
const body: HTMLElement = fixture.nativeElement.querySelector(".modal-body");
expect(body).toBeFalsy();
});
it('should enable rule', () => {
fixture.detectChanges();
comp.selectedRow = comp.rules[0];
comp.selectedRow.enabled = false;
fixture.detectChanges();
const action: HTMLElement = fixture.nativeElement.querySelector("#rule-action");
action.click();
fixture.detectChanges();
const enable: HTMLElement = fixture.nativeElement.querySelector("#rule-enable");
enable.click();
fixture.detectChanges();
const button: HTMLElement = fixture.nativeElement.querySelector("#dialog-action-enable");
button.click();
fixture.detectChanges();
const body: HTMLElement = fixture.nativeElement.querySelector(".modal-body");
expect(body).toBeFalsy();
});
}));
});

View File

@ -24,29 +24,28 @@ import {
SimpleChange,
SimpleChanges
} from "@angular/core";
import { Comparator } from "../../services/interface";
import { Comparator } from "../../services";
import { TranslateService } from "@ngx-translate/core";
import { map, catchError } from "rxjs/operators";
import { Observable, forkJoin, of, throwError as observableThrowError } from "rxjs";
import { ReplicationService } from "../../services/replication.service";
import { Observable, forkJoin, throwError as observableThrowError } from "rxjs";
import { ReplicationService } from "../../services";
import {
ReplicationJob,
ReplicationJobItem,
ReplicationRule
} from "../../services/interface";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message";
} from "../../services";
import { ConfirmationDialogComponent } from "../confirmation-dialog";
import { ConfirmationMessage } from "../confirmation-dialog";
import { ConfirmationAcknowledgement } from "../confirmation-dialog";
import {
ConfirmationState,
ConfirmationTargets,
ConfirmationButtons
} from "../../entities/shared.const";
import { ErrorHandler } from "../../utils/error-handler/error-handler";
import { CustomComparator } from "../../utils/utils";
import { ErrorHandler } from "../../utils/error-handler";
import { clone, CustomComparator } from "../../utils/utils";
import { operateChanges, OperateInfo, OperationState } from "../operation/operate";
import { OperationService } from "../operation/operation.service";
import { errorHandler as errorHandFn } from "../../utils/shared/shared.utils";
import { errorHandler as errorHandFn} from "../../utils/shared/shared.utils";
const jobstatus = "InProgress";
@ -158,6 +157,35 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
) {
this.deleteOpe(message.data);
}
if ( message &&
message.source === ConfirmationTargets.REPLICATION &&
message.state === ConfirmationState.CONFIRMED) {
const rule: ReplicationRule = clone(message.data);
rule.enabled = !message.data.enabled;
const opeMessage = new OperateInfo();
opeMessage.name = rule.enabled ? 'REPLICATION.ENABLE_TITLE' : 'REPLICATION.DISABLE_TITLE';
opeMessage.data.id = rule.id;
opeMessage.state = OperationState.progressing;
opeMessage.data.name = rule.name;
this.operationService.publishInfo(opeMessage);
this.replicationService.updateReplicationRule(rule.id, rule).subscribe(
res => {
this.translateService.get(rule.enabled ? 'REPLICATION.ENABLE_SUCCESS' : 'REPLICATION.DISABLE_SUCCESS')
.subscribe(msg => {
operateChanges(opeMessage, OperationState.success);
this.errorHandler.info(msg);
this.retrieveRules('');
});
}, error => {
const errMessage = errorHandFn(error);
this.translateService.get(rule.enabled ? 'REPLICATION.ENABLE_FAILED' : 'REPLICATION.DISABLE_FAILED')
.subscribe(msg => {
operateChanges(opeMessage, OperationState.failure, msg);
this.errorHandler.error(errMessage);
});
}
);
}
}
selectRule(rule: ReplicationRule): void {
@ -232,4 +260,34 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
return observableThrowError(error);
}));
}
operateRule(operation: string, rule: ReplicationRule): void {
let title: string;
let summary: string;
let buttons: ConfirmationButtons;
switch (operation) {
case 'enable':
title = 'REPLICATION.ENABLE_TITLE';
summary = 'REPLICATION.ENABLE_SUMMARY';
buttons = ConfirmationButtons.ENABLE_CANCEL;
break;
case 'disable':
title = 'REPLICATION.DISABLE_TITLE';
summary = 'REPLICATION.DISABLE_SUMMARY';
buttons = ConfirmationButtons.DISABLE_CANCEL;
break;
default:
return;
}
// Confirm
const msg: ConfirmationMessage = new ConfirmationMessage(
title,
summary,
rule.name,
rule,
ConfirmationTargets.REPLICATION,
buttons
);
this.deletionConfirmDialog.open(msg);
}
}

View File

@ -37,7 +37,8 @@ export const enum ConfirmationTargets {
CONFIG_TAB,
HELM_CHART,
HELM_CHART_VERSION,
STOP_EXECUTIONS
STOP_EXECUTIONS,
REPLICATION
}
export const enum ActionType {

View File

@ -237,6 +237,7 @@ Select Rule And Replicate
Select Rule And Click Edit Button
[Arguments] ${rule_name}
Retry Element Click //clr-dg-row[contains(.,'${rule_name}')]//clr-radio-wrapper/label
Retry Element Click ${replication_action}
Retry Element Click ${edit_replication_rule_id}
Delete Replication Rule

View File

@ -69,6 +69,7 @@ ${trigger_mode_selector} //*[@id='ruleTrigger']
${dest_namespace_xpath} //*[@id='dest_namespace']
${new_replication_rule_id} //*[@id='new_replication_rule_id']
${edit_replication_rule_id} //*[@id='edit_replication_rule_id']
${replication_action} //*[@id='rule-action']
${delete_replication_rule_id} //*[@id='delete_replication_rule_id']
${replication_exec_id} //*[@id='replication_exe_id']
${replication_task_line_1} //clr-datagrid//clr-dg-row/div/div[2]//clr-checkbox-wrapper/label[1]