Skip to content

Commit

Permalink
v1.188.0-rc1
Browse files Browse the repository at this point in the history
  • Loading branch information
varovaro committed Oct 7, 2024
2 parents b027984 + 1afaf35 commit c9e4e66
Show file tree
Hide file tree
Showing 20 changed files with 1,919 additions and 2,211 deletions.
2 changes: 1 addition & 1 deletion app/api/search.v2/searchResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function getSnippetsForNonFullText(hit: ElasticHit<EntitySchema>) {
function extractFullTextSnippets(hit: ElasticHit<EntitySchema>) {
const fullTextSnippets: { text: string; page: number }[] = [];

if (hit.inner_hits && hit.inner_hits.fullText.hits.hits.length > 0) {
if (hit.inner_hits && hit.inner_hits.fullText.hits.hits[0]?.highlight) {
const { highlight } = hit.inner_hits.fullText.hits.hits[0];
const regex = /\[{2}(\d+)]{2}/g;

Expand Down
11 changes: 11 additions & 0 deletions app/react/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ import { ActivityLog, activityLogLoader } from 'V2/Routes/Settings/ActivityLog';
import { CustomUploads, customUploadsLoader } from 'V2/Routes/Settings/CustomUploads/CustomUploads';
import { FiltersTable, filtersLoader } from 'V2/Routes/Settings/Filters';
import { RouteErrorBoundary, GeneralError } from 'V2/Components/ErrorHandling';
import {
ParagraphExtractorDashboard,
ParagraphExtractorLoader,
} from 'app/V2/Routes/Settings/ParagraphExtraction/ParagraphExtraction';
import {
loggedInUsersRoute,
adminsOnlyRoute,
Expand Down Expand Up @@ -138,6 +142,13 @@ const getRoutesLayout = (
}
/>
</Route>
<Route path="paragraph_extraction">
<Route
loader={ParagraphExtractorLoader(headers)}
index
element={adminsOnlyRoute(<ParagraphExtractorDashboard />)}
/>
</Route>
<Route path="relationship-types">
<Route
index
Expand Down
11 changes: 11 additions & 0 deletions app/react/Settings/components/SettingsNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ const SettingsNavigationComponent = ({ allowcustomJS }: { allowcustomJS: boolean
</I18NLink>
</NeedAuthorization>
</FeatureToggle>
<FeatureToggle feature="paragraphExtraction">
<NeedAuthorization roles={['admin', 'editor']}>
<I18NLink
to="settings/paragraph_extraction"
activeclassname="active"
className="list-group-item"
>
<Translate>Paragraph Extraction</Translate>
</I18NLink>
</NeedAuthorization>
</FeatureToggle>
<NeedAuthorization roles={['admin']}>
<I18NLink to="settings/thesauri" activeclassname="active" className="list-group-item">
<Translate>Thesauri</Translate>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/* eslint-disable max-statements */
import React, { useMemo, useState } from 'react';
import { IncomingHttpHeaders } from 'http';
import { LoaderFunction, useLoaderData, useRevalidator } from 'react-router-dom';
import * as extractorsAPI from 'app/V2/api/paragraphExtractor/extractors';
import * as templatesAPI from 'V2/api/templates';
import { SettingsContent } from 'V2/Components/Layouts/SettingsContent';
import { ClientTemplateSchema } from 'app/istore';
import { Button, Table, ConfirmationModal } from 'V2/Components/UI';
import { Translate } from 'app/I18N';
import { useSetAtom } from 'jotai';
import { notificationAtom } from 'V2/atoms';
import { extractorsTableColumns } from './components/TableElements';
import { TableExtractor, Extractor } from './types';
import { List } from './components/List';

const getTemplateName = (templates: ClientTemplateSchema[], targetId: string) => {
const foundTemplate = templates.find(template => template._id === targetId);
return foundTemplate?.name || targetId;
};

const formatExtractors = (
extractors: Extractor[],
templates: ClientTemplateSchema[]
): TableExtractor[] =>
(extractors || []).map(extractor => {
const targetTemplateName = getTemplateName(templates, extractor.templateTo);
const originTemplateNames = extractor.templateFrom.map(templateFrom =>
getTemplateName(templates, templateFrom)
);

return { ...extractor, rowId: extractor._id, originTemplateNames, targetTemplateName };
});

const ParagraphExtractorDashboard = () => {
const { extractors = [], templates } = useLoaderData() as {
extractors: TableExtractor[];
templates: ClientTemplateSchema[];
};
const [isSaving, setIsSaving] = useState(false);
const revalidator = useRevalidator();
const [selected, setSelected] = useState<TableExtractor[]>([]);
const [confirmModal, setConfirmModal] = useState(false);
const setNotifications = useSetAtom(notificationAtom);

const deleteExtractors = async () => {
setIsSaving(true);
const extractorIds = selected?.map(selection => selection._id) as string[];

try {
await extractorsAPI.remove(extractorIds);
revalidator.revalidate();
setNotifications({
type: 'success',
text: <Translate>Extractor/s deleted</Translate>,
});
} catch (error) {
setNotifications({
type: 'error',
text: <Translate>An error occurred</Translate>,
details: error.json?.prettyMessage ? error.json.prettyMessage : undefined,
});
} finally {
setIsSaving(false);
}
};
// const handleSave = async (extractor: IXExtractorInfo) => {};
const paragraphExtractorData = useMemo(
() => formatExtractors(extractors, templates),
[extractors, templates]
);

return (
<div
className="tw-content"
data-testid="settings-ix"
style={{ width: '100%', overflowY: 'auto' }}
>
<SettingsContent>
<SettingsContent.Header title="Paragraph extraction" />

<SettingsContent.Body>
{/* should create a component for empty data? */}
<Table
data={paragraphExtractorData}
columns={extractorsTableColumns}
header={
<Translate className="text-base font-semibold text-left text-gray-900 bg-white">
Extractors
</Translate>
}
enableSelections
onChange={({ selectedRows }) => {
setSelected(() => paragraphExtractorData.filter(ex => ex.rowId in selectedRows));
}}
// what default sorting to use?
defaultSorting={[{ id: 'status', desc: false }]}
/>
</SettingsContent.Body>

<SettingsContent.Footer className="flex gap-2">
{selected?.length === 1 ? (
<Button type="button" onClick={() => {}} disabled={isSaving}>
<Translate>Edit Extractor</Translate>
</Button>
) : undefined}

{selected?.length ? (
<Button
type="button"
color="error"
onClick={() => setConfirmModal(true)}
disabled={isSaving}
>
<Translate>Delete</Translate>
</Button>
) : (
<Button type="button" onClick={() => {}} disabled={isSaving}>
<Translate>Create Extractor</Translate>
</Button>
)}
</SettingsContent.Footer>
</SettingsContent>

{confirmModal && (
<ConfirmationModal
header="Delete extractors"
warningText="Do you want to delete the following items?"
body={<List items={selected || []} />}
onAcceptClick={async () => {
await deleteExtractors();
setConfirmModal(false);
setSelected([]);
}}
onCancelClick={() => setConfirmModal(false)}
dangerStyle
/>
)}
</div>
);
};

const ParagraphExtractorLoader =
(headers?: IncomingHttpHeaders): LoaderFunction =>
async () => {
const extractors = await extractorsAPI.get();
const templates = await templatesAPI.get(headers);
return { extractors, templates };
};

export { ParagraphExtractorDashboard, ParagraphExtractorLoader };
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { Translate } from 'app/I18N';
import { TableExtractor } from '../types';

const List = ({ items }: { items: TableExtractor[] }) => (
<ul className="flex flex-wrap gap-8 max-w-md list-disc">
{/* what should be displayed on the confirm modal? */}
{items.map(item => (
<li key={item._id}>
<Translate>Templates: </Translate>
{item.originTemplateNames.join(', ')}
<Translate>Target Template:</Translate>
{item.targetTemplateName}
</li>
))}
</ul>
);

export { List };
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* eslint-disable max-lines */
/* eslint-disable react/no-multi-comp */
import React from 'react';
import { CellContext, createColumnHelper } from '@tanstack/react-table';
import { Link } from 'react-router-dom';
import { Translate } from 'app/I18N';
import { Button, Pill } from 'V2/Components/UI';
import { TableExtractor } from '../types';

const extractorColumnHelper = createColumnHelper<TableExtractor>();

const TemplateFromHeader = () => <Translate>Template</Translate>;
const TemplateToHeader = () => <Translate>Target Template</Translate>;
const DocumentsHeader = () => <Translate>Documents</Translate>;
const GeneratedEntitiesHeader = () => <Translate>Generated Entities</Translate>;
const ActionHeader = () => <Translate className="">Action</Translate>;

const NumericCell = ({
cell,
}: CellContext<
TableExtractor,
TableExtractor['documents'] | TableExtractor['generatedEntities']
>) => <span className="text-sm font-normal text-gray-500">{cell.getValue()}</span>;

const TemplatesCell = ({ cell }: CellContext<TableExtractor, TableExtractor['templateTo']>) => (
<div className="flex flex-wrap gap-2">
<div key={cell.getValue()} className="whitespace-nowrap">
<Pill color="gray">{cell.getValue()}</Pill>
</div>
</div>
);

const TemplateFromCell = ({
cell,
}: CellContext<TableExtractor, TableExtractor['templateFrom']>) => (
<div className="flex flex-wrap gap-2">
{cell.getValue().map(value => (
<div key={value} className="whitespace-nowrap">
<Pill color="gray">{value}</Pill>
</div>
))}
</div>
);

const LinkButton = ({ cell }: CellContext<TableExtractor, TableExtractor['_id']>) => (
<Link to={`suggestions/${cell.getValue()}`}>
<Button className="leading-4" styling="outline">
<Translate>View</Translate>
</Button>
</Link>
);

// todo: fix width of each column
const extractorsTableColumns = [
extractorColumnHelper.accessor('originTemplateNames', {
header: TemplateFromHeader,
enableSorting: true,
cell: TemplateFromCell,
meta: { headerClassName: 'w-4/6' },
}),
extractorColumnHelper.accessor('targetTemplateName', {
header: TemplateToHeader,
enableSorting: true,
cell: TemplatesCell,
meta: { headerClassName: 'w-4/6' },
}),
extractorColumnHelper.accessor('documents', {
header: DocumentsHeader,
enableSorting: true,
cell: NumericCell,
meta: { headerClassName: 'w-4/6' },
}),
extractorColumnHelper.accessor('generatedEntities', {
header: GeneratedEntitiesHeader,
enableSorting: true,
cell: NumericCell,
meta: { headerClassName: 'w-4/6' },
}),
extractorColumnHelper.accessor('_id', {
header: ActionHeader,
enableSorting: true,
cell: LinkButton,
meta: { headerClassName: '' },
}),
];

export { extractorsTableColumns };
13 changes: 13 additions & 0 deletions app/react/V2/Routes/Settings/ParagraphExtraction/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type Extractor = {
_id: string;
templateFrom: string[];
templateTo: string;
documents: number;
generatedEntities: number;
};

export type TableExtractor = Extractor & {
rowId: string;
targetTemplateName: string;
originTemplateNames: string[];
};
64 changes: 64 additions & 0 deletions app/react/V2/api/paragraphExtractor/extractors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { IncomingHttpHeaders } from 'http';
import api from 'app/utils/api';
import { RequestParams } from 'app/utils/RequestParams';
import { IXExtractorInfo } from 'V2/shared/types';

let dummyData = [
{
_id: '1',
templateFrom: ['66fbe4f28542cc5545e05a46', '66fbe4d28542cc5545e0599c'],
templateTo: '5bfbb1a0471dd0fc16ada146',
documents: 831,
generatedEntities: 12000,
rowId: '1',
status: '',
},
{
_id: '2',
templateFrom: ['66fbe4d28542cc5545e0599c', 'Judge Documents'],
templateTo: '66fbe4f28542cc5545e05a46',
documents: 500,
generatedEntities: 12001,
rowId: '1',
status: '',
},
];

const apiEndpoint = 'paragraph-extractor';

const get = async () =>
new Promise(resolve => {
setTimeout(() => resolve(dummyData));
});

const getById = async (extractorId: string, headers?: IncomingHttpHeaders) => {
try {
const requestParams = new RequestParams({ id: extractorId }, headers);
const { json: response } = await api.get(apiEndpoint, requestParams);
return response;
} catch (e) {
return e;
}
};

const save = async (extractor: IXExtractorInfo) => {
const requestParams = new RequestParams(extractor);
let response: IXExtractorInfo[];
if (extractor._id) {
response = await api.put(apiEndpoint, requestParams);
} else {
response = await api.post(apiEndpoint, requestParams);
}
return response;
};

const remove = async (ids: string[]) => {
// const requestParams = new RequestParams({ ids });
// const response = await api.delete(apiEndpoint, requestParams);
// return response;
dummyData = dummyData.filter(data => !ids.includes(data._id));
console.log(dummyData);
return true;
};

export { get, save, remove, getById };
1 change: 1 addition & 0 deletions app/react/apiResponseTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface ClientSettings
properties: string[];
}[];
};
paragraphExtraction?: boolean;
[k: string]: unknown | undefined;
};
}
Expand Down
Loading

0 comments on commit c9e4e66

Please sign in to comment.