diff --git a/app/adapters/external-redirect-service.ts b/app/adapters/external-redirect-service.ts
new file mode 100644
index 0000000000..dd26a4a91a
--- /dev/null
+++ b/app/adapters/external-redirect-service.ts
@@ -0,0 +1,10 @@
+import AddonServiceAdapter from './addon-service';
+
+export default class ExternalRedirectServiceAdapter extends AddonServiceAdapter {
+}
+
+declare module 'ember-data/types/registries/adapter' {
+ export default interface AdapterRegistry {
+ 'external-redirect-service': ExternalRedirectServiceAdapter;
+ } // eslint-disable-line semi
+}
diff --git a/app/guid-node/addons/index/template.hbs b/app/guid-node/addons/index/template.hbs
index 9705bfc09f..401e8a606a 100644
--- a/app/guid-node/addons/index/template.hbs
+++ b/app/guid-node/addons/index/template.hbs
@@ -27,9 +27,20 @@
data-analytics-scope='Addon - {{provider.provider.displayName}}'
>
{{#if (eq manager.pageMode 'terms')}}
-
+ {{#if manager.selectedProviderIsRedirectService}}
+
+ {{t 'addons.terms.redirect.disclaimer'}}
+
+
+
+ {{t 'addons.terms.redirect.pop-up-tooltip'}}
+
+
+ {{else}}
+
+ {{/if}}
diff --git a/app/models/external-redirect-service.ts b/app/models/external-redirect-service.ts
new file mode 100644
index 0000000000..06e8c280b7
--- /dev/null
+++ b/app/models/external-redirect-service.ts
@@ -0,0 +1,14 @@
+import { attr } from '@ember-data/model';
+
+import ExternalServiceModel from './external-service';
+
+export default class ExternalRedirectServiceModel extends ExternalServiceModel {
+ @attr('string') redirectUrl!: string;
+ // TODO: actually need some attrs here for redirect options
+}
+
+declare module 'ember-data/types/registries/model' {
+ export default interface ModelRegistry {
+ 'external-redirect-service': ExternalRedirectServiceModel;
+ } // eslint-disable-line semi
+}
diff --git a/app/packages/addons-service/provider.ts b/app/packages/addons-service/provider.ts
index 25e07b6d88..b7f7f23d84 100644
--- a/app/packages/addons-service/provider.ts
+++ b/app/packages/addons-service/provider.ts
@@ -1,5 +1,6 @@
import { getOwner, setOwner } from '@ember/application';
import EmberArray from '@ember/array';
+import { notifyPropertyChange } from '@ember/object';
import { inject as service } from '@ember/service';
import { waitFor } from '@ember/test-waiters';
import Store from '@ember-data/store';
@@ -23,7 +24,7 @@ import AuthorizedComputingAccountModel from 'ember-osf-web/models/authorized-com
import ExternalStorageServiceModel from 'ember-osf-web/models/external-storage-service';
import ExternalComputingServiceModel from 'ember-osf-web/models/external-computing-service';
import ExternalCitationServiceModel from 'ember-osf-web/models/external-citation-service';
-import { notifyPropertyChange } from '@ember/object';
+import ExternalRedirectServiceModel from 'ember-osf-web/models/external-redirect-service';
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
import ExternalLinkServiceModel from 'ember-osf-web/models/external-link-service';
import AuthorizedLinkAccountModel from 'ember-osf-web/models/authorized-link-account';
@@ -33,7 +34,8 @@ export type AllProviderTypes =
ExternalStorageServiceModel |
ExternalComputingServiceModel |
ExternalCitationServiceModel |
- ExternalLinkServiceModel;
+ ExternalLinkServiceModel |
+ ExternalRedirectServiceModel;
export type AllAuthorizedAccountTypes =
AuthorizedStorageAccountModel |
AuthorizedCitationAccountModel |
diff --git a/app/serializers/external-redirect-service.ts b/app/serializers/external-redirect-service.ts
new file mode 100644
index 0000000000..cbef16ffcd
--- /dev/null
+++ b/app/serializers/external-redirect-service.ts
@@ -0,0 +1,10 @@
+import GravyValetSerializer from './gravy-valet-serializer';
+
+export default class ExternalRedirectServiceSerializer extends GravyValetSerializer {
+}
+
+declare module 'ember-data/types/registries/serializer' {
+ export default interface SerializerRegistry {
+ 'external-redirect-service': ExternalRedirectServiceSerializer;
+ } // eslint-disable-line semi
+}
diff --git a/app/settings/addons/styles.scss b/app/settings/addons/styles.scss
index 2dac0a7a54..cde6d0160c 100644
--- a/app/settings/addons/styles.scss
+++ b/app/settings/addons/styles.scss
@@ -120,4 +120,9 @@
background-color: $bg-light;
color: var(--primary-color);
}
+
+ .disabled {
+ opacity: 0.5;
+ pointer-events: none;
+ }
}
diff --git a/app/settings/addons/template.hbs b/app/settings/addons/template.hbs
index d7a93cd4a6..789d5cbd5b 100644
--- a/app/settings/addons/template.hbs
+++ b/app/settings/addons/template.hbs
@@ -23,9 +23,20 @@
{{t 'addons.terms.heading' providerName=manager.selectedProvider.provider.displayName}}
-
+ {{#if manager.selectedProviderIsRedirectService}}
+
+ {{t 'addons.terms.redirect.disclaimer'}}
+
+
+
+ {{t 'addons.terms.redirect.pop-up-tooltip'}}
+
+
+ {{else}}
+
+ {{/if}}
@@ -94,14 +109,14 @@
{{/each}}
-
@@ -110,6 +125,8 @@
{{t 'addons.list.connected-accounts'}}
diff --git a/lib/osf-components/addon/components/addons-service/manager/component.ts b/lib/osf-components/addon/components/addons-service/manager/component.ts
index 4bbb936617..f1dec99a29 100644
--- a/lib/osf-components/addon/components/addons-service/manager/component.ts
+++ b/lib/osf-components/addon/components/addons-service/manager/component.ts
@@ -24,6 +24,7 @@ import AuthorizedStorageAccountModel from 'ember-osf-web/models/authorized-stora
import ConfiguredCitationAddonModel from 'ember-osf-web/models/configured-citation-addon';
import UserReferenceModel from 'ember-osf-web/models/user-reference';
import ConfiguredLinkAddonModel from 'ember-osf-web/models/configured-link-addon';
+import ExternalRedirectServiceModel from 'ember-osf-web/models/external-redirect-service';
interface FilterSpecificObject {
modelName: string;
@@ -47,6 +48,7 @@ export enum FilterTypes {
CITATION_MANAGER = 'citation-manager',
VERIFIED_LINK = 'verified-link',
// CLOUD_COMPUTING = 'cloud-computing', // disabled because BOA is down
+ REDIRECT_SERVICE = 'redirect-service',
}
interface Args {
@@ -92,6 +94,12 @@ export default class AddonsServiceManagerComponent extends Component {
// list: A([]),
// configuredAddons: A([]),
// },
+ [FilterTypes.REDIRECT_SERVICE]: {
+ modelName: 'external-redirect-service',
+ task: taskFor(this.getRedirectAddonProviders),
+ list: A([]),
+ // configuredAddons: A([]),
+ },
};
filterTypeMapper = new TrackedObject(this.mapper);
@tracked filterText = '';
@@ -152,6 +160,10 @@ export default class AddonsServiceManagerComponent extends Component {
return activeFilterObject.task.isRunning || taskFor(this.initialize).isRunning;
}
+ get selectedProviderIsRedirectService() {
+ return this.selectedProvider?.provider instanceof ExternalRedirectServiceModel;
+ }
+
@action
async configureProvider(provider: Provider, configuredAddon: AllConfiguredAddonTypes) {
this.cancelSetup();
@@ -204,6 +216,23 @@ export default class AddonsServiceManagerComponent extends Component {
@action
async acceptTerms() {
+ if (this.selectedProviderIsRedirectService) {
+ const openURL = new URL((this.selectedProvider!.provider as ExternalRedirectServiceModel).redirectUrl);
+ openURL.searchParams.set('nodeIri', this.node.links.iri!.toString());
+ openURL.searchParams.set('userIri', this.userReference!.userUri);
+ const newWindow = window.open(
+ openURL.toString(),
+ '_blank', 'popup,width=600,height=600,scrollbars=yes,resizable=yes',
+ );
+ if (newWindow) {
+ newWindow.focus();
+ } else {
+ this.toast.error(this.intl.t('addons.redirect.pop-up-error'));
+ }
+
+ this.cancelSetup();
+ return;
+ }
await taskFor(this.selectedProvider!.getAuthorizedAccounts).perform();
if(this.selectedProvider!.authorizedAccounts!.length > 0){
this.pageMode = PageMode.NEW_OR_EXISTING_ACCOUNT;
@@ -431,6 +460,17 @@ export default class AddonsServiceManagerComponent extends Component {
activeFilterObject.list = serviceCitationProviders.sort(this.providerSorter);
}
+ @task
+ @waitFor
+ async getRedirectAddonProviders() {
+ const activeFilterObject = this.filterTypeMapper[FilterTypes.REDIRECT_SERVICE];
+
+ const serviceRedirectProviders: Provider[] =
+ await taskFor(this.getExternalProviders)
+ .perform(activeFilterObject.modelName, activeFilterObject.configuredAddons);
+ activeFilterObject.list = serviceRedirectProviders.sort(this.providerSorter);
+ }
+
providerSorter(a: Provider, b: Provider) {
return a.provider.displayName.localeCompare(b.provider.displayName);
}
diff --git a/lib/osf-components/addon/components/addons-service/manager/template.hbs b/lib/osf-components/addon/components/addons-service/manager/template.hbs
index ab527a5258..6ebdb63255 100644
--- a/lib/osf-components/addon/components/addons-service/manager/template.hbs
+++ b/lib/osf-components/addon/components/addons-service/manager/template.hbs
@@ -10,6 +10,7 @@
currentListIsLoading=this.currentListIsLoading
projectEnabledAddons=this.projectEnabledAddons
selectedProvider=this.selectedProvider
+ selectedProviderIsRedirectService=this.selectedProviderIsRedirectService
configureProvider=this.configureProvider
beginAccountSetup=this.beginAccountSetup
acceptTerms=this.acceptTerms
diff --git a/lib/osf-components/addon/components/addons-service/user-addons-manager/component.ts b/lib/osf-components/addon/components/addons-service/user-addons-manager/component.ts
index fd8d630934..7ac40aa05c 100644
--- a/lib/osf-components/addon/components/addons-service/user-addons-manager/component.ts
+++ b/lib/osf-components/addon/components/addons-service/user-addons-manager/component.ts
@@ -20,6 +20,7 @@ import ExternalStorageServiceModel from 'ember-osf-web/models/external-storage-s
import ExternalComputingServiceModel from 'ember-osf-web/models/external-computing-service';
import ExternalCitationServiceModel from 'ember-osf-web/models/external-citation-service';
import ExternalLinkServiceModel from 'ember-osf-web/models/external-link-service';
+import ExternalRedirectServiceModel from 'ember-osf-web/models/external-redirect-service';
import UserModel from 'ember-osf-web/models/user';
import UserReferenceModel from 'ember-osf-web/models/user-reference';
@@ -52,6 +53,7 @@ export default class UserAddonManagerComponent extends Component {
user = this.args.user;
@tracked userReference!: UserReferenceModel;
@tracked tabIndex = 0;
+ @tracked connectAccountsTabDisabled = false;
possibleFilterTypes = Object.values(FilterTypes);
@tracked filterTypeMapper = {
@@ -87,6 +89,14 @@ export default class UserAddonManagerComponent extends Component {
// authorizedAccounts: [] as AuthorizedComputingAccountModel[],
// authorizedServiceIds: [] as string[],
// },
+ [FilterTypes.REDIRECT_SERVICE]: {
+ modelName: 'external-redirect-service',
+ fetchProvidersTask: taskFor(this.getRedirectAddonProviders),
+ list: A([]) as EmberArray,
+ getAuthorizedAccountsTask: taskFor(this.getAuthorizedRedirectAccounts),
+ authorizedAccounts: [] as AllAuthorizedAccountTypes[],
+ authorizedServiceIds: [] as string[],
+ },
};
@tracked filterText = '';
@tracked activeFilterType = FilterTypes.STORAGE;
@@ -107,6 +117,11 @@ export default class UserAddonManagerComponent extends Component {
this.filterText = '';
}
this.activeFilterType = type;
+ this.connectAccountsTabDisabled = false;
+ if (this.activeFilterType === FilterTypes.REDIRECT_SERVICE) {
+ this.changeTab(0);
+ this.connectAccountsTabDisabled = true;
+ }
const activeFilterObject = this.filterTypeMapper[type];
if (activeFilterObject.list.length === 0) {
activeFilterObject.fetchProvidersTask.perform();
@@ -143,6 +158,10 @@ export default class UserAddonManagerComponent extends Component {
return activeFilterObject.fetchProvidersTask.isRunning;
}
+ get selectedProviderIsRedirectService() {
+ return this.selectedProvider?.provider instanceof ExternalRedirectServiceModel;
+ }
+
@action
connectNewProviderAccount(provider: Provider) {
this.pageMode = UserSettingPageModes.TERMS;
@@ -151,6 +170,22 @@ export default class UserAddonManagerComponent extends Component {
@action
acceptProviderTerms() {
+ if (this.selectedProviderIsRedirectService) {
+ const openURL = new URL((this.selectedProvider!.provider as ExternalRedirectServiceModel).redirectUrl);
+ openURL.searchParams.set('userIri', this.userReference!.userUri);
+ const newWindow = window.open(
+ openURL.toString(),
+ '_blank', 'popup,width=600,height=600,scrollbars=yes,resizable=yes',
+ );
+ if (newWindow) {
+ newWindow.focus();
+ } else {
+ this.toast.error(this.intl.t('addons.redirect.pop-up-error'));
+ }
+
+ this.cancelSetup();
+ return;
+ }
this.pageMode = UserSettingPageModes.ACCOUNT_CREATE;
}
@@ -258,6 +293,16 @@ export default class UserAddonManagerComponent extends Component {
notifyPropertyChange(this, 'filterTypeMapper');
}
+ @task
+ @waitFor
+ async getAuthorizedRedirectAccounts() {
+ // Redirect services do not have authorized accounts
+ const mappedObject = this.filterTypeMapper[FilterTypes.REDIRECT_SERVICE];
+ mappedObject.authorizedAccounts = [] as AllAuthorizedAccountTypes[];
+ mappedObject.authorizedServiceIds = [];
+ notifyPropertyChange(this, 'filterTypeMapper');
+ }
+
@task
@waitFor
async getAuthorizedAccounts() {
@@ -334,6 +379,23 @@ export default class UserAddonManagerComponent extends Component {
));
}
+ @task
+ @waitFor
+ async getRedirectAddonProviders() {
+ const activeFilterObject = this.filterTypeMapper[FilterTypes.REDIRECT_SERVICE];
+ const serviceRedirectProviders = await taskFor(this.getExternalProviders)
+ .perform(activeFilterObject.modelName) as ExternalRedirectServiceModel[];
+ activeFilterObject.list = serviceRedirectProviders.sort(this.providerSorter)
+ .map(provider => new Provider(
+ provider,
+ this.currentUser,
+ undefined,
+ undefined,
+ undefined,
+ this.userReference,
+ ));
+ }
+
@task
@waitFor
async getAddonProviders() {
diff --git a/lib/osf-components/addon/components/addons-service/user-addons-manager/template.hbs b/lib/osf-components/addon/components/addons-service/user-addons-manager/template.hbs
index 7f1888a41b..922637d64c 100644
--- a/lib/osf-components/addon/components/addons-service/user-addons-manager/template.hbs
+++ b/lib/osf-components/addon/components/addons-service/user-addons-manager/template.hbs
@@ -10,8 +10,10 @@
currentTypeAuthorizedServiceIds=this.currentTypeAuthorizedServiceIds
pageMode=this.pageMode
tabIndex=this.tabIndex
+ connectAccountsTabDisabled=this.connectAccountsTabDisabled
changeTab=this.changeTab
selectedProvider=this.selectedProvider
+ selectedProviderIsRedirectService=this.selectedProviderIsRedirectService
selectedAccount=this.selectedAccount
connectNewProviderAccount=this.connectNewProviderAccount
acceptProviderTerms=this.acceptProviderTerms
diff --git a/mirage/config.ts b/mirage/config.ts
index 5ae2c92c20..b46133b243 100644
--- a/mirage/config.ts
+++ b/mirage/config.ts
@@ -572,6 +572,8 @@ export default function(this: Server) {
this.resource('external-storage-services', { only: ['index', 'show'] });
this.resource('external-citation-services', { only: ['index', 'show'] });
this.resource('external-computing-services', { only: ['index', 'show'] });
+ this.resource('external-redirect-services', { only: ['index', 'show'] });
+ this.resource('external-link-services', { only: ['index', 'show'] });
this.resource('user-references', { only: ['index', 'show'] });
this.get('/user-references/:userGuid/authorized_storage_accounts/',
addons.userReferenceAuthorizedStorageAccountList);
diff --git a/mirage/fixtures/external-link-service.ts b/mirage/fixtures/external-link-service.ts
new file mode 100644
index 0000000000..fa3b10baf7
--- /dev/null
+++ b/mirage/fixtures/external-link-service.ts
@@ -0,0 +1,9 @@
+import { CredentialsFormat } from 'ember-osf-web/models/external-service';
+export default [
+ {
+ id: 'dataverse',
+ displayName: 'Dataverse',
+ credentialsFormat: CredentialsFormat.OAUTH2,
+ iconUrl: 'https://dataverse.org/logo.png',
+ },
+];
diff --git a/mirage/fixtures/external-redirect-service.ts b/mirage/fixtures/external-redirect-service.ts
new file mode 100644
index 0000000000..0da2f3c870
--- /dev/null
+++ b/mirage/fixtures/external-redirect-service.ts
@@ -0,0 +1,11 @@
+/* eslint-disable max-len */
+import { CredentialsFormat } from 'ember-osf-web/models/external-service';
+export default [
+ {
+ id: 'datapipe',
+ displayName: 'DataPipe',
+ credentialsFormat: CredentialsFormat.OAUTH2,
+ redirectUrl: 'https://pipe.jspsych.org/',
+ iconUrl: 'https://pipe.jspsych.org/logo.png',
+ },
+];
diff --git a/mirage/scenarios/default.ts b/mirage/scenarios/default.ts
index db16a251b8..d2f43e1470 100644
--- a/mirage/scenarios/default.ts
+++ b/mirage/scenarios/default.ts
@@ -31,6 +31,8 @@ export default function(server: Server) {
server.loadFixtures('external-storage-services');
server.loadFixtures('external-computing-services');
server.loadFixtures('external-citation-services');
+ // server.loadFixtures('external-link-services');
+ server.loadFixtures('external-redirect-services');
// server.loadFixtures('registration-providers');
// load citations for preprints, registrations, or manyProjectRegistrations
diff --git a/mirage/serializers/external-redirect-service.ts b/mirage/serializers/external-redirect-service.ts
new file mode 100644
index 0000000000..15a1e342eb
--- /dev/null
+++ b/mirage/serializers/external-redirect-service.ts
@@ -0,0 +1,6 @@
+import ExternalRedirectServiceModel from 'ember-osf-web/models/external-redirect-service';
+
+import AddonServiceSerializer from './addon-service';
+
+export default class ConfiguredRedirectAddonSerializer extends AddonServiceSerializer {
+}
diff --git a/tests/acceptance/guid-node/addons-test.ts b/tests/acceptance/guid-node/addons-test.ts
index c9728956a8..808dcfd0dc 100644
--- a/tests/acceptance/guid-node/addons-test.ts
+++ b/tests/acceptance/guid-node/addons-test.ts
@@ -45,7 +45,7 @@ module('Acceptance | guid-node/addons', hooks => {
.hasAttribute('aria-selected', 'true', 'All addons tab is selected');
// check additonal storage providers
- assert.dom('[data-test-addon-list-filter]').exists({ count: 3 }, '3 addon filters are present');
+ assert.dom('[data-test-addon-list-filter]').exists({ count: 4 }, '3 addon filters are present');
assert.dom('[data-test-addon-list-filter="additional-storage"]')
.hasClass(styles.active, 'Additional storage filter is active');
assert.dom('[data-test-addon-card-connect]').exists({ count: 9 }, '9 storage addons are present');
diff --git a/translations/en-us.yml b/translations/en-us.yml
index e568ed6c76..72e3979a1f 100644
--- a/translations/en-us.yml
+++ b/translations/en-us.yml
@@ -259,6 +259,7 @@ addons:
citation-manager: 'Citation Manager'
cloud-computing: 'Cloud Computing'
verified-link: 'Linked Services'
+ redirect-service: 'Other Services'
no-results: 'No results found'
no-connected-accounts: 'No connected accounts'
connect: 'Connect'
@@ -271,6 +272,7 @@ addons:
terms:
heading: '{providerName} Terms'
accepting: 'Accepting terms…'
+ go-to-service: 'Go to {providerName}'
table-headings:
function: 'Function'
status: 'Status'
@@ -318,6 +320,10 @@ addons:
permissions-true: 'The OSF does not change permissions for linked {provider} files. Privacy changes made to an OSF project or component will not affect those set in {provider}.'
registering-false: '{provider} content will not be registered.'
file-versions-false: '{provider} files can be viewed/downloaded outside OSF only. Version history is not supported.'
+ redirect:
+ disclaimer: "Clicking the button below will redirect you outside of OSF. You will need to follow that service's permissions to continue."
+ pop-up-tooltip: 'Tip: If the page does not open, try disabling your pop-up blocker.'
+ pop-up-error: 'Unable to open pop-up window. Please disable your pop-up blocker and try again.'
accountSelect:
heading: 'Log in to {providerName} or select an account'
no-accounts: 'No accounts available'
@@ -368,7 +374,7 @@ addons:
verify: 'Connect the following account:'
authorizing: 'Connecting…'
configure:
- google-file-picker:
+ google-file-picker:
select-root-folder: 'Select Root Folder'
selected-folder: 'Selected Folder'
root-folder-title: 'Select a root folder'