Skip to content

Commit

Permalink
QHAna plugin refactor (#162)
Browse files Browse the repository at this point in the history
* Refactor QHAna plugin config and loading plugin list

* Always provide a QHAna service task option

* Refactor QHAna transformation to use variables over topic names

* Fix broken QHAna tests due to config changes

* Fix linting issues
  • Loading branch information
buehlefs authored Oct 14, 2024
1 parent 1d3dd25 commit b48d8ee
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,59 +10,41 @@ import { getModeler } from "../../../editor/ModelerHandler";
* @constructor
*/
export default function QHAnaConfigurationsTab() {
const [listPluginsEndpoint, setListPluginsEndpoint] = useState(
configManager.getListPluginsURL()
);
const [getPluginEndpoint, setGetPluginEndpoint] = useState(
configManager.getGetPluginsURL()
const [getPluginRegistryURL, setPluginRegistryURL] = useState(
configManager.getPluginRegistryURL()
);
const modeler = getModeler();

// save changed values on close
QHAnaConfigurationsTab.prototype.onClose = () => {
modeler.config.listPluginsEndpoint = listPluginsEndpoint;
modeler.config.getGetPluginsURL = getPluginEndpoint;
configManager.setListPluginsURL(listPluginsEndpoint);
configManager.setGetPluginsURL(getPluginEndpoint);
modeler.config.getPluginRegistryURL = getPluginRegistryURL;
configManager.setPluginRegistryURL(setPluginRegistryURL);
};

return (
<>
<div>
<h3>QHAna endpoint configuration:</h3>
<table>
<tbody>
<tr className="spaceUnder">
<td align="right">List Plugins Endpoint</td>
<td align="left">
<input
className="qwm-input"
type="string"
name="listPluginsEndpoint"
value={listPluginsEndpoint}
onChange={(event) => setListPluginsEndpoint(event.target.value)}
/>
</td>
</tr>
<tr className="spaceUnder">
<td align="right">Get Plugin Endpoint</td>
<td align="right">Plugin Registry URL</td>
<td align="left">
<input
className="qwm-input"
type="string"
name="getPluginEndpoint"
value={getPluginEndpoint}
onChange={(event) => setGetPluginEndpoint(event.target.value)}
type="url"
name="qhanaPluginRegistryURL"
value={getPluginRegistryURL}
onChange={(event) => setPluginRegistryURL(event.target.value)}
/>
</td>
</tr>
</tbody>
</table>
</>
</div>
);
}

QHAnaConfigurationsTab.prototype.config = () => {
const modeler = getModeler();
modeler.config.qhanaListPluginsURL = configManager.getListPluginsURL();
modeler.config.qhanqGetPluginURL = configManager.getGetPluginsURL();
modeler.config.qhanaPluginRegistryURL = configManager.getPluginRegistryURL();
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,56 @@ import ConfigurationsEndpoint from "../../../editor/configurations/Configuration
import * as configManager from "../framework-config/QHAnaConfigManager";
import * as consts from "../Constants";

const CUSTOM_PLUGIN_CONFIG = {
name: "CUSTOM",
id: "CUSTOM",
description: "",
appliesTo: "qhana:QHAnaServiceTask",
groupLabel: "Service Properties",
attributes: [
{
name: "identifier",
label: "Identifier",
type: "string",
value: "",
editable: "true",
bindTo: {
name: "qhanaIdentifier",
},
},
{
name: "version",
label: "Version",
type: "string",
value: "",
editable: "true",
bindTo: {
name: "qhanaVersion",
},
},
{
name: "name",
label: "Title",
type: "string",
value: "CUSTOM",
editable: "true",
bindTo: {
name: "qhanaName",
},
},
{
name: "description",
label: "Description",
type: "string",
value: "",
editable: "true",
bindTo: {
name: "qhanaDescription",
},
},
],
};

/**
* Custom ConfigurationsEndpoint for the QHAna Plugin. Extends the ConfigurationsEndpoint to fetch the configurations directly
* from the QHAna plugin registry.
Expand All @@ -14,71 +64,105 @@ export default class QHAnaConfigurationsEndpoint extends ConfigurationsEndpoint
/**
* Fetch all plugins from the QHAna plugin registry and transform them into configurations for QHAna service tasks.
*/
fetchConfigurations() {
const self = this;

// fetch all QHAna services from the QHAna plugin registry
fetch(configManager.getListPluginsURL())
.then((response) => response.json())
.then((data) => {
try {
const allServices = data.data.items;
console.log("Received " + allServices.length + " QHAna services: ");

let serviceId;

// fetch details for each service and create configuration
allServices.forEach(function (service) {
serviceId = service.resourceKey.pluginId;

// fetch plugin details for serviceId
fetch(configManager.getGetPluginsURL() + serviceId + "/")
.then((response) => response.json())
.then((data) => {
const serviceData = data.data;
console.log(
"Received QHAna service details for service " + serviceId
);
console.log(serviceData);

// create configuration from serviceData
self._configurations.push(
createConfigurationForServiceData(serviceData)
);
})
.catch((error) => {
console.error(
"Error fetching QHAna service with id " +
serviceId +
": \n" +
error
);
});
});
} catch (error) {
console.error(
"Error while parsing QHAna services from " +
configManager.getGetPluginsURL() +
": \n" +
error
);
async fetchConfigurations() {
const newConfigurations = [];

const registryUrl = configManager.getPluginRegistryURL();

if (!registryUrl) {
console.info(
"Cannot fetch QHAna Plugins, Plugin Registry URL is not configured."
);
return; // nothing to fetch, registry is not configured
}

let pluginsLink = null;
try {
const apiResult = await (await fetch(registryUrl)).json();
pluginsLink = apiResult?.links?.find?.(
(link) =>
link.resourceType === "plugin" &&
link.rel.some((r) => r === "collection")
);
} catch (error) {
console.error(
"Could not reach QHAna Plugin Registry to load available plugins!",
error
);
}

if (!pluginsLink) {
// no plugins found
this.configurations = newConfigurations;
return;
}

async function loadPlugins(url, configurations, seen) {
try {
const pluginListResponse = await (await fetch(url)).json();

await Promise.allSettled(
pluginListResponse.data.items.map(async (pluginLink) => {
if (seen.has(pluginLink.href)) {
return; // plugin already processed
}
seen.add(pluginLink.href);

let pluginResponse = pluginListResponse.embedded.find(
(e) => e.data.self.href === pluginLink.href
);

try {
if (!pluginResponse) {
pluginResponse = await (await fetch(pluginLink.href)).json();
}

// create configuration from plugin data
configurations.push(
createConfigurationForServiceData(pluginResponse.data)
);
} catch (error) {
console.error(
`Failed to load plugin ${pluginLink.name} (${pluginLink.href})!`,
error
);
}
})
);

const nextLink = pluginListResponse.links.find(
(link) =>
link.resourceType === "plugin" &&
link.rel.some((r) => r === "page") &&
link.rel.some((r) => r === "next")
);
if (nextLink && nextLink.href !== url) {
await loadPlugins(nextLink.href, configurations, seen);
}
})
.catch((error) => {
} catch (error) {
console.error(
"Error fetching configurations from " +
configManager.getListPluginsURL() +
": \n" +
error
"Failed to fetch plugin page from QHAna Plugin Registry.",
error
);
});
}
}

await loadPlugins(pluginsLink.href, newConfigurations, new Set());

console.info(`${newConfigurations.length} QHAna plugins loaded`);

this.configurations = newConfigurations;
return;
}

/**
* Returns all Configurations for QHAna service tasks which are saved in this endpoint.
*/
getQHAnaServiceConfigurations() {
return this.getConfigurations(consts.QHANA_SERVICE_TASK);
return [
CUSTOM_PLUGIN_CONFIG,
...this.getConfigurations(consts.QHANA_SERVICE_TASK),
];
}

/**
Expand All @@ -88,6 +172,9 @@ export default class QHAnaConfigurationsEndpoint extends ConfigurationsEndpoint
* @return {*}
*/
getQHAnaServiceConfiguration(id) {
if (id === "CUSTOM") {
return CUSTOM_PLUGIN_CONFIG;
}
return this.getConfiguration(id);
}

Expand Down Expand Up @@ -208,7 +295,5 @@ export function createConfigurationForServiceData(serviceData) {
});
});

console.log("Created configuration for QHAna service");
console.log(configuration);
return configuration;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,33 @@ import { getPluginConfig } from "../../../editor/plugin/PluginConfigHandler";

// default config entries used if no value is specified in the initial plugin config
const defaultConfig = {
qhanaListPluginsURL: process.env.QHANA_LIST_PLUGINS_URL,
qhanqGetPluginURL: process.env.QHANA_GET_PLUGIN_URL,
qhanaPluginRegistryURL: process.env.QHANA_PLUGIN_REGISTRY_URL ?? "",
};

const config = {};

/**
* Get the url to list all plugins of the QHAna plugin registry
* Get the url of the QHAna plugin registry
*
* @return {string} the url
*/
export function getListPluginsURL() {
export function getPluginRegistryURL() {
if (config.qhanaListPluginsURL === undefined) {
setListPluginsURL(
getPluginConfig("qhana").qhanaListPluginsURL ||
defaultConfig.qhanaListPluginsURL
setPluginRegistryURL(
getPluginConfig("qhana").qhanaPluginRegistryURL ||
defaultConfig.qhanaPluginRegistryURL
);
}
return config.qhanaListPluginsURL;
return config.qhanaPluginRegistryURL;
}

/**
* Set the url to list all plugins of the QHAna plugin registry
* Set the url of the QHAna plugin registry
*
* @return {string} the url
*/
export function setListPluginsURL(url) {
export function setPluginRegistryURL(url) {
if (url !== null && url !== undefined) {
// remove trailing slashes
config.qhanaListPluginsURL = url.replace(/\/$/, "");
}
}

/**
* Get the url to get a specific plugin from the QHAna plugin registry
*
* @return {string} the url
*/
export function getGetPluginsURL() {
if (config.qhanqGetPluginURL === undefined) {
setGetPluginsURL(
getPluginConfig("qhana").qhanqGetPluginURL ||
defaultConfig.qhanqGetPluginURL
);
}
return config.qhanqGetPluginURL;
}

/**
* Set the url to get a specific plugin from the QHAna plugin registry
*
* @return {string} the url
*/
export function setGetPluginsURL(url) {
if (url !== null && url !== undefined) {
// remove trailing slashes
config.qhanqGetPluginURL = url;
config.qhanaPluginRegistryURL = url;
}
}
Loading

0 comments on commit b48d8ee

Please sign in to comment.