diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index e587d7959..f9bc114f8 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -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", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index d6e6ef43b..bf886ae87 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -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", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index a5818daff..f1efdc950 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -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", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index e5933d6d5..513c26339 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -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", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index 9edbe81b6..c5d87c330 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -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", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 57b88fae4..0424899d4 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -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": "新建目标", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 9a703b732..ae1812aa8 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -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": "新建目標", diff --git a/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.html b/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.html index f985e79c1..7dc2408df 100644 --- a/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.html +++ b/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.html @@ -2,9 +2,44 @@ - - + + {{ 'BUTTON.ACTIONS' | translate}} + + + + + + + + + + {{'REPLICATION.NAME' | translate}} {{'REPLICATION.STATUS' | translate}} diff --git a/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.spec.ts b/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.spec.ts index 668faec7d..4d3fadf40 100644 --- a/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.spec.ts +++ b/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.spec.ts @@ -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; + let fixture: ComponentFixture; - 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(); }); - })); - }); diff --git a/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.ts b/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.ts index 81b109b14..a2033c690 100644 --- a/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.ts +++ b/src/portal/src/lib/components/list-replication-rule/list-replication-rule.component.ts @@ -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); + } } diff --git a/src/portal/src/lib/entities/shared.const.ts b/src/portal/src/lib/entities/shared.const.ts index 663faccab..a50f8beb1 100644 --- a/src/portal/src/lib/entities/shared.const.ts +++ b/src/portal/src/lib/entities/shared.const.ts @@ -37,7 +37,8 @@ export const enum ConfirmationTargets { CONFIG_TAB, HELM_CHART, HELM_CHART_VERSION, - STOP_EXECUTIONS + STOP_EXECUTIONS, + REPLICATION } export const enum ActionType { diff --git a/tests/resources/Harbor-Pages/Replication.robot b/tests/resources/Harbor-Pages/Replication.robot index 395859430..175a36c85 100644 --- a/tests/resources/Harbor-Pages/Replication.robot +++ b/tests/resources/Harbor-Pages/Replication.robot @@ -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 diff --git a/tests/resources/Harbor-Pages/Replication_Elements.robot b/tests/resources/Harbor-Pages/Replication_Elements.robot index 97f9d36bb..faa38ceea 100644 --- a/tests/resources/Harbor-Pages/Replication_Elements.robot +++ b/tests/resources/Harbor-Pages/Replication_Elements.robot @@ -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]