Skip to content

Commit

Permalink
Merge branch 'staging'
Browse files Browse the repository at this point in the history
  • Loading branch information
ericjeangirard committed Oct 23, 2024
2 parents 6a3c4aa + 176a798 commit 491db1f
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 24 deletions.
1 change: 1 addition & 0 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<meta charset="UTF-8" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/fonts/remixicon.css" />
<meta name="google-site-verification" content="_w-F9sijoMQg6zOyO8yiJOAm_ZYxQ720ysRRq9K2psM" />
<meta name="google-site-verification" content="6hRuX0N3vV6ahdIot4Od8sI5aABG9Q1yYN-R8FpSy5w" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Works magnet</title>
</head>
Expand Down
10 changes: 4 additions & 6 deletions client/src/pages/actions/actionsOpenalexFeedback.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function ActionsOpenalexFeedback({ allOpenalexCorrections }) {

const switchModal = () => setIsModalOpen((prev) => !prev);

const { readyState, sendJsonMessage } = useWebSocket(`${VITE_WS_HOST}/ws`, {
const { sendJsonMessage } = useWebSocket(`${VITE_WS_HOST}/ws`, {
onError: (event) => console.error(event),
onMessage: (event) => {
const { autoDismissAfter, description, title, toastType } = JSON.parse(event.data);
Expand All @@ -41,10 +41,10 @@ export default function ActionsOpenalexFeedback({ allOpenalexCorrections }) {

const feedback = async () => {
try {
sendJsonMessage({ data: allOpenalexCorrections, email: userEmail });
sendJsonMessage({ data: allOpenalexCorrections, email: userEmail, type: 'openalex-affiliations' });
toast({
autoDismissAfter: 5000,
description: 'Your correction(s) are currently submitted to the <a href="https://github.com/dataesr/openalex-affiliations/issues" target="_blank">Github repository</a>',
description: 'Your corrections are currently submitted to the <a href="https://github.com/dataesr/openalex-affiliations/issues" target="_blank">Github repository</a>',
id: 'initOpenAlex',
title: 'OpenAlex corrections submitted',
});
Expand All @@ -61,9 +61,7 @@ export default function ActionsOpenalexFeedback({ allOpenalexCorrections }) {
};

useEffect(() => {
const emailRegex = new RegExp(
/^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i,
);
const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
const testEmail = (email) => setValidEmail(emailRegex.test(email) ? email : null);
const timeOutId = setTimeout(() => testEmail(userEmail), 500);
return () => clearTimeout(timeOutId);
Expand Down
167 changes: 162 additions & 5 deletions client/src/pages/mentions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import {
Button,
Col,
Container,
Modal,
ModalContent,
ModalFooter,
ModalTitle,
Row,
Tab,
Tabs,
Expand All @@ -11,10 +15,14 @@ import { Column } from 'primereact/column';
import { DataTable } from 'primereact/datatable';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import useWebSocket from 'react-use-websocket';

import { affiliations2Template, authorsTemplate } from '../utils/templates';
import { affiliations2Template, authorsTemplate, hasCorrectionTemplate } from '../utils/templates';
import useToast from '../hooks/useToast';
import { getMentions } from '../utils/works';

const { VITE_WS_HOST } = import.meta.env;

const DEFAULT_FROM = 0;
const DEFAULT_SEARCH = '';
const DEFAULT_SIZE = 50;
Expand All @@ -23,11 +31,12 @@ const DEFAULT_SORTORDER = '';
const DEFAULT_TYPE = 'software';

export default function Mentions() {
const [searchParams, setSearchParams] = useSearchParams();
const [corrections, setCorrections] = useState('');
const [correctionsUsed, setCorrectionsUsed] = useState(true);
const [correctionsCreated, setCorrectionsCreated] = useState(true);
const [correctionsShared, setCorrectionsShared] = useState(true);
const [fixedMenu, setFixedMenu] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [mentions, setMentions] = useState([]);
const [search, setSearch] = useState(DEFAULT_SEARCH);
Expand All @@ -42,6 +51,96 @@ export default function Mentions() {
sortOrder: DEFAULT_SORTORDER,
type: DEFAULT_TYPE,
});
const [userEmail, setUserEmail] = useState(null);
const [validEmail, setValidEmail] = useState(null);

// Hooks
const [searchParams, setSearchParams] = useSearchParams();
const { toast } = useToast();
const { sendJsonMessage } = useWebSocket(`${VITE_WS_HOST}/ws`, {
onError: (event) => console.error(event),
onMessage: (event) => {
const { autoDismissAfter, description, title, toastType } = JSON.parse(event.data);
return toast({
autoDismissAfter: autoDismissAfter ?? 10000,
description: description ?? '',
id: 'websocket',
title: title ?? 'Message renvoyé par le WebSocket',
toastType: toastType ?? 'info',
});
},
onOpen: () => console.log('Websocket opened'),
onClose: () => console.log('Websocket closed'),
shouldReconnect: () => true,
});

// Methods
const addCorrections = () => {
const correctedMentions = selectedMentions.map((selectedMention) => ({
id: selectedMention.id,
doi: selectedMention.doi,
texts: [
{
text: selectedMention.context,
class_attributes: {
classification: {
used: {
value: correctionsUsed,
score: 1.0,
previousValue: selectedMention.mention_context.used,
},
created: {
value: correctionsCreated,
score: 1.0,
previousValue: selectedMention.mention_context.created,
},
shared: {
value: correctionsShared,
score: 1.0,
previousValue: selectedMention.mention_context.shared,
},
},
},
},
],
}));
const correctedIds = correctedMentions.map((correctedMention) => correctedMention.id);
setMentions(mentions.map((mention) => {
if (correctedIds.includes(mention.id)) {
mention.hasCorrection = true;
mention.mention_context.used = correctionsUsed;
mention.mention_context.created = correctionsCreated;
mention.mention_context.shared = correctionsShared;
}
return mention;
}));
setCorrections([...corrections, ...correctedMentions]);
setSelectedMentions([]);
setCorrectionsUsed(true);
setCorrectionsCreated(true);
setCorrectionsShared(true);
};
const switchModal = () => setIsModalOpen((prev) => !prev);
const feedback = async () => {
try {
sendJsonMessage({ data: corrections, email: userEmail, type: 'mentions-characterizations' });
toast({
autoDismissAfter: 5000,
description: 'Your corrections are currently submitted to the <a href="https://github.com/dataesr/mentions-characterizations/issues" target="_blank">Github repository</a>',
id: 'initMentions',
title: 'Mentions characterizations submitted',
});
} catch (error) {
toast({
description: error.message,
id: 'errorMentions',
title: 'Error while sending mentions characterizations',
toastType: 'error',
});
} finally {
switchModal();
}
};

// Templates
const contextTemplate = (rowData) => (
Expand Down Expand Up @@ -138,6 +237,12 @@ export default function Mentions() {
};
getData();
}, [urlSearchParams]);
useEffect(() => {
const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
const testEmail = (email) => setValidEmail(emailRegex.test(email) ? email : null);
const timeOutId = setTimeout(() => testEmail(userEmail), 500);
return () => clearTimeout(timeOutId);
}, [userEmail]);

return (
<Container as="section" className="fr-mt-4w mentions">
Expand All @@ -149,7 +254,7 @@ export default function Mentions() {
className={`selected-item ${selectedMentions.length && 'selected'}`}
>
<span className="number">{selectedMentions.length}</span>
{`selected mention${selectedMentions.length === 1 ? '' : 's'}`}
{`selected mention${selectedMentions.length > 1 ? 's' : ''}`}
</div>
<Button
className="fr-mb-1w fr-pl-1w button"
Expand Down Expand Up @@ -213,7 +318,7 @@ export default function Mentions() {
color="blue-ecume"
disabled={!selectedMentions.length}
key="add-ror"
onClick={() => {}}
onClick={() => addCorrections()}
size="lg"
style={{ display: 'block', width: '100%', textAlign: 'left' }}
title="Add ROR"
Expand All @@ -222,7 +327,7 @@ export default function Mentions() {
className="fr-icon-send-plane-line fr-mr-2w"
style={{ color: '#000091' }}
/>
Send feedbacks
Add corrections
</Button>
<div className="text-right">
<Button
Expand Down Expand Up @@ -265,6 +370,34 @@ export default function Mentions() {
</Button>
</Col>
</Row>
<Button
disabled={!corrections.length > 0}
onClick={switchModal}
size="sm"
>
{`Send ${corrections.length} correction${corrections.length > 1 ? 's' : ''}`}
</Button>
<Modal isOpen={isModalOpen} hide={switchModal}>
<ModalTitle>Improve mentions characterizations</ModalTitle>
<ModalContent>
{`You corrected characterizations for ${corrections.length} mention${corrections.length > 1 ? 's' : ''}.`}
<TextInput
label="Please indicate your email. Only an encrypted version of your email will be public."
onChange={(e) => setUserEmail(e.target.value)}
required
type="email"
/>
</ModalContent>
<ModalFooter>
<Button
disabled={!corrections.length > 0 || !validEmail}
onClick={feedback}
title={`Send ${corrections.length} correction${corrections.length > 1 ? 's' : ''}`}
>
{`Send ${corrections.length} correction${corrections.length > 1 ? 's' : ''}`}
</Button>
</ModalFooter>
</Modal>
<Tabs
defaultActiveIndex={0}
onTabChange={(i) => setUrlSearchParams({
Expand Down Expand Up @@ -322,13 +455,25 @@ export default function Mentions() {
header="Shared"
sortable
/>
<Column
body={hasCorrectionTemplate}
field="hasCorrection"
header="Modified by user?"
sortable
style={{ maxWidth: '110px' }}
/>
<Column
body={affiliations2Template}
field="affiliations"
header="Affiliations"
/>
<Column body={authorsTemplate} field="authors" header="Authors" />
</DataTable>
{corrections && corrections.length > 0 && (
<code>
<pre>{JSON.stringify(corrections, null, 4)}</pre>
</code>
)}
</Tab>
<Tab label="Datasets">
<DataTable
Expand Down Expand Up @@ -380,13 +525,25 @@ export default function Mentions() {
header="Shared"
sortable
/>
<Column
body={hasCorrectionTemplate}
field="hasCorrection"
header="Modified by user?"
sortable
style={{ maxWidth: '110px' }}
/>
<Column
body={affiliations2Template}
field="affiliations"
header="Affiliations"
/>
<Column body={authorsTemplate} field="authors" header="Authors" />
</DataTable>
{corrections && corrections.length > 0 && (
<code>
<pre>{JSON.stringify(corrections, null, 4)}</pre>
</code>
)}
</Tab>
</Tabs>
</Container>
Expand Down
36 changes: 33 additions & 3 deletions server/src/utils/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ const octokit = new MyOctokit({
auth,
request: { retryAfter: 10 },
throttle: {
onRateLimit: (retryAfter, options) => {
onRateLimit: (_, options) => {
octokit.log.warn(
`Request quota exhausted for request ${options.method} ${options.url}`,
);
},
onSecondaryRateLimit: (retryAfter, options) => {
onSecondaryRateLimit: (_, options) => {
// Retry 5 times after hitting a rate limit error after 5 seconds
if (options.request.retryCount <= 5) {
return true;
Expand All @@ -43,7 +43,7 @@ const encrypt = (text) => {
return `${iv.toString('hex')}:${encrypted.toString('hex')}`;
};

const createIssue = (issue, email) => {
const createIssueOpenAlexAffiliations = ({ email, issue }) => {
let title = `Correction for raw affiliation ${issue.rawAffiliationString}`;
if (title.length > 1000) {
title = `${title.slice(0, 1000)}...`;
Expand Down Expand Up @@ -73,6 +73,36 @@ const createIssue = (issue, email) => {
});
};

const createIssueMentionsCharacterizations = ({ email, issue }) => {
const title = `Correction for mention ${issue.id}`;
const user = `${encrypt(email.split('@')[0])} @ ${email.split('@')[1]}`;
// eslint-disable-next-line no-param-reassign
issue.texts[0].class_attributes.classification.used.user = user;
// eslint-disable-next-line no-param-reassign
issue.texts[0].class_attributes.classification.created.user = user;
// eslint-disable-next-line no-param-reassign
issue.texts[0].class_attributes.classification.shared.user = user;
const body = JSON.stringify(issue, null, 4);
return octokit.rest.issues.create({
body,
owner: 'dataesr',
repo: 'mentions-characterizations',
title,
});
};

const createIssue = ({ email, issue, type }) => {
switch (type) {
case 'mentions-characterizations':
return createIssueMentionsCharacterizations({ email, issue });
case 'openalex-affiliations':
return createIssueOpenAlexAffiliations({ email, issue });
default:
console.error(`Error wile creating Github issue as "type" should be one of ["mentions-characterizations", "openalex-affiliations"] instead of "${type}".`);
return false;
}
};

export {
createIssue,
};
Loading

0 comments on commit 491db1f

Please sign in to comment.