Add dynamic column support (#18331)

1. Fixes #17815
2. Add dynamic column support for replication rule list  and artifact list

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Shijun Sun 2023-03-14 14:41:16 +08:00 committed by GitHub
parent a1d397842d
commit 1acba0c3bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 311 additions and 68 deletions

View File

@ -90,31 +90,69 @@
</clr-dropdown-menu>
</clr-dropdown>
</clr-dg-action-bar>
<clr-dg-column>{{ 'REPLICATION.NAME' | translate }}</clr-dg-column>
<clr-dg-column class="status-width">{{
'REPLICATION.STATUS' | translate
}}</clr-dg-column>
<clr-dg-column class="col-width">{{
'REPLICATION.SRC_REGISTRY' | translate
}}</clr-dg-column>
<clr-dg-column class="col-width">{{
'REPLICATION.REPLICATION_MODE' | translate
}}</clr-dg-column>
<clr-dg-column class="min-width">{{
'REPLICATION.DESTINATION_NAMESPACE' | translate
}}</clr-dg-column>
<clr-dg-column>{{
'REPLICATION.DES_REPO_FLATTENING' | translate
}}</clr-dg-column>
<clr-dg-column>{{
'REPLICATION.REPLICATION_TRIGGER' | translate
}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'speed'">{{
'REPLICATION.BANDWIDTH' | translate
}}</clr-dg-column>
<clr-dg-column [clrDgField]="'description'">{{
'REPLICATION.DESCRIPTION' | translate
}}</clr-dg-column>
<clr-dg-column>
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[0] }"
(clrDgHiddenChange)="columnHiddenChange(0)">
{{ 'REPLICATION.NAME' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="status-width">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[1] }"
(clrDgHiddenChange)="columnHiddenChange(1)">
{{ 'REPLICATION.STATUS' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="col-width">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[2] }"
(clrDgHiddenChange)="columnHiddenChange(2)">
{{ 'REPLICATION.SRC_REGISTRY' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="col-width">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[3] }"
(clrDgHiddenChange)="columnHiddenChange(3)">
{{ 'REPLICATION.REPLICATION_MODE' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="min-width">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[4] }"
(clrDgHiddenChange)="columnHiddenChange(4)">
{{ 'REPLICATION.DESTINATION_NAMESPACE' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column>
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[5] }"
(clrDgHiddenChange)="columnHiddenChange(5)">
{{ 'REPLICATION.DES_REPO_FLATTENING' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column>
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[6] }"
(clrDgHiddenChange)="columnHiddenChange(6)">
{{ 'REPLICATION.REPLICATION_TRIGGER' | translate }}
</ng-template></clr-dg-column
>
<clr-dg-column [clrDgSortBy]="'speed'">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[7] }"
(clrDgHiddenChange)="columnHiddenChange(7)">
{{ 'REPLICATION.BANDWIDTH' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column [clrDgField]="'description'">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[8] }"
(clrDgHiddenChange)="columnHiddenChange(8)">
{{ 'REPLICATION.DESCRIPTION' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-placeholder>{{
'REPLICATION.PLACEHOLDER' | translate
}}</clr-dg-placeholder>

View File

@ -7,15 +7,15 @@
}
.min-width {
width: 236px;
min-width: 236px;
}
.col-width {
width: 140px;
min-width: 140px;
}
.status-width {
width: 130px;
min-width: 130px;
}
.icon-style {

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {
AfterViewInit,
Component,
EventEmitter,
Input,
@ -31,10 +32,12 @@ import {
import { ErrorHandler } from '../../../../../shared/units/error-handler';
import {
clone,
getHiddenArrayFromLocalStorage,
getPageSizeFromLocalStorage,
getQueryString,
getSortingString,
PageSizeMapKeys,
setHiddenArrayToLocalStorage,
setPageSizeToLocalStorage,
} from '../../../../../shared/units/utils';
import {
@ -64,7 +67,7 @@ import { JobType } from '../../../job-service-dashboard/job-service-dashboard.in
templateUrl: './list-replication-rule.component.html',
styleUrls: ['./list-replication-rule.component.scss'],
})
export class ListReplicationRuleComponent implements OnInit {
export class ListReplicationRuleComponent implements OnInit, AfterViewInit {
@Input() selectedId: number | string;
@Input() withReplicationJob: boolean;
@Input() hasCreateReplicationPermission: boolean;
@ -94,14 +97,21 @@ export class ListReplicationRuleComponent implements OnInit {
loading: boolean = true;
paused: boolean = false;
hiddenArray: boolean[] = getHiddenArrayFromLocalStorage(
PageSizeMapKeys.LIST_REPLICATION_RULE_COMPONENT,
[false, false, false, false, false, false, false, true, true]
);
copiedHiddenArray: boolean[] = [];
private _hasViewInit: boolean = false;
constructor(
private replicationService: ReplicationService,
private translateService: TranslateService,
private errorHandlerEntity: ErrorHandler,
private operationService: OperationService,
private scheduleService: ScheduleService
) {}
) {
this.copiedHiddenArray = clone(this.hiddenArray);
}
ngOnInit() {
this.scheduleService
@ -111,6 +121,10 @@ export class ListReplicationRuleComponent implements OnInit {
});
}
ngAfterViewInit() {
this._hasViewInit = true;
}
getTriggerTypeI18n(t: ReplicationTrigger) {
if (t) {
if (this.paused && t?.type === TRIGGER.SCHEDULED) {
@ -383,4 +397,14 @@ export class ListReplicationRuleComponent implements OnInit {
}
return 'REPLICATION.UNLIMITED';
}
columnHiddenChange(index: number) {
if (this._hasViewInit) {
this.copiedHiddenArray[index] = !this.copiedHiddenArray[index];
setHiddenArrayToLocalStorage(
PageSizeMapKeys.LIST_REPLICATION_RULE_COMPONENT,
this.copiedHiddenArray
);
}
}
}

View File

@ -136,34 +136,82 @@
</clr-dg-action-bar>
<clr-dg-column class="flex-max-width" [clrDgSortBy]="'digest'"
>{{ 'REPOSITORY.ARTIFACTS_COUNT' | translate }}
><ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[0] }"
(clrDgHiddenChange)="columnHiddenChange(0)">
{{ 'REPOSITORY.ARTIFACTS_COUNT' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="pull-command-column">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[1] }"
(clrDgHiddenChange)="columnHiddenChange(1)">
{{ 'REPOSITORY.PULL_COMMAND' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column *ngIf="depth">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[2] }"
(clrDgHiddenChange)="columnHiddenChange(2)">
{{ 'REPOSITORY.PLATFORM' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column>
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[3] }"
(clrDgHiddenChange)="columnHiddenChange(3)">
{{ 'REPOSITORY.TAGS' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="co-signed-column">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[4] }"
(clrDgHiddenChange)="columnHiddenChange(4)">
{{ 'ACCESSORY.CO_SIGNED' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'size'">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[5] }"
(clrDgHiddenChange)="columnHiddenChange(5)">
{{ 'REPOSITORY.SIZE' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="vul-column">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[6] }"
(clrDgHiddenChange)="columnHiddenChange(6)">
{{ 'REPOSITORY.VULNERABILITY' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="annotations-column">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[7] }"
(clrDgHiddenChange)="columnHiddenChange(7)">
{{ 'ARTIFACT.ANNOTATION' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column>
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[8] }"
(clrDgHiddenChange)="columnHiddenChange(8)">
{{ 'REPOSITORY.LABELS' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pushComparator">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[9] }"
(clrDgHiddenChange)="columnHiddenChange(9)">
{{ 'REPOSITORY.PUSH_TIME' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pullComparator">
<ng-template
[clrDgHideableColumn]="{ hidden: hiddenArray[10] }"
(clrDgHiddenChange)="columnHiddenChange(10)">
{{ 'REPOSITORY.PULL_TIME' | translate }}
</ng-template>
</clr-dg-column>
<clr-dg-column class="pull-command-column">{{
'REPOSITORY.PULL_COMMAND' | translate
}}</clr-dg-column>
<clr-dg-column *ngIf="depth">{{
'REPOSITORY.PLATFORM' | translate
}}</clr-dg-column>
<clr-dg-column>{{ 'REPOSITORY.TAGS' | translate }}</clr-dg-column>
<clr-dg-column class="co-signed-column">{{
'ACCESSORY.CO_SIGNED' | translate
}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'size'">{{
'REPOSITORY.SIZE' | translate
}}</clr-dg-column>
<clr-dg-column class="vul-column">{{
'REPOSITORY.VULNERABILITY' | translate
}}</clr-dg-column>
<clr-dg-column class="annotations-column">{{
'ARTIFACT.ANNOTATION' | translate
}}</clr-dg-column>
<clr-dg-column>{{ 'REPOSITORY.LABELS' | translate }}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pushComparator">{{
'REPOSITORY.PUSH_TIME' | translate
}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pullComparator">{{
'REPOSITORY.PULL_TIME' | translate
}}</clr-dg-column>
<clr-dg-placeholder>{{
'ARTIFACT.PLACEHOLDER' | translate
}}</clr-dg-placeholder>

View File

@ -11,7 +11,13 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import {
AfterViewInit,
Component,
OnDestroy,
OnInit,
ViewChild,
} from '@angular/core';
import { forkJoin, Observable, of, Subscription } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
@ -30,9 +36,11 @@ import {
DEFAULT_SUPPORTED_MIME_TYPES,
doSorting,
formatSize,
getHiddenArrayFromLocalStorage,
getPageSizeFromLocalStorage,
getSortingString,
PageSizeMapKeys,
setHiddenArrayToLocalStorage,
setPageSizeToLocalStorage,
VULNERABILITY_SCAN_STATUS,
} from '../../../../../../../shared/units/utils';
@ -96,7 +104,9 @@ const FALSE: string = 'false';
templateUrl: './artifact-list-tab.component.html',
styleUrls: ['./artifact-list-tab.component.scss'],
})
export class ArtifactListTabComponent implements OnInit, OnDestroy {
export class ArtifactListTabComponent
implements OnInit, OnDestroy, AfterViewInit
{
projectId: number;
projectName: string;
repoName: string;
@ -160,6 +170,25 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
onScanArtifactsLength: number = 0;
stopBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
updateArtifactSub: Subscription;
hiddenArray: boolean[] = getHiddenArrayFromLocalStorage(
PageSizeMapKeys.ARTIFACT_LIST_TAB_COMPONENT,
[
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
false,
]
);
copiedHiddenArray: boolean[] = [];
private _hasViewInit: boolean = false;
constructor(
private errorHandlerService: ErrorHandler,
private artifactService: ArtifactService,
@ -171,7 +200,9 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
private router: Router,
private appConfigService: AppConfigService,
private artifactListPageService: ArtifactListPageService
) {}
) {
this.copiedHiddenArray = clone(this.hiddenArray);
}
initRouterData() {
this.projectId =
this.activatedRoute.snapshot?.parent?.parent?.params['id'];
@ -212,6 +243,11 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
);
}
}
ngAfterViewInit() {
this._hasViewInit = true;
}
ngOnDestroy() {
if (this.updateArtifactSub) {
this.updateArtifactSub.unsubscribe();
@ -1014,4 +1050,14 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
isEllipsisActive(ele: HTMLSpanElement): boolean {
return ele?.offsetWidth < ele?.scrollWidth;
}
columnHiddenChange(index: number) {
if (this._hasViewInit) {
this.copiedHiddenArray[index] = !this.copiedHiddenArray[index];
setHiddenArrayToLocalStorage(
PageSizeMapKeys.ARTIFACT_LIST_TAB_COMPONENT,
this.copiedHiddenArray
);
}
}
}

View File

@ -2,6 +2,7 @@ import {
DEFAULT_PAGE_SIZE,
delUrlParam,
durationStr,
getHiddenArrayFromLocalStorage,
getPageSizeFromLocalStorage,
getQueryString,
getSizeNumber,
@ -9,6 +10,7 @@ import {
getSortingString,
isSameArrayValue,
isSameObject,
setHiddenArrayToLocalStorage,
setPageSizeToLocalStorage,
} from './utils';
import { ClrDatagridStateInterface } from '@clr/angular';
@ -127,4 +129,31 @@ describe('functions in utils.ts should work', () => {
expect(durationStr(61111)).toEqual('1min 1sec');
expect(durationStr(3661111)).toEqual('1hrs 1min 1sec');
});
it('functions getHiddenArrayFromLocalStorage() and setHiddenArrayToLocalStorage() should work', () => {
let store = {};
spyOn(localStorage, 'getItem').and.callFake(key => {
return store[key];
});
spyOn(localStorage, 'setItem').and.callFake((key, value) => {
return (store[key] = value + '');
});
spyOn(localStorage, 'clear').and.callFake(() => {
store = {};
});
expect(getHiddenArrayFromLocalStorage(null, [])).toEqual([]);
expect(getHiddenArrayFromLocalStorage('test', [true])).toEqual([true]);
expect(getHiddenArrayFromLocalStorage('test1', [])).toEqual([]);
setHiddenArrayToLocalStorage('test1', [false, false, false]);
expect(getHiddenArrayFromLocalStorage('test1', [false])).toEqual([
false,
false,
false,
]);
setHiddenArrayToLocalStorage('test1', [true, true]);
expect(getHiddenArrayFromLocalStorage('test1', [false])).toEqual([
true,
true,
]);
});
});

View File

@ -888,6 +888,11 @@ export function delUrlParam(url: string, key: string): string {
const PAGE_SIZE_MAP_KEY: string = 'pageSizeMap';
interface DataGridMetadata {
pageSize?: number;
columnHiddenArray?: boolean[];
}
/**
* Get the page size from the browser's localStorage
* @param key
@ -901,10 +906,12 @@ export function getPageSizeFromLocalStorage(
initialSize = DEFAULT_PAGE_SIZE;
}
if (localStorage && key && localStorage.getItem(PAGE_SIZE_MAP_KEY)) {
const pageSizeMap: {
[k: string]: number;
const metadataMap: {
[k: string]: DataGridMetadata;
} = JSON.parse(localStorage.getItem(PAGE_SIZE_MAP_KEY));
return pageSizeMap[key] ? pageSizeMap[key] : initialSize;
return metadataMap[key]?.pageSize
? metadataMap[key]?.pageSize
: initialSize;
}
return initialSize;
}
@ -920,11 +927,62 @@ export function setPageSizeToLocalStorage(key: string, pageSize: number) {
// if first set
localStorage.setItem(PAGE_SIZE_MAP_KEY, '{}');
}
const pageSizeMap: {
[k: string]: number;
const metadataMap: {
[k: string]: DataGridMetadata;
} = JSON.parse(localStorage.getItem(PAGE_SIZE_MAP_KEY));
pageSizeMap[key] = pageSize;
localStorage.setItem(PAGE_SIZE_MAP_KEY, JSON.stringify(pageSizeMap));
if (!isObject(metadataMap[key])) {
metadataMap[key] = {};
}
metadataMap[key].pageSize = pageSize;
localStorage.setItem(PAGE_SIZE_MAP_KEY, JSON.stringify(metadataMap));
}
}
/**
* Get the hidden array from the browser's localStorage
* @param key
* @param initialArray
*/
export function getHiddenArrayFromLocalStorage(
key: string,
initialArray: boolean[]
) {
if (!initialArray?.length) {
initialArray = [];
}
if (localStorage && key && localStorage.getItem(PAGE_SIZE_MAP_KEY)) {
const metadataMap: {
[k: string]: DataGridMetadata;
} = JSON.parse(localStorage.getItem(PAGE_SIZE_MAP_KEY));
return metadataMap[key]?.columnHiddenArray
? metadataMap[key]?.columnHiddenArray
: initialArray;
}
return initialArray;
}
/**
* Set the hidden array to the browser's localStorage
* @param key
* @param hiddenArray
*/
export function setHiddenArrayToLocalStorage(
key: string,
hiddenArray: boolean[]
) {
if (localStorage && key && hiddenArray?.length) {
if (!localStorage.getItem(PAGE_SIZE_MAP_KEY)) {
// if first set
localStorage.setItem(PAGE_SIZE_MAP_KEY, '{}');
}
const metadataMap: {
[k: string]: DataGridMetadata;
} = JSON.parse(localStorage.getItem(PAGE_SIZE_MAP_KEY));
if (!isObject(metadataMap[key])) {
metadataMap[key] = {};
}
metadataMap[key].columnHiddenArray = hiddenArray;
localStorage.setItem(PAGE_SIZE_MAP_KEY, JSON.stringify(metadataMap));
}
}