Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ <h2>
</h2>

<div class="mt-4">
<osf-addon-terms [addon]="addon()" />
<osf-addon-terms [addon]="addon()" [redirectUrl]="redirectUrl()" />
</div>

<div class="flex flex-column gap-2 mt-4">
Expand All @@ -33,17 +33,29 @@ <h2>
[routerLink]="[baseUrl() + '/addons']"
data-test-addon-cancel-button
></p-button>
<p-button
[label]="
!isAuthorizedAddonsLoading()
? ('settings.addons.form.buttons.next' | translate)
: ('settings.addons.form.buttons.acceptingTerms' | translate)
"
class="w-10rem btn-full-width"
[loading]="isAuthorizedAddonsLoading()"
(onClick)="handleAuthorizedAccountsPresenceCheck()"
data-test-addon-terms-confirm-button
></p-button>
@if (addonTypeString() === AddonType.REDIRECT) {
<p-button
[label]="
'settings.addons.connectAddon.redirectAddons.goToService'
| translate: { serviceName: addon()?.displayName }
"
class="w-10rem btn-full-width"
(onClick)="goToService()"
data-test-addon-redirect-button
/>
} @else {
<p-button
[label]="
!isAuthorizedAddonsLoading()
? ('settings.addons.form.buttons.next' | translate)
: ('settings.addons.form.buttons.acceptingTerms' | translate)
"
class="w-10rem btn-full-width"
[loading]="isAuthorizedAddonsLoading()"
(onClick)="handleAuthorizedAccountsPresenceCheck()"
data-test-addon-terms-confirm-button
></p-button>
}
</div>
</section>
</ng-template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,17 @@ export class ConnectConfiguredAddonComponent {

addonTypeString = computed(() => getAddonTypeString(this.addon()));

redirectUrl = computed(() => {
const addon = this.addon();
if (!addon || !addon.redirectUrl) {
return null;
}
const openURL = new URL(addon.redirectUrl);
openURL.searchParams.set('nodeIri', this.resourceUri());
openURL.searchParams.set('userIri', this.addonsUserReference()[0]?.attributes.user_uri);
return openURL.toString();
});

readonly baseUrl = computed(() => {
const currentUrl = this.router.url;
return currentUrl.split('/addons')[0];
Expand Down Expand Up @@ -253,6 +264,22 @@ export class ConnectConfiguredAddonComponent {
});
}

goToService() {
if (!this.redirectUrl()) return;

const newWindow = window.open(
this.redirectUrl()!.toString(),
'_blank',
'popup,width=600,height=600,scrollbars=yes,resizable=yes'
);
if (newWindow) {
this.router.navigate([`${this.baseUrl()}/addons`]);
newWindow.focus();
} else {
this.toastService.showError('addons.redirect.popUpError');
}
}

private getDataForAccountCheck() {
const addonType = this.addonTypeString();
const referenceId = this.userReferenceId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
GetConfiguredLinkAddons,
GetConfiguredStorageAddons,
GetLinkAddons,
GetRedirectAddons,
GetStorageAddons,
} from '@shared/stores/addons';
import { CurrentResourceSelectors } from '@shared/stores/current-resource';
Expand Down Expand Up @@ -86,6 +87,7 @@ export class ProjectAddonsComponent implements OnInit {
storageAddons = select(AddonsSelectors.getStorageAddons);
citationAddons = select(AddonsSelectors.getCitationAddons);
linkAddons = select(AddonsSelectors.getLinkAddons);
redirectAddons = select(AddonsSelectors.getRedirectAddons);
configuredStorageAddons = select(AddonsSelectors.getConfiguredStorageAddons);
configuredCitationAddons = select(AddonsSelectors.getConfiguredCitationAddons);
configuredLinkAddons = select(AddonsSelectors.getConfiguredLinkAddons);
Expand All @@ -95,6 +97,8 @@ export class ProjectAddonsComponent implements OnInit {
isResourceReferenceLoading = select(AddonsSelectors.getAddonsResourceReferenceLoading);
isStorageAddonsLoading = select(AddonsSelectors.getStorageAddonsLoading);
isCitationAddonsLoading = select(AddonsSelectors.getCitationAddonsLoading);
isLinkAddonsLoading = select(AddonsSelectors.getLinkAddonsLoading);
isRedirectAddonsLoading = select(AddonsSelectors.getRedirectAddonsLoading);
isConfiguredStorageAddonsLoading = select(AddonsSelectors.getConfiguredStorageAddonsLoading);
isConfiguredCitationAddonsLoading = select(AddonsSelectors.getConfiguredCitationAddonsLoading);
isConfiguredLinkAddonsLoading = select(AddonsSelectors.getConfiguredLinkAddonsLoading);
Expand All @@ -103,6 +107,7 @@ export class ProjectAddonsComponent implements OnInit {
this.isStorageAddonsLoading() ||
this.isCitationAddonsLoading() ||
this.isLinkAddonsLoading() ||
this.isRedirectAddonsLoading() ||
this.isUserReferenceLoading() ||
this.isCurrentUserLoading()
);
Expand Down Expand Up @@ -130,8 +135,6 @@ export class ProjectAddonsComponent implements OnInit {
return categoryLoading || this.isResourceReferenceLoading() || this.isCurrentUserLoading();
});

isLinkAddonsLoading = select(AddonsSelectors.getLinkAddonsLoading);

currentAddonsLoading = computed(() => {
switch (this.selectedCategory()) {
case AddonCategory.EXTERNAL_STORAGE_SERVICES:
Expand All @@ -140,6 +143,8 @@ export class ProjectAddonsComponent implements OnInit {
return this.isCitationAddonsLoading();
case AddonCategory.EXTERNAL_LINK_SERVICES:
return this.isLinkAddonsLoading();
case AddonCategory.EXTERNAL_REDIRECT_SERVICES:
return this.isRedirectAddonsLoading();
default:
return this.isStorageAddonsLoading();
}
Expand All @@ -153,6 +158,7 @@ export class ProjectAddonsComponent implements OnInit {
getStorageAddons: GetStorageAddons,
getCitationAddons: GetCitationAddons,
getLinkAddons: GetLinkAddons,
getRedirectAddons: GetRedirectAddons,
getConfiguredStorageAddons: GetConfiguredStorageAddons,
getConfiguredCitationAddons: GetConfiguredCitationAddons,
getConfiguredLinkAddons: GetConfiguredLinkAddons,
Expand Down Expand Up @@ -216,6 +222,8 @@ export class ProjectAddonsComponent implements OnInit {
return this.actions.getCitationAddons;
case AddonCategory.EXTERNAL_LINK_SERVICES:
return this.actions.getLinkAddons;
case AddonCategory.EXTERNAL_REDIRECT_SERVICES:
return this.actions.getRedirectAddons;
default:
return this.actions.getStorageAddons;
}
Expand All @@ -229,6 +237,8 @@ export class ProjectAddonsComponent implements OnInit {
return this.citationAddons();
case AddonCategory.EXTERNAL_LINK_SERVICES:
return this.linkAddons();
case AddonCategory.EXTERNAL_REDIRECT_SERVICES:
return this.redirectAddons();
default:
return this.storageAddons();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
<p-table [value]="terms()" class="addon-table">
<ng-template pTemplate="header">
<tr>
<th>
{{ 'settings.addons.connectAddon.table.function' | translate }}
</th>
<th>
{{ 'settings.addons.connectAddon.table.status' | translate }}
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-term>
<tr
[ngClass]="{
'background-warning': term.type === 'warning',
'background-success': term.type === 'info',
'background-danger': term.type === 'danger',
}"
>
<td>{{ term.function }}</td>
<td>{{ term.status }}</td>
</tr>
</ng-template>
</p-table>
@if (isRedirectService()) {
<p class="mb-4">
{{ 'settings.addons.connectAddon.redirectAddons.terms' | translate }}
</p>
<em
class="ml-2"
[innerHTML]="
'settings.addons.connectAddon.redirectAddons.tip'
| translate: { serviceName: addon()?.displayName, serviceUrl: redirectUrl() }
"
></em>
} @else {
<p-table [value]="terms()" class="addon-table">
<ng-template pTemplate="header">
<tr>
<th>
{{ 'settings.addons.connectAddon.table.function' | translate }}
</th>
<th>
{{ 'settings.addons.connectAddon.table.status' | translate }}
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-term>
<tr
[ngClass]="{
'background-warning': term.type === 'warning',
'background-success': term.type === 'info',
'background-danger': term.type === 'danger',
}"
>
<td>{{ term.function }}</td>
<td>{{ term.status }}</td>
</tr>
</ng-template>
</p-table>
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { isCitationAddon } from '@osf/shared/helpers';
import { isCitationAddon, isRedirectAddon } from '@osf/shared/helpers';
import { AddonTermsComponent } from '@shared/components/addons';
import { ADDON_TERMS } from '@shared/constants';
import { AddonModel, AddonTerm } from '@shared/models';
Expand All @@ -10,12 +10,14 @@ import { OSFTestingModule } from '@testing/osf.testing.module';

jest.mock('@shared/helpers', () => ({
isCitationAddon: jest.fn(),
isRedirectAddon: jest.fn(),
}));

describe('AddonTermsComponent', () => {
let component: AddonTermsComponent;
let fixture: ComponentFixture<AddonTermsComponent>;
const mockIsCitationAddon = isCitationAddon as jest.MockedFunction<typeof isCitationAddon>;
const mockIsRedirectAddon = isRedirectAddon as jest.MockedFunction<typeof isRedirectAddon>;
const mockAddon: AddonModel = MOCK_ADDON;

beforeEach(async () => {
Expand All @@ -27,6 +29,7 @@ describe('AddonTermsComponent', () => {
component = fixture.componentInstance;

mockIsCitationAddon.mockReturnValue(false);
mockIsRedirectAddon.mockReturnValue(false);
});

it('should create', () => {
Expand Down Expand Up @@ -210,4 +213,24 @@ describe('AddonTermsComponent', () => {

expect(hasInfoTerm || hasWarningTerm || hasDangerTerm).toBe(true);
});

it('should handle redirect terms correctly', () => {
const redirectAddon: AddonModel = {
...mockAddon,
type: 'redirect',
};

mockIsRedirectAddon.mockReturnValue(true);
fixture.componentRef.setInput('addon', redirectAddon);
fixture.detectChanges();

const terms = component.terms();
expect(terms).toEqual([]);

const termsElement: HTMLElement = fixture.nativeElement;
expect(termsElement.querySelectorAll('tr').length).toBe(0);
expect(termsElement.querySelectorAll('p').length).toBe(1);
expect(termsElement.querySelectorAll('em').length).toBe(1);
expect(termsElement.textContent).toContain('settings.addons.connectAddon.redirectAddons.terms');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { TableModule } from 'primeng/table';
import { NgClass } from '@angular/common';
import { Component, computed, input } from '@angular/core';

import { isCitationAddon } from '@osf/shared/helpers';
import { isCitationAddon, isRedirectAddon } from '@osf/shared/helpers';
import { ADDON_TERMS as addonTerms } from '@shared/constants';
import { AddonModel, AddonTerm, AuthorizedAccountModel } from '@shared/models';

Expand All @@ -17,6 +17,9 @@ import { AddonModel, AddonTerm, AuthorizedAccountModel } from '@shared/models';
})
export class AddonTermsComponent {
addon = input<AddonModel | AuthorizedAccountModel | null>(null);
redirectUrl = input<string | null>(null);

isRedirectService = computed(() => isRedirectAddon(this.addon()));
terms = computed(() => {
const addon = this.addon();
if (!addon) {
Expand All @@ -29,6 +32,9 @@ export class AddonTermsComponent {
const supportedFeatures = addon.supportedFeatures || [];
const provider = addon.providerName;
const isCitationService = isCitationAddon(addon);
if (isRedirectAddon(addon)) {
return [];
}

const relevantTerms = isCitationService ? addonTerms.filter((term) => term.citation) : addonTerms;

Expand Down
4 changes: 4 additions & 0 deletions src/app/shared/constants/addons-category-options.const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ export const ADDON_CATEGORY_OPTIONS: SelectOption[] = [
label: 'settings.addons.categories.linkedServices',
value: AddonCategory.EXTERNAL_LINK_SERVICES,
},
{
label: 'settings.addons.categories.otherServices',
value: AddonCategory.EXTERNAL_REDIRECT_SERVICES,
},
];
1 change: 1 addition & 0 deletions src/app/shared/enums/addon-type.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export enum AddonType {
STORAGE = 'storage',
CITATION = 'citation',
LINK = 'link',
REDIRECT = 'redirect', // Redirect addons will not have authorized accounts or configured addons
}

export enum AuthorizedAccountType {
Expand Down
1 change: 1 addition & 0 deletions src/app/shared/enums/addons-category.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum AddonCategory {
EXTERNAL_STORAGE_SERVICES = 'external-storage-services',
EXTERNAL_CITATION_SERVICES = 'external-citation-services',
EXTERNAL_LINK_SERVICES = 'external-link-services',
EXTERNAL_REDIRECT_SERVICES = 'external-redirect-services',
}
6 changes: 6 additions & 0 deletions src/app/shared/helpers/addon-type.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export function isLinkAddon(addon: AddonModel | AuthorizedAccountModel | Configu
);
}

export function isRedirectAddon(addon: AddonModel | AuthorizedAccountModel | ConfiguredAddonModel | null): boolean {
if (!addon) return false;

return addon.type === AddonCategory.EXTERNAL_REDIRECT_SERVICES;
}

export function getAddonTypeString(addon: AddonModel | AuthorizedAccountModel | ConfiguredAddonModel | null): string {
if (!addon) return '';

Expand Down
1 change: 1 addition & 0 deletions src/app/shared/mappers/addon.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class AddonMapper {
credentialsFormat: response.attributes.credentials_format,
providerName: response.attributes.display_name,
iconUrl: response.attributes.icon_url,
redirectUrl: response.attributes.redirect_url,
};
}

Expand Down
1 change: 1 addition & 0 deletions src/app/shared/models/addons/addon-json-api.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface AddonGetResponseJsonApi {
credentials_format: string;
wb_key: string;
icon_url: string;
redirect_url?: string;
[key: string]: unknown;
};
relationships: {
Expand Down
1 change: 1 addition & 0 deletions src/app/shared/models/addons/addon.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export interface AddonModel {
credentialsAvailable?: boolean;
supportedResourceTypes?: string[];
wbKey?: string;
redirectUrl?: string;
}
4 changes: 4 additions & 0 deletions src/app/shared/stores/addons/addons.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export class GetLinkAddons {
static readonly type = '[Addons] Get Link Addons';
}

export class GetRedirectAddons {
static readonly type = '[Addons] Get Other Addons';
}

export class GetAuthorizedStorageAddons {
static readonly type = '[Addons] Get Authorized Storage Addons';

Expand Down
Loading
Loading