Skip to content

Commit

Permalink
pkp/pkp-lib#9771 Move ORCID functionality into core application
Browse files Browse the repository at this point in the history
  • Loading branch information
ewhanson committed May 22, 2024
1 parent ec72d49 commit 2aefe54
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/components/Container/AccessPage.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<script type="text/javascript">
import Page from './Page.vue';
import NotifyUsersForm from '@/components/Form/context/NotifyUsersForm.vue';
import OrcidSettingsForm from '@/components/Form/context/OrcidSettingsForm.vue';
export default {
name: 'AccessPage',
components: {
NotifyUsersForm,
OrcidSettingsForm,
},
extends: Page,
data() {
Expand Down
2 changes: 2 additions & 0 deletions src/components/Container/AdminPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import HighlightsListPanel from '../ListPanel/highlights/HighlightsListPanel.vue
import ThemeForm from '@/components/Form/context/ThemeForm.vue';
import ActionPanel from '../ActionPanel/ActionPanel.vue';
import AnnouncementsListPanel from '../ListPanel/announcements/AnnouncementsListPanel.vue';
import OrcidSettingsForm from '@/components/Form/context/OrcidSettingsForm.vue';
export default {
name: 'AdminPage',
Expand All @@ -12,6 +13,7 @@ export default {
HighlightsListPanel,
AnnouncementsListPanel,
ThemeForm,
OrcidSettingsForm,
},
extends: Page,
data() {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Form/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export default {
submitValues() {
let values = {};
this.fields.forEach((field) => {
if (field.component === 'field-html') {
if (field.isInert) {
return;
}
if (!field.isMultilingual) {
Expand Down
2 changes: 2 additions & 0 deletions src/components/Form/FormGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import FieldPubId from './fields/FieldPubId.vue';
import FieldHtml from './fields/FieldHtml.vue';
import FieldMetadataSetting from './fields/FieldMetadataSetting.vue';
import FieldOptions from './fields/FieldOptions.vue';
import FieldOrcid from './fields/FieldOrcid.vue';
import FieldPreparedContent from './fields/FieldPreparedContent.vue';
import FieldRadioInput from './fields/FieldRadioInput.vue';
import FieldRichTextarea from './fields/FieldRichTextarea.vue';
Expand Down Expand Up @@ -90,6 +91,7 @@ export default {
FieldHtml,
FieldMetadataSetting,
FieldOptions,
FieldOrcid,
FieldPreparedContent,
FieldRadioInput,
FieldRichTextarea,
Expand Down
28 changes: 28 additions & 0 deletions src/components/Form/context/OrcidSettingsForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script>
import Form from '../Form.vue';
export default {
name: 'OrcidSettingsForm',
extends: Form,
props: {},
methods: {
/**
* Update values when a field has changed
*
* @param {String} name Name of the field to modify
* @param {String} prop Name of the prop to modify
* @param {mixed} value The new value for the prop
* @param {String} localeKey Optional locale key for multilingual props
*/
fieldChanged: function (name, prop, value, localeKey) {
// TODO: This isn't actually working as the fields are properly required.
if (name === 'orcidEnabled') {
this.removeError('orcidApiType', localeKey);
this.removeError('orcidClientId', localeKey);
this.removeError('orcidClientSecret', localeKey);
}
Form.methods.fieldChanged.apply(this, [name, prop, value, localeKey]);
},
},
};
</script>
7 changes: 7 additions & 0 deletions src/components/Form/fields/FieldBase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ export default {
groupId: String,
/** The ID of the form this field should appear in. This is passed down from the `Form`. */
formId: String,
/** Whether the field should be ignored when a form is submitted (e.g. purely informational field). */
isInert: {
type: Boolean,
default() {
return false;
},
},
/** Whether or not this field should be presented for each supported language. */
isMultilingual: Boolean,
/** Whether or not a value for this field should be required. */
Expand Down
14 changes: 14 additions & 0 deletions src/components/Form/fields/FieldOrcid.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {Primary, Controls, Meta} from '@storybook/blocks';

import * as FieldOrcidStories from './FieldOrcid.stories.js';

<Meta of={FieldOrcidStories} />{' '}

# FieldOrcid

## Usage

Field used for managing a linked user/author's ORCID

<Primary />
<Controls />
48 changes: 48 additions & 0 deletions src/components/Form/fields/FieldOrcid.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import FieldOrcid from '@/components/Form/fields/FieldOrcid.vue';
import FieldBaseMock from '../mocks/field-base';
import FieldOrcidMock from '../mocks/field-orcid';
import {http, HttpResponse} from 'msw';

export default {
title: 'Forms/FieldOrcid',
component: FieldOrcid,
render: (args) => ({
components: {FieldOrcid},
setup() {
function change(name, prop, newValue, localeKey) {
if (localeKey) {
args[prop][localeKey] = newValue;
} else {
args[prop] = newValue;
}
}

return {args, change};
},
template: `
<FieldOrcid v-bind="args" @change="change" />
`,
}),
parameters: {
msw: {
handlers: [
http.post(
'https://mock/index.php/publicknowledge/api/v1/orcid/requestAuthorVerification/1',
async () => {
return HttpResponse.json();
},
),
http.post(
'https://mock/index.php/publicknowledge/api/v1/orcid/deleteForAuthor/1',
async () => {
return HttpResponse.json();
},
),
],
},
},
};

export const Base = {
args: {...FieldBaseMock, ...FieldOrcidMock},
};
230 changes: 230 additions & 0 deletions src/components/Form/fields/FieldOrcid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
<template>
<div class="pkpFormField pkpFormField--html">
<div class="pkpFormField__heading">
<span class="pkpFormFieldLabel">
{{ label }}
</span>
<tooltip v-if="tooltip" aria-hidden="true" :tooltip="tooltip" label="" />
<span v-if="tooltip" class="-screenReader" v-html="tooltip" />
<help-button
v-if="helpTopic"
:topic="helpTopic"
:section="helpSection"
:label="t('help.help')"
/>
</div>
<!-- When ORCID is present -->
<div
v-if="hasOrcid"
class="pkpFormField__control pkpFormField__control--html"
v-html="orcidValue"
/>
<pkp-button
v-if="hasOrcid"
class="pkpFormField__control--html__button"
:is-warnable="true"
:is-disabled="isButtonDisabled"
@click="openDeleteDialog"
>
{{ t('common.delete') }}
</pkp-button>
<!-- When ORCID is absent -->
<pkp-button
v-if="!hasOrcid"
:disabled="verificationRequested || isButtonDisabled"
:icon="verificationRequested ? 'Complete' : null"
@click="openSendAuthorEmailDialog"
>
{{
verificationRequested
? t('orcid.field.verification.requested')
: t('orcid.field.verification.request')
}}
</pkp-button>
</div>
</template>

<script>
import FieldBase from '@/components/Form/fields/FieldBase.vue';
import {useApiUrl} from '@/composables/useApiUrl';
import {useFetch} from '@/composables/useFetch';
import {useModal} from '@/composables/useModal';
export default {
name: 'FieldOrcid',
extends: FieldBase,
props: {
/** ORCID URL that has been verified */
orcid: {
type: String,
required: true,
default: '',
},
/** Author ID used in ORCID related actions */
authorId: {
type: Number,
required: true,
default: 0,
},
},
data() {
return {
/** Internal value used for displaying ORCID in component. Takes initial value from `orcid` prop */
orcidValue: '',
/** Whether an email requesting users verify their ORCID has been sent or not */
verificationRequested: false,
/** Whether request verification/delete ORCID button should be disabled or not */
isButtonDisabled: false,
};
},
computed: {
/**
* Helper to see if an ORCID value is present
* @returns {boolean}
*/
hasOrcid: function () {
return this.orcidValue.length !== 0;
},
},
created() {
this.orcidValue = this.orcid;
},
methods: {
/**
* Triggers author email request via API
*
* @returns {Promise<void>}
*/
sendAuthorEmail: async function () {
this.isButtonDisabled = true;
const {apiUrl} = useApiUrl(
`orcid/requestAuthorVerification/${this.authorId}`,
);
const {data, validationError, fetch} = useFetch(apiUrl, {
method: 'POST',
expectValidationError: true,
});
await fetch();
if (validationError.value !== null) {
this.verificationRequested = false;
pkp.eventBus.$emit('notify', validationError.value['error'], 'warning');
}
if (data.value !== null) {
this.verificationRequested = true;
}
this.isButtonDisabled = false;
},
/**
* Open confirmation dialog for requesting author ORCID verification
*/
openSendAuthorEmailDialog: function () {
const {openDialog} = useModal();
openDialog({
name: 'sendAuthorEmail',
title: this.t('orcid.field.authorEmailModal.title'),
message: this.t('orcid.field.authorEmailModal.message'),
actions: [
{
label: this.t('common.yes'),
isPrimary: true,
callback: async (close) => {
await this.sendAuthorEmail();
close();
},
},
{
label: this.t('common.no'),
isWarnable: true,
callback: (close) => {
close();
},
},
],
close: () => {},
});
},
/**
* Trigger API request to remove ORCID and access tokens from author/user
*
* @returns {Promise<void>}
*/
deleteOrcid: async function () {
this.isButtonDisabled = true;
const {apiUrl} = useApiUrl(`orcid/deleteForAuthor/${this.authorId}`);
const {data, validationError, fetch} = useFetch(apiUrl, {
method: 'POST',
expectValidationError: true,
});
await fetch();
if (validationError.value !== null) {
pkp.eventBus.$emit('notify', validationError.value['error'], 'warning');
}
if (data.value !== null) {
this.orcidValue = '';
}
this.isButtonDisabled = false;
},
/**
* Opens dialog to confirm deletion of ORCID from author/user
*/
openDeleteDialog: function () {
const {openDialog} = useModal();
openDialog({
name: 'deleteOrcid',
title: this.t('orcid.field.deleteOrcidModal.title'),
message: this.t('orcid.field.deleteOrcidModal.message'),
actions: [
{
label: this.t('common.yes'),
isPrimary: true,
callback: async (close) => {
await this.deleteOrcid();
close();
},
},
{
label: this.t('common.no'),
isWarnable: true,
callback: (close) => {
close();
},
},
],
close: () => {},
});
},
},
};
</script>

<style lang="less">
@import '../../../styles/_import';
.pkpFormField__control--html {
font-size: @font-sml;
line-height: 1.8em;
display: inline-block;
p:first-child {
margin-top: 0;
}
p:last-child {
margin-bottom: 0;
}
}
.pkpFormField__control--html__button {
margin-inline-start: 0.25rem;
}
</style>
Loading

0 comments on commit 2aefe54

Please sign in to comment.