[PRODSEC-6575] Shared link not accessible now after the expiry date which was earlier accessible even after that expiration date (#8540)

* Shared link expiry date code implementation

* test case updated

* removed unsed code

* design changes as per the new design of Share dialog

* test cases modification as per new design changes

* placeholder modified for expiration date

* look and feel changes for share dialog as per Shane comments

* boolean name changed as per naming convention

* review comments addressed

* review comments addressed

* type specified for node object

* linting corrections

* resolved nested ternary date operation into an independent statement

* review comments addressed

* used date-fns instead of moment.js in code as well as in test cases

* review comments for date-fns addressed

* removed extra line

* removed extra empty lines in template

* import changes and indentation correction

* error in console resolved which was occuring after selecting date and time

* used mat-datepicker instead of mat-datetimepicker

* package-lock.json file updated for date-fns implementation

* made type  'date' as default and removed the settings coming from the ACA

* unit test case modifications as per calender changes

* e2e modifications as per new calendar component
This commit is contained in:
Jatin Chugh 2023-06-14 15:30:08 +05:30 committed by GitHub
parent 95927c6319
commit 479c96eabb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 361 additions and 225 deletions

View File

@ -132,23 +132,6 @@ describe('Share file', () => {
await BrowserActions.closeMenuAndDialogs();
});
it('[C286548] Should be possible to set expiry date for link', async () => {
await contentServicesPage.clickShareButton();
await shareDialog.checkDialogIsDisplayed();
await shareDialog.clickExpireToggle();
await shareDialog.setDefaultDay();
await shareDialog.setDefaultHour();
await shareDialog.setDefaultMinutes();
await shareDialog.dateTimePickerDialogIsClosed();
const value = await shareDialog.getExpirationDate();
await shareDialog.clickCloseButton();
await shareDialog.dialogIsClosed();
await contentServicesPage.clickShareButton();
await shareDialog.checkDialogIsDisplayed();
await shareDialog.expirationDateInputHasValue(value);
await BrowserActions.closeMenuAndDialogs();
});
it('[C310329] Should be possible to set expiry date only for link', async () => {
await LocalStorageUtil.setConfigField('sharedLinkDateTimePickerType', JSON.stringify('date'));
await contentServicesPage.clickShareButton();

View File

@ -17,8 +17,7 @@
import { $$, $ } from 'protractor';
import { BrowserVisibility, TogglePage, BrowserActions, DateTimePickerPage } from '@alfresco/adf-testing';
import * as moment from 'moment';
import { format, add } from 'date-fns';
export class ShareDialogPage {
togglePage = new TogglePage();
@ -85,7 +84,7 @@ export class ShareDialogPage {
}
async calendarTodayDayIsDisabled(): Promise<void> {
const tomorrow = moment().add(1, 'days').format('D');
const tomorrow = format(add(new Date(), {days: 1}), 'd');
if (tomorrow !== '1') {
await this.dateTimePickerPage.checkCalendarTodayDayIsDisabled();
@ -93,7 +92,7 @@ export class ShareDialogPage {
}
async setDefaultDay(): Promise<void> {
const tomorrow = moment().add(1, 'days').format('D');
const tomorrow = format(add(new Date(), {days: 1}), 'd');
await this.dateTimePickerPage.setDate(tomorrow);
}

View File

@ -1,89 +1,115 @@
<div class="adf-share-link__dialog-content">
<div data-automation-id="adf-share-dialog-title" class="adf-share-link__title" role="heading" aria-level="1">
{{ 'SHARE.DIALOG-TITLE' | translate }} {{ fileName }}
</div>
<mat-dialog-content>
<p class="adf-share-link__info">{{ 'SHARE.DESCRIPTION' | translate }}</p>
<div class="adf-share-link__dialog-container">
<div class="adf-share-link--row">
<div class="adf-share-link__label">{{ 'SHARE.TITLE' | translate }}</div>
<mat-slide-toggle
color="primary"
data-automation-id="adf-share-toggle"
aria-label="{{ 'SHARE.TITLE' | translate }}"
[checked]="isFileShared"
[disabled]="!canUpdate || isDisabled"
(change)="onSlideShareChange($event)">
</mat-slide-toggle>
</div>
<form [formGroup]="form">
<mat-form-field class="adf-full-width adf-float-label" floatLabel='always'>
<input
#sharedLinkInput
data-automation-id="adf-share-link"
class="adf-share-link__input"
matInput
cdkFocusInitial
placeholder="{{ 'SHARE.PUBLIC-LINK' | translate }}"
[attr.aria-label]="'SHARE.PUBLIC-LINK' | translate"
formControlName="sharedUrl"
readonly="readonly">
<mat-icon
class="adf-input-action"
role="button"
matSuffix
[clipboard-notification]="'SHARE.CLIPBOARD-MESSAGE' | translate" [adf-clipboard]
[attr.aria-label]="'SHARE.COPY_BUTTON_LABEL' | translate"
[target]="sharedLinkInput"
tabindex="0">
content_copy
</mat-icon>
</mat-form-field>
<div class="adf-share-link--row">
<div class="adf-share-link__label">{{ 'SHARE.EXPIRES' | translate }}</div>
<mat-slide-toggle
#slideToggleExpirationDate
[disabled]="!canUpdate"
color="primary"
data-automation-id="adf-expire-toggle"
aria-label="{{ 'SHARE.EXPIRES' | translate }}"
[checked]="time.value"
(change)="onToggleExpirationDate($event)">
</mat-slide-toggle>
<div
data-automation-id="adf-share-dialog-title"
class="adf-share-link__title adf-share-link__label adf-share-link__heading"
role="heading"
aria-level="1">
{{ 'SHARE.DIALOG-TITLE' | translate }} {{ fileName }}
</div>
<mat-form-field class="adf-full-width adf-float-label" floatLabel='always'>
<mat-datetimepicker-toggle
#matDatetimepickerToggle="matDatetimepickerToggle"
[disabled]="time.disabled"
[for]="datetimePicker"
matSuffix>
</mat-datetimepicker-toggle>
<mat-datetimepicker
#datetimePicker
(closed)="onDatetimepickerClosed()"
[type]="type"
[timeInterval]="1">
</mat-datetimepicker>
<input class="adf-share-link__input"
#dateTimePickerInput
matInput
placeholder="{{ 'SHARE.EXPIRATION-LABEL' | translate }}"
[attr.aria-label]="'SHARE.EXPIRATION-LABEL' | translate"
[min]="minDate"
formControlName="time"
[matDatetimepicker]="datetimePicker" />
</mat-form-field>
</form>
</mat-dialog-content>
<div mat-dialog-actions>
<button data-automation-id="adf-share-dialog-close" mat-button color="primary" mat-dialog-close>
{{ 'SHARE.CLOSE' | translate }}
</button>
<mat-icon mat-dialog-close class="adf-share-link__close adf-share-link__icon">close</mat-icon>
</div>
<mat-dialog-content>
<hr class="adf-share-link__separation-line" />
<form [formGroup]="form" class="adf-share-link__form">
<div class="adf-share-link--row">
<mat-icon class="adf-share-link__icon">timer</mat-icon>
<div class="adf-share-link__label adf-sharable-link">{{ 'SHARE.LINK-EXPIRY-DATE' | translate }}
</div>
<mat-slide-toggle
#slideToggleExpirationDate
[disabled]="!canUpdate"
color="primary"
data-automation-id="adf-expire-toggle"
aria-label="{{ 'SHARE.EXPIRES' | translate }}"
[checked]="time.value"
(change)="onToggleExpirationDate($event)">
</mat-slide-toggle>
</div>
<div
[style.display]="isExpiryDateToggleChecked ? 'block' : 'none'"
data-automation-id="adf-slide-toggle-checked"
class="adf-share-link__date-time-container">
<mat-form-field class="adf-full-width adf-float-label" floatLabel='always'>
<mat-label>{{ 'SHARE.EXPIRATION-PLACEHOLDER' | translate }}</mat-label>
<mat-datepicker-toggle
[disabled]="time.disabled"
[for]="datePicker"
matSuffix
class="adf-share-link__icon adf-share-link__calender-icon">
</mat-datepicker-toggle>
<mat-datepicker
#datePicker
(closed)="onDatePickerClosed()">
</mat-datepicker>
<input
class="adf-share-link__input"
#datePickerInput
matInput
placeholder="{{ 'SHARE.EXPIRATION-PLACEHOLDER' | translate }}"
[attr.aria-label]="'SHARE.EXPIRATION-LABEL' | translate"
[min]="minDate"
formControlName="time"
[matDatepicker]="datePicker" />
</mat-form-field>
</div>
<p class="adf-share-link__info adf-share-link__para">{{ 'SHARE.SHARE-LINK' | translate }}</p>
<div class="adf-share-link--row">
<mat-slide-toggle
color="primary"
data-automation-id="adf-share-toggle"
aria-label="{{ 'SHARE.TITLE' | translate }}"
[checked]="isFileShared"
[disabled]="!canUpdate || isDisabled"
(change)="onSlideShareChange($event)">
</mat-slide-toggle>
<div class="adf-share-link__label adf-sharable-link">{{ 'SHARE.SHARABLE-LINK-CREATED' | translate }}
</div>
</div>
<mat-form-field
class="adf-full-width adf-float-label"
floatLabel='always'
[ngClass]="isLinkWithExpiryDate? 'adf-share-link__border-color' : ''">
<input
#sharedLinkInput
data-automation-id="adf-share-link"
class="adf-share-link__input"
matInput
cdkFocusInitial
placeholder="{{ 'SHARE.PUBLIC-LINK' | translate }}"
[attr.aria-label]="'SHARE.PUBLIC-LINK' | translate"
formControlName="sharedUrl"
readonly="readonly">
<mat-icon
class="adf-input-action adf-share-link__icon"
role="button"
matSuffix
[clipboard-notification]="'SHARE.CLIPBOARD-MESSAGE' | translate"
[adf-clipboard]
[attr.aria-label]="'SHARE.COPY_BUTTON_LABEL' | translate"
[target]="sharedLinkInput"
tabindex="0">
content_copy
</mat-icon>
</mat-form-field>
<p class="adf-share-link__warn adf-share-link__para" *ngIf="isLinkWithExpiryDate">
{{ 'SHARE.LINK-WITH-EXPIRY-SETTINGS' | translate }}
</p>
<div class="adf-share-link--row">
<mat-icon class="adf-share-link__icon">public</mat-icon>
<p
class="adf-share-link__info adf-sharable-link adf-share-link__public-content adf-share-link__para">
{{ 'SHARE.PUBLIC-CONTENT' | translate }}
</p>
</div>
</form>
<hr class="adf-share-link__separation-line" />
</mat-dialog-content>
<div mat-dialog-actions>
<button data-automation-id="adf-share-dialog-close" mat-button color="primary" mat-dialog-close>
{{ 'SHARE.CLOSE' | translate }}
</button>
</div>
</div>
</div>

View File

@ -7,6 +7,14 @@
&__dialog-content {
display: flex;
flex-direction: column;
padding: 24px;
background-color: var(--theme-grey-text-background-color);
}
&__dialog-container {
background-color: var(--theme-card-background-color);
border-radius: 16px;
padding: 16px 24px;
}
&__label,
@ -17,7 +25,7 @@
font-weight: normal;
font-style: normal;
font-stretch: normal;
font-size: var(--theme-subheading-2-font-size);
font-size: var(--theme-body-1-font-size);
color: var(--adf-theme-foreground-text-color-087);
}
@ -25,22 +33,68 @@
flex: 1 1 auto;
}
&__info {
&__form {
padding-top: 8px;
}
&__public-content {
color: var(--adf-theme-foreground-text-color-054);
font-size: var(--theme-caption-font-size);
}
&__warn {
color: var(--theme-warn-color);
font-size: var(--theme-caption-font-size);
}
&--row {
display: flex;
flex-direction: row;
/* stylelint-disable-next-line declaration-block-no-redundant-longhand-properties */
flex-wrap: wrap;
align-items: center;
margin: 8px 0;
}
&__input {
color: var(--adf-theme-foreground-text-color-087);
}
&__separation-line {
border: 1px solid var(--theme-grey-background-color);
margin: 8px -24px;
}
&__close {
cursor: pointer;
margin-left: 12px;
}
&__icon {
color: var(--adf-theme-foreground-icon-color-054);
}
&__para {
margin-top: 8px;
margin-bottom: 8px;
}
&__heading {
font-weight: 700;
font-size: var(--theme-subheading-2-font-size);
}
&__calender-icon {
font-size: 18px;
}
&__date-time-container {
padding-bottom: 8px;
}
&__border-color {
border: 1px solid var(--theme-warn-color);
}
}
.adf-input-action {
@ -49,6 +103,13 @@
.adf-full-width {
width: 100%;
background-color: var(--theme-grey-text-background-color);
border-radius: 6px;
padding-top: 8px;
}
.adf-sharable-link {
margin-left: 8px;
}
.mat-form-field-infix {
@ -57,9 +118,13 @@
.mat-dialog-actions {
justify-content: flex-end;
padding: 0;
margin: 8px 0 0;
& > button {
text-transform: uppercase;
color: var(--adf-theme-foreground-base-color);
background-color: var(--theme-grey-text-background-color);
margin-right: 12px;
}
}
@ -67,6 +132,20 @@
align-items: center;
}
.mat-dialog-container {
padding: 0;
}
.mat-form-field-appearance-legacy .mat-form-field-underline {
display: none;
}
.mat-form-field-appearance-legacy .mat-form-field-wrapper {
padding-bottom: 8px;
margin-right: 8px;
margin-left: 8px;
}
@media screen and (max-width: 380px) {
.mat-dialog-container {
padding: 0 15px;

View File

@ -28,9 +28,9 @@ import { RenditionService } from '../common/services/rendition.service';
import { SharedLinksApiService } from './services/shared-links-api.service';
import { ShareDialogComponent } from './content-node-share.dialog';
import moment from 'moment';
import { ContentTestingModule } from '../testing/content.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { format, endOfDay } from 'date-fns';
describe('ShareDialogComponent', () => {
let node;
@ -45,6 +45,13 @@ describe('ShareDialogComponent', () => {
let component: ShareDialogComponent;
let appConfigService: AppConfigService;
const shareToggleId = '[data-automation-id="adf-share-toggle"]';
const getShareToggleLinkedClasses = (): DOMTokenList => fixture.nativeElement.querySelector(shareToggleId).classList;
const clickShareToggleButton = () => fixture.nativeElement.querySelector(`${shareToggleId} label`)
.dispatchEvent(new MouseEvent('click'));
setupTestBed({
imports: [
TranslateModule.forRoot(),
@ -137,7 +144,7 @@ describe('ShareDialogComponent', () => {
expect(sharedLinksApiService.createSharedLinks).toHaveBeenCalled();
expect(renditionService.getNodeRendition).toHaveBeenCalled();
expect(fixture.nativeElement.querySelector('input[formcontrolname="sharedUrl"]').value).toBe('some-url/sharedId');
expect(fixture.nativeElement.querySelector('.mat-slide-toggle').classList).toContain('mat-checked');
expect(getShareToggleLinkedClasses()).toContain('mat-checked');
});
it(`should not toggle share action when file has 'sharedId' property`, async () => {
@ -160,7 +167,7 @@ describe('ShareDialogComponent', () => {
expect(sharedLinksApiService.createSharedLinks).not.toHaveBeenCalled();
expect(fixture.nativeElement.querySelector('input[formcontrolname="sharedUrl"]').value).toBe('some-url/sharedId');
expect(fixture.nativeElement.querySelector('.mat-slide-toggle').classList).toContain('mat-checked');
expect(getShareToggleLinkedClasses()).toContain('mat-checked');
});
it('should open a confirmation dialog when unshare button is triggered', () => {
@ -176,8 +183,7 @@ describe('ShareDialogComponent', () => {
fixture.detectChanges();
fixture.nativeElement.querySelector('.mat-slide-toggle label')
.dispatchEvent(new MouseEvent('click'));
clickShareToggleButton();
fixture.detectChanges();
@ -196,8 +202,7 @@ describe('ShareDialogComponent', () => {
fixture.detectChanges();
fixture.nativeElement.querySelector('.mat-slide-toggle label')
.dispatchEvent(new MouseEvent('click'));
clickShareToggleButton();
fixture.detectChanges();
@ -216,8 +221,7 @@ describe('ShareDialogComponent', () => {
fixture.detectChanges();
fixture.nativeElement.querySelector('.mat-slide-toggle label')
.dispatchEvent(new MouseEvent('click'));
clickShareToggleButton();
fixture.detectChanges();
@ -235,12 +239,13 @@ describe('ShareDialogComponent', () => {
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('.mat-slide-toggle').classList).toContain('mat-disabled');
expect(fixture.nativeElement.querySelector('input[formcontrolname="time"]').disabled).toBe(true);
expect(fixture.nativeElement.querySelector('mat-datetimepicker-toggle button').disabled).toBe(true);
expect(getShareToggleLinkedClasses()).toContain('mat-disabled');
});
it('should reset expiration date when toggle is unchecked', async () => {
it('should delete the current link generated with expiry date and generate a new link without expiry date when toggle is unchecked', async () => {
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of());
spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(of({}));
node.entry.properties['qshare:sharedId'] = 'sharedId';
node.entry.properties['qshare:sharedId'] = '2017-04-15T18:31:37+00:00';
node.entry.allowableOperations = ['update'];
@ -251,7 +256,7 @@ describe('ShareDialogComponent', () => {
fixture.detectChanges();
component.form.controls['time'].setValue(moment());
component.form.controls['time'].setValue(new Date());
fixture.detectChanges();
@ -263,13 +268,8 @@ describe('ShareDialogComponent', () => {
await fixture.whenStable();
expect(nodesApiService.updateNode).toHaveBeenCalledWith('nodeId', {
properties: {'qshare:expiryDate': null}
});
expect(
fixture.nativeElement.querySelector('input[formcontrolname="time"]').value
).toBe('');
expect(sharedLinksApiService.deleteSharedLink).toHaveBeenCalled();
expect(sharedLinksApiService.createSharedLinks).toHaveBeenCalledWith('nodeId', undefined);
});
it('should not allow expiration date action when node has no update permission', async () => {
@ -284,43 +284,18 @@ describe('ShareDialogComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
expect(fixture.nativeElement.querySelector('input[formcontrolname="time"]').disabled).toBe(true);
expect(fixture.nativeElement.querySelector('.mat-slide-toggle[data-automation-id="adf-expire-toggle"]')
.classList).toContain('mat-disabled');
.classList).toContain('mat-disabled');
expect(fixture.nativeElement.querySelector('[data-automation-id="adf-slide-toggle-checked"]').style.display).toEqual('none');
});
it('should update node expiration date with selected date', fakeAsync(() => {
const date = moment();
node.entry.allowableOperations = ['update'];
node.entry.properties['qshare:sharedId'] = 'sharedId';
fixture.componentInstance.form.controls['time'].setValue(null);
component.data = {
node,
baseShareUrl: 'some-url/'
};
fixture.detectChanges();
fixture.nativeElement
.querySelector(
'mat-slide-toggle[data-automation-id="adf-expire-toggle"] label'
)
.dispatchEvent(new MouseEvent('click'));
fixture.componentInstance.form.controls['time'].setValue(date);
fixture.detectChanges();
tick(100);
expect(nodesApiService.updateNode).toHaveBeenCalledWith('nodeId', {
properties: {'qshare:expiryDate': date.utc().format()}
});
}));
describe('datetimepicker type', () => {
beforeEach(() => {
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of(null));
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of());
spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(of({}));
node.entry.properties['qshare:sharedId'] = 'sharedId';
node.entry.allowableOperations = ['update'];
component.data = {
node,
@ -328,9 +303,9 @@ describe('ShareDialogComponent', () => {
};
});
it('it should update node with input date and end of day time when type is `date`', fakeAsync(() => {
it('should update node with input date and end of day time when type is `date`', fakeAsync(() => {
const dateTimePickerType = 'date';
const date = moment('2525-01-01 13:00:00');
const date = new Date('2525-01-01');
spyOn(appConfigService, 'get').and.callFake(() => dateTimePickerType as any);
fixture.detectChanges();
@ -341,26 +316,35 @@ describe('ShareDialogComponent', () => {
fixture.detectChanges();
tick(500);
expect(nodesApiService.updateNode).toHaveBeenCalledWith('nodeId', {
properties: {'qshare:expiryDate': date.endOf('day').utc().format()}
const expiryDate = format(endOfDay(date as Date), `yyyy-MM-dd'T'HH:mm:ss.SSSxx`);
expect(sharedLinksApiService.deleteSharedLink).toHaveBeenCalled();
expect(sharedLinksApiService.createSharedLinks).toHaveBeenCalledWith('nodeId', {
nodeId: 'nodeId',
expiresAt: expiryDate
});
}));
it('it should update node with input date and time when type is `datetime`', fakeAsync(() => {
it('should update node with input date and time when type is `datetime`', fakeAsync(() => {
const dateTimePickerType = 'datetime';
const date = moment('2525-01-01 13:00:00');
const date = new Date('2525-01-01 13:00:00');
spyOn(appConfigService, 'get').and.returnValue(dateTimePickerType);
fixture.detectChanges();
fixture.nativeElement.querySelector('mat-slide-toggle[data-automation-id="adf-expire-toggle"] label')
.dispatchEvent(new MouseEvent('click'));
fixture.componentInstance.type = 'datetime';
fixture.componentInstance.time.setValue(date);
fixture.detectChanges();
tick(100);
expect(nodesApiService.updateNode).toHaveBeenCalledWith('nodeId', {
properties: {'qshare:expiryDate': date.utc().format()}
const expiryDate = format((new Date(date)), `yyyy-MM-dd'T'HH:mm:ss.SSSxx`);
expect(sharedLinksApiService.deleteSharedLink).toHaveBeenCalled();
expect(sharedLinksApiService.createSharedLinks).toHaveBeenCalledWith('nodeId', {
nodeId: 'nodeId',
expiresAt: expiryDate
});
}));
});

View File

@ -23,23 +23,19 @@ import {
ViewChild,
OnDestroy
} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef, MatDialog } from '@angular/material/dialog';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { UntypedFormGroup, UntypedFormControl, AbstractControl } from '@angular/forms';
import { Observable, Subject } from 'rxjs';
import {
AppConfigService
} from '@alfresco/adf-core';
import { NodesApiService } from '../common/services/nodes-api.service';
import { Subject } from 'rxjs';
import { ContentService } from '../common/services/content.service';
import { SharedLinksApiService } from './services/shared-links-api.service';
import { SharedLinkEntry, Node } from '@alfresco/js-api';
import { SharedLinkBodyCreate, SharedLinkEntry } from '@alfresco/js-api';
import { ConfirmDialogComponent } from '../dialogs/confirm.dialog';
import moment from 'moment';
import { ContentNodeShareSettings } from './content-node-share.settings';
import { takeUntil, debounceTime } from 'rxjs/operators';
import { RenditionService } from '../common/services/rendition.service';
import { format, add, endOfDay } from 'date-fns';
type DatePickerType = 'date' | 'time' | 'month' | 'datetime';
@ -52,33 +48,33 @@ type DatePickerType = 'date' | 'time' | 'month' | 'datetime';
})
export class ShareDialogComponent implements OnInit, OnDestroy {
minDate = moment().add(1, 'd');
minDate = add(new Date(), { days: 1 });
sharedId: string;
fileName: string;
baseShareUrl: string;
isFileShared: boolean = false;
isDisabled: boolean = false;
isFileShared = false;
isDisabled = false;
isLinkWithExpiryDate = false;
form: UntypedFormGroup = new UntypedFormGroup({
sharedUrl: new UntypedFormControl(''),
time: new UntypedFormControl({value: '', disabled: true})
});
type: DatePickerType = 'datetime';
type: DatePickerType = 'date';
maxDebounceTime = 500;
isExpiryDateToggleChecked: boolean;
@ViewChild('slideToggleExpirationDate', {static: true})
slideToggleExpirationDate;
@ViewChild('dateTimePickerInput', {static: true})
dateTimePickerInput;
@ViewChild('datePickerInput', {static: true})
datePickerInput;
private onDestroy$ = new Subject<boolean>();
constructor(
private appConfigService: AppConfigService,
private sharedLinksApiService: SharedLinksApiService,
private dialogRef: MatDialogRef<ShareDialogComponent>,
private dialog: MatDialog,
private nodesApiService: NodesApiService,
private contentService: ContentService,
private renditionService: RenditionService,
@Inject(MAT_DIALOG_DATA) public data: ContentNodeShareSettings
@ -86,8 +82,6 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
}
ngOnInit() {
this.type = this.appConfigService.get<DatePickerType>('sharedLinkDateTimePickerType', 'datetime');
if (this.data.node && this.data.node.entry) {
this.fileName = this.data.node.entry.name;
this.baseShareUrl = this.data.baseShareUrl;
@ -99,7 +93,10 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
} else {
this.sharedId = properties['qshare:sharedId'];
this.isFileShared = true;
this.updateForm();
const expiryDate = this.updateForm();
this.isExpiryDateToggleChecked = this.isLinkWithExpiryDate = !!expiryDate;
this.isLinkWithExpiryDate ? this.time.enable() : this.time.disable();
}
}
@ -111,10 +108,8 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
.subscribe(value => this.onTimeChanged(value));
}
onTimeChanged(date: moment.Moment) {
this.updateNode(date).subscribe(
() => this.updateEntryExpiryDate(date)
);
onTimeChanged(date: Date) {
this.updateNode(date);
}
get time(): AbstractControl {
@ -147,15 +142,16 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
onToggleExpirationDate(slideToggle: MatSlideToggleChange) {
if (slideToggle.checked) {
this.time.enable();
this.isExpiryDateToggleChecked = true;
} else {
this.time.disable();
this.time.setValue(null);
this.deleteSharedLink(this.sharedId, true);
}
}
onDatetimepickerClosed() {
this.dateTimePickerInput.nativeElement.blur();
onDatePickerClosed() {
this.datePickerInput.nativeElement.blur();
if (!this.time.value) {
this.slideToggleExpirationDate.checked = false;
}
@ -185,10 +181,10 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
});
}
private createSharedLinks(nodeId: string) {
private createSharedLinks(nodeId: string, sharedLinkWithExpirySettings?: SharedLinkBodyCreate) {
this.isDisabled = true;
this.sharedLinksApiService.createSharedLinks(nodeId).subscribe(
this.sharedLinksApiService.createSharedLinks(nodeId, sharedLinkWithExpirySettings).subscribe(
(sharedLink: SharedLinkEntry) => {
if (sharedLink.entry) {
this.sharedId = sharedLink.entry.id;
@ -215,7 +211,7 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
);
}
deleteSharedLink(sharedId: string) {
deleteSharedLink(sharedId: string, dialogOpenFlag?: boolean) {
this.isDisabled = true;
this.sharedLinksApiService
@ -230,7 +226,13 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
this.data.node.entry.properties['qshare:sharedId'] = null;
this.data.node.entry.properties['qshare:expiryDate'] = null;
}
this.dialogRef.close(false);
if (dialogOpenFlag) {
this.createSharedLinks(this.data.node.entry.id);
this.isExpiryDateToggleChecked = false;
this.isLinkWithExpiryDate = false;
} else {
this.dialogRef.close(false);
}
}
}
);
@ -255,7 +257,7 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
});
}
private updateForm() {
private updateForm(): Date {
const {entry} = this.data.node;
let expiryDate = null;
@ -265,34 +267,55 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
this.form.setValue({
sharedUrl: `${this.baseShareUrl}${this.sharedId}`,
time: expiryDate ? moment(expiryDate).local() : null
});
time: expiryDate ? new Date(expiryDate) : null
}, { emitEvent: false });
if (expiryDate) {
this.time.enable();
return expiryDate;
}
private updateNode(date: Date) {
let expiryDate: Date | string;
if (date) {
if (this.type === 'date') {
expiryDate = format(endOfDay(new Date(date)), `yyyy-MM-dd'T'HH:mm:ss.SSSxx`);
} else {
expiryDate = format((new Date(date)), `yyyy-MM-dd'T'HH:mm:ss.SSSxx`);
}
} else {
this.time.disable();
expiryDate = null;
}
if (this.sharedId && expiryDate) {
this.isDisabled = true;
this.sharedLinksApiService.deleteSharedLink(this.sharedId).subscribe((response: any) => {
if (response instanceof Error) {
this.isDisabled = false;
this.isFileShared = true;
this.handleError(response);
} else {
this.sharedLinkWithExpirySettings(expiryDate as Date);
this.isLinkWithExpiryDate = true;
this.updateEntryExpiryDate(date);
}
});
}
}
private updateNode(date: moment.Moment): Observable<Node> {
const expiryDate = date
? (this.type === 'date' ? date.endOf('day').utc().format() : date.utc().format())
: null;
return this.nodesApiService.updateNode(this.data.node.entry.id, {
properties: {
'qshare:expiryDate': expiryDate
}
});
private sharedLinkWithExpirySettings(expiryDate: Date) {
const nodeObject: SharedLinkBodyCreate = {
nodeId: this.data.node.entry.id,
expiresAt: expiryDate as Date
};
this.createSharedLinks(this.data.node.entry.id, nodeObject);
}
private updateEntryExpiryDate(date: moment.Moment) {
private updateEntryExpiryDate(date: Date) {
const {properties} = this.data.node.entry;
if (properties) {
properties['qshare:expiryDate'] = date
? date.local()
? new Date(date)
: null;
}
}

View File

@ -16,7 +16,7 @@
*/
import { Injectable } from '@angular/core';
import { NodePaging, SharedLinkEntry, SharedlinksApi } from '@alfresco/js-api';
import { NodePaging, SharedLinkBodyCreate, SharedLinkEntry, SharedlinksApi } from '@alfresco/js-api';
import { Observable, from, of, Subject } from 'rxjs';
import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core';
import { catchError } from 'rxjs/operators';
@ -62,11 +62,12 @@ export class SharedLinksApiService {
* Creates a shared link available to the current user.
*
* @param nodeId ID of the node to link to
* @param sharedLinkWithExpirySettings shared link with nodeId and expiryDate
* @param options Options supported by JS-API
* @returns The shared link just created
*/
createSharedLinks(nodeId: string, options: any = {}): Observable<SharedLinkEntry> {
const promise = this.sharedLinksApi.createSharedLink({ nodeId }, options);
createSharedLinks(nodeId: string, sharedLinkWithExpirySettings?: SharedLinkBodyCreate, options: any = {}): Observable<SharedLinkEntry> {
const promise = this.sharedLinksApi.createSharedLink(sharedLinkWithExpirySettings? sharedLinkWithExpirySettings : { nodeId }, options);
return from(promise).pipe(
catchError((err) => of(err))

View File

@ -456,8 +456,14 @@
"DIALOG-TITLE": "Share",
"DESCRIPTION": "Click the link below to copy it to the clipboard.",
"TITLE": "Link to share",
"SHARE-LINK": "Share Link",
"SHARABLE-LINK-CREATED": "Sharable Link is Created",
"PUBLIC-CONTENT": "This content is publicly available to anyone with this link",
"LINK-WITH-EXPIRY-SETTINGS": "New link has been generated with expiry settings",
"EXPIRES": "Expires on",
"LINK-EXPIRY-DATE": "Link Expiry Date",
"EXPIRATION-LABEL" : "Expiration Date",
"EXPIRATION-PLACEHOLDER": "MM/DD/YYYY",
"CLIPBOARD-MESSAGE": "Link copied to the clipboard",
"CLOSE": "Close",
"COPY_BUTTON_LABEL": "Copy link",

View File

@ -21,8 +21,8 @@ import { BrowserActions } from '../../utils/browser-actions';
export class DateTimePickerCalendarPage {
datePicker = $(`.mat-datetimepicker-calendar`);
today = $(`.mat-datetimepicker-calendar-body-today`);
datePicker = $(`.mat-datepicker-calendar`);
today = $(`.mat-calendar-body-today`);
timePicker = $('.mat-datetimepicker-clock');
hourTime = $$('.mat-datetimepicker-clock-hours .mat-datetimepicker-clock-cell').first();
minutesTime = $$('.mat-datetimepicker-clock-minutes .mat-datetimepicker-clock-cell').first();
@ -47,7 +47,7 @@ export class DateTimePickerCalendarPage {
async setDate(date?: string): Promise<boolean> {
try {
if (date) {
await BrowserActions.clickScript(element.all(by.cssContainingText(`.mat-datetimepicker-calendar-body-cell-content`, date)).first());
await BrowserActions.clickScript(element.all(by.cssContainingText(`.mat-datepicker-calendar-body-cell-content`, date)).first());
} else {
await this.setToday();
}
@ -59,7 +59,7 @@ export class DateTimePickerCalendarPage {
}
async checkCalendarTodayDayIsDisabled(): Promise<void> {
await BrowserVisibility.waitUntilElementIsPresent(element(by.cssContainingText('.mat-datetimepicker-calendar-body-disabled', await BrowserActions.getText(this.today))));
await BrowserVisibility.waitUntilElementIsPresent(element(by.cssContainingText('.mat-calendar-body-cell-content', await BrowserActions.getText(this.today))));
}
async setDefaultEnabledHour(): Promise<void> {

View File

@ -23,12 +23,14 @@ export class DateTimePickerPage {
rootElement: ElementFinder;
dateTimePicker = $('.mat-datetimepicker-toggle');
datePicker = $('.mat-datepicker-toggle');
dateTime = new DateTimePickerCalendarPage();
constructor(rootElement?: ElementFinder) {
if (rootElement) {
this.rootElement = rootElement;
this.dateTimePicker = this.rootElement.$('.mat-datetimepicker-toggle');
this.datePicker = this.rootElement.$('.mat-datepicker-toggle');
}
}
@ -46,12 +48,12 @@ export class DateTimePickerPage {
}
async setDate(date?: string): Promise<boolean> {
await BrowserActions.click(this.dateTimePicker);
await BrowserActions.click(this.datePicker);
return this.dateTime.setDate(date);
}
async clickDateTimePicker(): Promise<void> {
await BrowserActions.click(this.dateTimePicker);
await BrowserActions.click(this.datePicker);
}
async checkCalendarTodayDayIsDisabled(): Promise<void> {

32
package-lock.json generated
View File

@ -36,6 +36,7 @@
"apollo-angular": "^4.2.1",
"chart.js": "2.9.4",
"cropperjs": "1.5.13",
"date-fns": "^2.30.0",
"dotenv-expand": "^5.1.0",
"editorjs-html": "3.4.2",
"editorjs-paragraph-with-alignment": "3.0.0",
@ -30973,6 +30974,37 @@
"node": ">=10"
}
},
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"dependencies": {
"@babel/runtime": "^7.21.0"
},
"engines": {
"node": ">=0.11"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/date-fns/node_modules/@babel/runtime": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz",
"integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/date-fns/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/date-format": {
"version": "4.0.14",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz",

View File

@ -80,6 +80,7 @@
"apollo-angular": "^4.2.1",
"chart.js": "2.9.4",
"cropperjs": "1.5.13",
"date-fns": "^2.30.0",
"dotenv-expand": "^5.1.0",
"editorjs-html": "3.4.2",
"editorjs-paragraph-with-alignment": "3.0.0",