Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAS-133308 / 25.04 / Remove call for buckets when not correct role. Adds readonly to ix-explorer #11289

Merged
merged 7 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading