Skip to content

Commit

Permalink
NAS-133308 / 25.04 / Remove call for buckets when not correct role. A…
Browse files Browse the repository at this point in the history
…dds `readonly` to `ix-explorer` (#11289)
  • Loading branch information
RehanY147 authored Jan 9, 2025
1 parent d102a73 commit 5dce43e
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<tree-root
#tree
[nodes]="nodes"
[options]="treeOptions"
[options]="treeOptions()"
(select)="onNodeSelect($event)"
(deselect)="onNodeDeselect($event)"
>
Expand Down Expand Up @@ -79,6 +79,7 @@
</tree-root>
</div>


@if (loadingError) {
<mat-error class="loading-error">
{{ loadingError }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,5 +358,9 @@ describe.skip('IxExplorerComponent', () => {
expect(spectator.query('input')).toBeDisabled();
expect(spectator.query('.tree-container')).toHaveClass('disabled');
});

// TODO: Add test 'disables input when readonly is set to true on ix-explorer'
// when overall tests for the component are working, after the following issue is solved
// https://github.com/help-me-mom/ng-mocks/issues/10503
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, input,
Component, computed, input,
OnChanges,
OnInit, viewChild,
OnInit, Signal, viewChild,
} from '@angular/core';
import { ControlValueAccessor, NgControl, ReactiveFormsModule } from '@angular/forms';
import { MatButton } from '@angular/material/button';
Expand Down Expand Up @@ -63,6 +63,7 @@ import { ErrorHandlerService } from 'app/services/error-handler.service';
export class IxExplorerComponent implements OnInit, OnChanges, ControlValueAccessor {
readonly label = input<string>();
readonly hint = input<string>();
readonly readonly = input<boolean>(false);
readonly multiple = input(false);
readonly tooltip = input<string>();
readonly required = input<boolean>(false);
Expand Down Expand Up @@ -110,13 +111,16 @@ export class IxExplorerComponent implements OnInit, OnChanges, ControlValueAcces
},
};

treeOptions: ITreeOptions = {
idField: 'path',
displayField: 'name',
getChildren: (node: TreeNode<ExplorerNodeData>) => firstValueFrom(this.loadChildren(node)),
actionMapping: this.actionMapping,
useTriState: false,
};
treeOptions: Signal<ITreeOptions> = computed<ITreeOptions>(() => {
return {
idField: 'path',
displayField: 'name',
getChildren: (node: TreeNode<ExplorerNodeData>) => firstValueFrom(this.loadChildren(node)),
actionMapping: this.actionMapping,
useTriState: false,
useCheckbox: this.multiple(),
};
});

constructor(
public controlDirective: NgControl,
Expand All @@ -129,10 +133,6 @@ export class IxExplorerComponent implements OnInit, OnChanges, ControlValueAcces
}

ngOnChanges(changes: IxSimpleChanges<this>): void {
if ('multiple' in changes) {
this.treeOptions.useCheckbox = this.multiple();
}

if ('nodeProvider' in changes || 'root' in changes) {
this.setInitialNode();
this.cdr.markForCheck();
Expand All @@ -159,7 +159,7 @@ export class IxExplorerComponent implements OnInit, OnChanges, ControlValueAcces
}

setDisabledState?(isDisabled: boolean): void {
this.isDisabled = isDisabled;
this.isDisabled = isDisabled || this.readonly();
this.cdr.markForCheck();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
<ix-explorer
formControlName="folder_destination"
root="/"
[readonly]="!hasRequiredRoles()"
[label]="helptext.folder_placeholder | translate"
[tooltip]="helptext.folder_tooltip | translate"
[nodeProvider]="bucketNodeProvider"
Expand All @@ -106,6 +107,7 @@
<ix-explorer
formControlName="folder_source"
root="/"
[readonly]="!hasRequiredRoles()"
[label]="helptext.folder_placeholder | translate"
[tooltip]="helptext.folder_tooltip | translate"
[multiple]="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@ import { mntPath } from 'app/enums/mnt-path.enum';
import { TransferMode } from 'app/enums/transfer-mode.enum';
import { CloudSyncTaskUi } from 'app/interfaces/cloud-sync-task.interface';
import { CloudSyncCredential } from 'app/interfaces/cloudsync-credential.interface';
import { AuthService } from 'app/modules/auth/auth.service';
import { DialogService } from 'app/modules/dialog/dialog.service';
import {
CloudCredentialsSelectComponent,
} from 'app/modules/forms/custom-selects/cloud-credentials-select/cloud-credentials-select.component';
import { IxSelectHarness } from 'app/modules/forms/ix-forms/components/ix-select/ix-select.harness';
import { SlideIn } from 'app/modules/slide-ins/slide-in';
import { SlideInRef } from 'app/modules/slide-ins/slide-in-ref';
import { ApiService } from 'app/modules/websocket/api.service';
import { CloudSyncFormComponent } from 'app/pages/data-protection/cloudsync/cloudsync-form/cloudsync-form.component';
import {
TransferModeExplanationComponent,
} from 'app/pages/data-protection/cloudsync/transfer-mode-explanation/transfer-mode-explanation.component';
import { CloudCredentialService } from 'app/services/cloud-credential.service';
import { FilesystemService } from 'app/services/filesystem.service';

describe('CloudSyncFormComponent', () => {
Expand Down Expand Up @@ -76,6 +79,59 @@ describe('CloudSyncFormComponent', () => {
state: { state: JobState.Pending },
} as CloudSyncTaskUi;

const existingTask2 = {
id: 1,
description: 'test3',
path: '/mnt/dozer',
attributes: {
folder: '/',
bucket: 'test3',
fast_list: false,
},
next_run: 'Disabled',
pre_script: '',
post_script: '',
snapshot: false,
include: [],
exclude: [],
args: '',
enabled: true,
job: null,
direction: 'PULL',
transfer_mode: 'COPY',
bwlimit: [],
transfers: 4,
encryption: false,
filename_encryption: false,
encryption_password: '',
encryption_salt: '',
create_empty_src_dirs: false,
follow_symlinks: false,
credentials: {
id: 1,
name: 'Storj',
provider: {
type: 'STORJ_IX',
access_key_id: 'julzdrlwyv37oixflnbyysbumg3q',
secret_access_key: 'jyncyw7oup4ad2fv3tectsaksdag73oi7633arrzdlj77gmmywmvo',
},
},
schedule: {
minute: '0',
hour: '0',
dom: '*',
month: '*',
dow: '*',
},
locked: false,
credential: 'Storj',
next_run_time: '2025-01-08T08:00:00.000Z',
state: {
state: 'PENDING',
},
last_run: 'Disabled',
} as CloudSyncTaskUi;

let loader: HarnessLoader;
let spectator: Spectator<CloudSyncFormComponent>;
const getData = jest.fn(() => existingTask);
Expand Down Expand Up @@ -280,4 +336,84 @@ describe('CloudSyncFormComponent', () => {
expect(slideInRef.close).toHaveBeenCalledWith({ response: existingTask, error: null });
});
});
describe('doesnt load buckets when user doesnt has roles', () => {
beforeEach(() => {
spectator = createComponent({
providers: [
mockProvider(SlideInRef, {
...slideInRef,
getData: jest.fn(() => existingTask2),
}),
mockProvider(CloudCredentialService, {
getProviders: jest.fn(() => {
return of([{
name: CloudSyncProviderName.Http,
title: 'Http',
buckets: false,
bucket_title: 'Bucket',
task_schema: [],
credentials_schema: [],
credentials_oauth: null,
},
{
name: CloudSyncProviderName.Mega,
title: 'Mega',
buckets: false,
bucket_title: 'Bucket',
task_schema: [],
credentials_schema: [],
credentials_oauth: null,
},
{
name: CloudSyncProviderName.Storj,
title: 'Storj iX',
credentials_oauth: null,
credentials_schema: [],
buckets: true,
bucket_title: 'Bucket',
task_schema: [
{
property: 'fast_list',
schema: {
type: 'boolean',
_name_: 'fast_list',
title: 'Use --fast-list',
description: 'Use fewer transactions in exchange for more RAM. This may also speed up or slow down your\ntransfer. See [rclone documentation](https://rclone.org/docs/#fast-list) for more details.',
default: false,
_required_: false,
},
},
],
}]);
}),
getCloudSyncCredentials: jest.fn(() => {
return of([
{
id: 1,
name: 'Storj',
provider: {
type: CloudSyncProviderName.Storj,
url: '',
access_key_id: 'julzdrlwyv37oixflnbyysbumg3q',
secret_access_key: 'jyncyw7oup4ad2fv3tectsaksdag73oi7633arrzdlj77gmmywmvo',
},
},
]);
}),
}),
mockProvider(AuthService, {
hasRole: jest.fn(() => of(false)),
}),
],
});
loader = TestbedHarnessEnvironment.loader(spectator.fixture);
spectator.detectChanges();
});

it('doesnt load buckets', async () => {
const buckets = await loader.getHarness(IxSelectHarness.with({ label: 'Bucket' }));
const options = await buckets.getOptionLabels();
expect(options).toEqual(['--', 'test3']);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit,
} from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
import { MatButton } from '@angular/material/button';
import { MatCard, MatCardContent } from '@angular/material/card';
Expand Down Expand Up @@ -35,6 +36,7 @@ import { CloudSyncCredential } from 'app/interfaces/cloudsync-credential.interfa
import { CloudSyncProvider } from 'app/interfaces/cloudsync-provider.interface';
import { newOption, SelectOption } from 'app/interfaces/option.interface';
import { ExplorerNodeData, TreeNode } from 'app/interfaces/tree-node.interface';
import { AuthService } from 'app/modules/auth/auth.service';
import { DialogService } from 'app/modules/dialog/dialog.service';
import { CloudCredentialsSelectComponent } from 'app/modules/forms/custom-selects/cloud-credentials-select/cloud-credentials-select.component';
import { IxCheckboxComponent } from 'app/modules/forms/ix-forms/components/ix-checkbox/ix-checkbox.component';
Expand Down Expand Up @@ -210,6 +212,8 @@ export class CloudSyncFormComponent implements OnInit {

bucketOptions$ = of<SelectOption[]>([]);

protected readonly hasRequiredRoles = toSignal(this.authService.hasRole(this.requiredRoles));

fileNodeProvider: TreeNodeProvider;
bucketNodeProvider: TreeNodeProvider;

Expand All @@ -229,6 +233,7 @@ export class CloudSyncFormComponent implements OnInit {
private filesystemService: FilesystemService,
protected cloudCredentialService: CloudCredentialService,
public slideInRef: SlideInRef<CloudSyncTaskUi | undefined, CloudSyncTask | false>,
private authService: AuthService,
) {
this.slideInRef.requireConfirmationWhen(() => {
return of(this.form.dirty);
Expand Down Expand Up @@ -388,6 +393,17 @@ export class CloudSyncFormComponent implements OnInit {
}

loadBucketOptions(): void {
if (!this.hasRequiredRoles()) {
this.isLoading = false;
const bucket = this.editingTask.attributes.bucket as string;
if (bucket) {
this.form.controls.bucket.enable();
this.bucketOptions$ = of([{ label: bucket, value: bucket }]);
this.form.controls.bucket.setValue(bucket);
}
this.cdr.markForCheck();
return;
}
const targetCredentials = find(this.credentialsList, { id: this.form.controls.credentials.value });
if (!targetCredentials) {
return;
Expand Down

0 comments on commit 5dce43e

Please sign in to comment.