Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/react/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { StorybookConfig } from "@storybook/react-vite";
import { mergeConfig } from 'vite'; // Import mergeConfig
import * as globals from "../src/app/globals";
import { dirname, join } from "node:path";

const config: StorybookConfig = {
Expand All @@ -19,6 +21,39 @@ const config: StorybookConfig = {
typescript: {
reactDocgen: "react-docgen",
},
async viteFinal(config, { configType }) {
// Return the merged configuration
return mergeConfig(config, {
server: {
proxy: {
'/antelope': {
target: globals.TIB_ANNOTATION_API_ENDPOINT + 'annotation/entitylinking/text?iconclass=true', // The address of your backend server
changeOrigin: true,
secure: false,
// Optional: Rewrite the path to remove the /api prefix if needed by your backend
rewrite: (path: any) => path.replace(/^\/antelope/, ''),
configure: (proxy: any, options) => {
proxy.on('error', (err: any, _req:any, _res:any) => {
console.log('proxy error', err);
});
proxy.on('proxyReq', (proxyReq:any, req:any, res:any) => {
// Add or modify headers here
proxyReq.setHeader('accept', 'application/json');
proxyReq.setHeader('Content-Type', 'application/json');
console.log('Sending Request to the Target (main.ts):', req.method, req.url, req.body);
});
proxy.on('proxyRes', (proxyRes: any, req:any, _res:any) => {
console.log('Received Response from the Target (main.ts):', proxyRes.statusCode, req.url);
console.log('proxyRes', proxyRes);
//console.log('_res',_res);
});
},
},
},
},
});
},

};
export default config;

Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/app/globals.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// TIB
export const TIB_API_ENDPOINT = "https://api.terminology.tib.eu/api/";
export const TIB_API_ENDPOINT_OLS3 = "https://service.tib.eu/ts4tib/api/";
// TIB Annotation Service Antelope
export const TIB_ANNOTATION_API_ENDPOINT = "https://service.tib.eu/annotation/api/";

// TS4NFDI
export const GATEWAY_API_ENDPOINT =
Expand Down
17 changes: 17 additions & 0 deletions packages/react/src/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,23 @@ export type EntityRelationsWidgetProps = ApiObj &
ParameterObj &
OnNavigates;

export type AntelopeApiWidgetProps = {
/**
* Threshold.
*/
threshold: number;

/**
* Search term.
*/
searchTerm: string;

/**
* Language.
*/
language?: string;
};

export type JsonApiWidgetProps = {
/**
* The API query whose response JSON should be displayed on click.
Expand Down
12 changes: 12 additions & 0 deletions packages/react/src/app/widgetDescriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ Allows users to open API endpoints directly in a new browser tab, providing imme
Supports user-defined button text and adjustable button sizes (\`small\`, \`medium\`, or \`large\`), ensuring it can blend seamlessly into various user interfaces.
`.trim();

export const AntelopeApiDescription = `
The AntelopeApiWidget is a lightweight and efficient component designed to provide users with direct access to the Antelope API endpoint through a widget that directly renders the result given a "text" parameter.

#### Key Features:

- **Direct API access**:
You can find the full api configuration docs here: [https://service.tib.eu/annotation/api.htm](https://service.tib.eu/annotation/api.htm)

- **Customizable inputs**:
Supports user-defined parameters "text", "threshold" and "language" or the terminology search, ensuring it can be used with the user's own form and integrated into various user interfaces.
`.trim();

export const BreadcrumbDescription = `
The BreadcrumbWidget to display the ontology and CURIE (compact URI) of an entity as badges to indicate the location of an entity within a terminology service.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { AntelopeApiWidget } from "./AntelopeApiWidget";
import {
commonAntelopeApiWidgetPlay,
AntelopeApiWidgetDefaultArgs,
AntelopeApiWidgetStoryArgs,
AntelopeApiWidgetStoryArgTypes,
} from "./AntelopeApiWidgetStories";
import { AntelopeApiDescription } from "../../../app/widgetDescriptions";
import type { Meta, StoryObj } from "@storybook/react-vite";

const meta = {
title: "Terminology Service/AntelopeApiWidget",
component: AntelopeApiWidget,
parameters: {
layout: "centered",
docs: {
description: {
component: AntelopeApiDescription,
},
},
},
argTypes: AntelopeApiWidgetStoryArgTypes,
args: AntelopeApiWidgetStoryArgs,
} satisfies Meta<typeof AntelopeApiWidget>;

export default meta;

type Story = StoryObj<typeof meta>;

export const AntelopeApiWidgetDefault = {
args: AntelopeApiWidgetDefaultArgs,
play: commonAntelopeApiWidgetPlay,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"use client";

import React, {useState, useEffect, useCallback } from "react";
import { EuiProvider, EuiLoadingSpinner } from "@elastic/eui";
import { AntelopeApiWidgetProps } from "../../../app/types";
import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider } from "react-query";

function AntelopeApiWidget(props: AntelopeApiWidgetProps) {
const { threshold, searchTerm, language } = props;
const [ annotation, updateAnnotation ] = useState([])
const [ isLoading, updateIsLoading ] = useState(true)
const [debouncedQuery, setDebouncedQuery] = useState('')

useEffect( () => {

const postData = async (data: any) => {
try {
updateIsLoading(true);
// use proxy (defined in main.ts)
const response = await fetch('/antelope', { // Use the proxied path
//const response = await fetch(globals.TIB_ANNOTATION_API_ENDPOINT + 'annotation/entitylinking/text?iconclass=true', { // Use the proxied path
method: 'POST',
headers: {
'Content-Type': 'application/json',
'accept': 'application/json',
},
body: JSON.stringify(data),
});

if (!response.ok) {
throw new Error('Network response was not ok');
}

const result = await response.json();
console.log('Success:', result);
updateAnnotation(result);
updateIsLoading(false);
return result;
} catch (error) {
console.error('Error:', error);
}
};
postData({ threshold, text: searchTerm, language })
}, [threshold, debouncedQuery, language])

useEffect(() => {
const timer = setTimeout(() => {
setDebouncedQuery(searchTerm)
}, 1000)

return () => clearTimeout(timer)
}, [searchTerm])


return ( <>
{
isLoading ? (
<EuiLoadingSpinner size="s" />
) :
<div dangerouslySetInnerHTML={{__html: annotation.html}}></div>
}
</>
);
}

function WrappedAntelopeApiWidget(props: AntelopeApiWidgetProps) {
const queryClient = new QueryClient();
return (
<EuiProvider colorMode="light">
<QueryClientProvider client={queryClient}>
<AntelopeApiWidget
threshold={props.threshold}
searchTerm={props.searchTerm}
language={props.language}
/>
</QueryClientProvider>
</EuiProvider>
);
}

export { AntelopeApiWidget, WrappedAntelopeApiWidget };
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as globals from "../../../app/globals";

import {
thresholdArgType,
searchTermArgType,
languageArgType,
} from "../../../stories/storyArgs";
import { expect, waitFor, within } from "storybook/test";

export const AntelopeApiWidgetStoryArgTypes = {
...thresholdArgType,
...searchTermArgType,
...languageArgType,
};

export const AntelopeApiWidgetStoryArgs = {
threshold: "",
searchTerm: "",
language: "",
} as const;

export const AntelopeApiWidgetDefaultArgs = {
threshold: 0.8,
searchTerm: "string",
language: "en",
};

export const commonAntelopeApiWidgetPlay = async ({
canvasElement,
}: {
canvasElement: HTMLElement;
}) => {
const canvas = within(canvasElement);

await waitFor(
async () => {
const content = canvas.getByTestId("antelope-api");
await expect(content).toBeInTheDocument();
},
{
timeout: 3000,
},
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AntelopeApiWidget } from "./AntelopeApiWidget";
36 changes: 36 additions & 0 deletions packages/react/src/stories/storyArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,42 @@ export const buttonSizeArgType: ArgTypes = {
options: ["s", "m"],
},
};
export const thresholdArgType: ArgTypes = {
threshold: {
required: true,
description: "Threshold.",
control: {
type: 'number',
min: 0,
max: 1,
step: 0.1,
},
},
};
export const searchTermArgType: ArgTypes = {
searchTerm: {
required: true,
description: "The search term.",
table: {
type: { summary: "string" },
},
},
};
export const languageArgType: ArgTypes = {
language: {
required: false,
control: {
type: "radio",
},
options: [
"en",
],
description: "Language for Antelope term search.",
table: {
type: { summary: "string" },
},
},
};
export const initialEntriesPerPageArgType: ArgTypes = {
initialEntriesPerPage: {
required: false,
Expand Down