From 370db336943243457e36cd31d95484b111f32ee4 Mon Sep 17 00:00:00 2001
From: connoratrug <47183404+connoratrug@users.noreply.github.com>
Date: Fri, 16 Aug 2024 15:02:43 +0200
Subject: [PATCH] feat(analytics, ui): event based analytics service plus (pre
alpha)tailwind UI for the analytics config (#4031)
Analytics
Allow admin user to setup analytics events. When the enduser of the web interface executes an action ( for example clicks on the 'contact' button) a signal is send to a analytics service ( third party; site-improve, google, .. or emx2 analytics endpoint). The app maintainer can configure which events are send in response to what ui element the user interacts with ( initial 'click' event only). The maintainer can configure these events on a per schema basis.
Tailwind (analytics) ui (pre alpha)
- ui path: /apps/ui/#/
- login as admin to update analytics settings
- analytics path: /ui/[schema-name]/analytics
---
apps/build.gradle | 5 +-
apps/catalogue.code-workspace | 6 +
apps/dev-proxy.config.js | 4 +
apps/emx2-analytics/.gitignore | 24 +
apps/emx2-analytics/README.md | 49 ++
apps/emx2-analytics/index.html | 13 +
apps/emx2-analytics/package.json | 48 ++
apps/emx2-analytics/src/App.vue | 176 +++++
apps/emx2-analytics/src/lib/analytics.ts | 64 ++
.../src/lib/providers/siteImprove.ts | 38 ++
apps/emx2-analytics/src/main.ts | 5 +
apps/emx2-analytics/src/style.css | 79 +++
apps/emx2-analytics/src/types/Provider.ts | 12 +
apps/emx2-analytics/src/types/Trigger.ts | 6 +
apps/emx2-analytics/src/vite-env.d.ts | 1 +
apps/emx2-analytics/tsconfig.app.json | 27 +
apps/emx2-analytics/tsconfig.json | 35 +
apps/emx2-analytics/tsconfig.node.json | 13 +
apps/emx2-analytics/vite.config.ts | 21 +
.../components/CollectionEventDisplay.vue | 1 -
.../nuxt3-ssr/components/SubCohortDisplay.vue | 1 -
.../HarmonisationGridPerVariable.vue | 5 +-
.../harmonisation/HarmonisationTable.vue | 5 +-
.../components/layouts/DetailPage.vue | 6 +-
.../components/layouts/LandingPage.vue | 6 +-
.../components/layouts/SearchPage.vue | 6 +-
.../table/{StickyTable.vue => Sticky.vue} | 0
apps/nuxt3-ssr/middleware/analytics.global.ts | 20 -
apps/nuxt3-ssr/nuxt.config.ts | 12 +-
apps/nuxt3-ssr/plugins/analytics.client.ts | 14 +
.../server/routes/[schema]/api/trigger.ts | 9 +
apps/package.json | 6 +-
apps/tailwind-components/app.vue | 201 +++---
.../components/BreadCrumbs.vue | 1 -
.../components/Button.vue | 2 +-
.../components/CardList.vue | 0
.../components/CardListItem.vue | 0
.../components/ContentReadMore.vue} | 0
.../components}/FooterComponent.vue | 4 +-
.../tailwind-components/components/Header.vue | 39 ++
.../components/HeaderButton.vue | 0
.../components/HeaderButtonMobile.vue | 0
.../components/Logo.vue | 1 -
.../components/LogoMobile.vue | 2 +-
.../components/Navigation.vue | 83 +++
.../components/PageHeader.vue | 0
.../components/SideModal.vue | 8 +-
.../components/Tab.vue | 0
.../components/Table.vue | 0
.../components/TableCell.vue | 0
.../components/TableHead.vue | 0
.../components/TableHeadRow.vue | 0
.../components/TableRow.vue | 0
.../components/content/ContentBlock.vue | 0
.../content/ContentBlockAttachedFiles.vue | 0
.../components/content/ContentBlockData.vue | 0
.../content/ContentBlockDescription.vue | 0
.../components/content/ContentBlockIntro.vue | 0
.../components/content/ContentBlockModal.vue | 0
.../components/content/ContentBlocks.vue | 0
.../components/content/ContentGenericItem.vue | 0
.../content/ContentGenericItemList.vue | 0
.../content/type/ContentTypeHyperLink.vue | 0
.../content/type/ContentTypeOntologyArray.vue | 0
.../content/type/ContentTypeRefBack.vue | 0
.../content/type/ContentTypeString.vue | 0
.../content/type/ContentTypeText.vue | 0
.../components/input/String.vue | 2 +-
.../components/table/TableEMX2.vue | 35 +
.../components/table/cellTypes/EMX2.vue | 25 +
.../composables/fetchMetadata.ts | 40 ++
.../composables/fetchTableData.ts | 114 ++++
.../composables/fetchTableMetadata.ts | 7 +
apps/tailwind-components/nuxt.config.ts | 11 +-
.../pages/DataFetch.other.vue | 123 ++++
.../pages/FooterComponent.story.vue | 7 +
.../pages/Header.story.vue | 30 +
apps/tailwind-components/pages/Logo.story.vue | 3 +
.../pages/LogoMobile.story.vue | 3 +
.../pages/Navigation.story.vue | 9 +
.../pages/SideModal.story.vue | 86 +++
.../pages/input/String.story.vue | 46 +-
.../pages/table/EMX2.story.vue | 120 ++++
.../plugins/floating-vue.ts | 7 +
.../server/routes/[schema]/api/trigger.ts | 9 +
.../server/routes/[schema]/graphql.ts | 9 +
.../server/routes/graphql.ts | 9 +
apps/tailwind-components/server/tsconfig.json | 3 +
apps/tailwind-components/types/types.ts | 23 +-
.../utils/moduleToString.ts} | 0
apps/ui/.gitignore | 24 +
apps/ui/app.vue | 5 +
apps/ui/composables/session.ts | 19 +
apps/ui/layouts/default.vue | 89 +++
apps/ui/middleware/adminOnly.ts | 7 +
apps/ui/nuxt.config.ts | 33 +
apps/ui/package.json | 26 +
apps/ui/pages/[schema]/[table]/index.vue | 72 +++
apps/ui/pages/[schema]/analytics.vue | 236 +++++++
apps/ui/pages/[schema]/index.vue | 102 +++
apps/ui/pages/[schema]/schema.vue | 6 +
apps/ui/pages/account.vue | 54 ++
apps/ui/pages/index.vue | 48 ++
apps/ui/pages/login.vue | 69 ++
apps/ui/public/favicon.ico | Bin 0 -> 4286 bytes
.../routes/[schema]/api/trigger/[trigger].ts | 8 +
.../routes/[schema]/api/trigger/index.ts | 9 +
apps/ui/stores/metaStore.ts | 69 ++
apps/ui/tsconfig.json | 4 +
apps/yarn.lock | 599 +++++++++++++++++-
backend/molgenis-emx2-analytics/build.gradle | 5 +
.../emx2/analytics/model/Trigger.java | 8 +
.../model/actions/CreateTriggerAction.java | 6 +
.../model/actions/DeleteTriggerAction.java | 5 +
.../model/actions/UpdateTriggerAction.java | 5 +
.../repository/TriggerRepository.java | 15 +
.../repository/TriggerRepositoryImpl.java | 192 ++++++
.../analytics/service/AnalyticsService.java | 15 +
.../service/AnalyticsServiceImpl.java | 38 ++
.../repository/TriggerRepositoryTest.java | 54 ++
.../service/TestAnalyticsService.java | 24 +
.../org/molgenis/emx2/sql/SqlDatabase.java | 8 +
backend/molgenis-emx2-webapi/build.gradle | 1 +
.../org/molgenis/emx2/web/AnalyticsApi.java | 114 ++++
.../java/org/molgenis/emx2/web/Constants.java | 4 +-
.../org/molgenis/emx2/web/GraphqlApi.java | 5 +
.../molgenis/emx2/web/MolgenisWebservice.java | 1 +
.../emx2/web/response/ResponseStatus.java | 3 +
.../molgenis/emx2/web/response/Status.java | 6 +
.../web/transformers/ActionTransformer.java | 18 +
.../web/transformers/JsonTransformer.java | 14 +
.../WebApiSmokeTests.java | 115 +++-
settings.gradle | 1 +
133 files changed, 3666 insertions(+), 249 deletions(-)
create mode 100644 apps/emx2-analytics/.gitignore
create mode 100644 apps/emx2-analytics/README.md
create mode 100644 apps/emx2-analytics/index.html
create mode 100644 apps/emx2-analytics/package.json
create mode 100644 apps/emx2-analytics/src/App.vue
create mode 100644 apps/emx2-analytics/src/lib/analytics.ts
create mode 100644 apps/emx2-analytics/src/lib/providers/siteImprove.ts
create mode 100644 apps/emx2-analytics/src/main.ts
create mode 100644 apps/emx2-analytics/src/style.css
create mode 100644 apps/emx2-analytics/src/types/Provider.ts
create mode 100644 apps/emx2-analytics/src/types/Trigger.ts
create mode 100644 apps/emx2-analytics/src/vite-env.d.ts
create mode 100644 apps/emx2-analytics/tsconfig.app.json
create mode 100644 apps/emx2-analytics/tsconfig.json
create mode 100644 apps/emx2-analytics/tsconfig.node.json
create mode 100644 apps/emx2-analytics/vite.config.ts
rename apps/nuxt3-ssr/components/table/{StickyTable.vue => Sticky.vue} (100%)
delete mode 100644 apps/nuxt3-ssr/middleware/analytics.global.ts
create mode 100644 apps/nuxt3-ssr/plugins/analytics.client.ts
create mode 100644 apps/nuxt3-ssr/server/routes/[schema]/api/trigger.ts
rename apps/{nuxt3-ssr => tailwind-components}/components/BreadCrumbs.vue (95%)
rename apps/{nuxt3-ssr => tailwind-components}/components/Button.vue (98%)
rename apps/{nuxt3-ssr => tailwind-components}/components/CardList.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/CardListItem.vue (100%)
rename apps/{nuxt3-ssr/components/content/ReadMore.vue => tailwind-components/components/ContentReadMore.vue} (100%)
rename apps/{nuxt3-ssr/components/footer => tailwind-components/components}/FooterComponent.vue (95%)
create mode 100644 apps/tailwind-components/components/Header.vue
rename apps/{nuxt3-ssr => tailwind-components}/components/HeaderButton.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/HeaderButtonMobile.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/Logo.vue (98%)
rename apps/{nuxt3-ssr => tailwind-components}/components/LogoMobile.vue (99%)
create mode 100644 apps/tailwind-components/components/Navigation.vue
rename apps/{nuxt3-ssr => tailwind-components}/components/PageHeader.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/SideModal.vue (95%)
rename apps/{nuxt3-ssr => tailwind-components}/components/Tab.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/Table.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/TableCell.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/TableHead.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/TableHeadRow.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/TableRow.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/ContentBlock.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/ContentBlockAttachedFiles.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/ContentBlockData.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/ContentBlockDescription.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/ContentBlockIntro.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/ContentBlockModal.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/ContentBlocks.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/ContentGenericItem.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/ContentGenericItemList.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/type/ContentTypeHyperLink.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/type/ContentTypeOntologyArray.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/type/ContentTypeRefBack.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/type/ContentTypeString.vue (100%)
rename apps/{nuxt3-ssr => tailwind-components}/components/content/type/ContentTypeText.vue (100%)
create mode 100644 apps/tailwind-components/components/table/TableEMX2.vue
create mode 100644 apps/tailwind-components/components/table/cellTypes/EMX2.vue
create mode 100644 apps/tailwind-components/composables/fetchMetadata.ts
create mode 100644 apps/tailwind-components/composables/fetchTableData.ts
create mode 100644 apps/tailwind-components/composables/fetchTableMetadata.ts
create mode 100644 apps/tailwind-components/pages/DataFetch.other.vue
create mode 100644 apps/tailwind-components/pages/FooterComponent.story.vue
create mode 100644 apps/tailwind-components/pages/Header.story.vue
create mode 100644 apps/tailwind-components/pages/Logo.story.vue
create mode 100644 apps/tailwind-components/pages/LogoMobile.story.vue
create mode 100644 apps/tailwind-components/pages/Navigation.story.vue
create mode 100644 apps/tailwind-components/pages/SideModal.story.vue
create mode 100644 apps/tailwind-components/pages/table/EMX2.story.vue
create mode 100644 apps/tailwind-components/plugins/floating-vue.ts
create mode 100644 apps/tailwind-components/server/routes/[schema]/api/trigger.ts
create mode 100644 apps/tailwind-components/server/routes/[schema]/graphql.ts
create mode 100644 apps/tailwind-components/server/routes/graphql.ts
create mode 100644 apps/tailwind-components/server/tsconfig.json
rename apps/{nuxt3-ssr/utils/queryLoader.ts => tailwind-components/utils/moduleToString.ts} (100%)
create mode 100644 apps/ui/.gitignore
create mode 100644 apps/ui/app.vue
create mode 100644 apps/ui/composables/session.ts
create mode 100644 apps/ui/layouts/default.vue
create mode 100644 apps/ui/middleware/adminOnly.ts
create mode 100644 apps/ui/nuxt.config.ts
create mode 100644 apps/ui/package.json
create mode 100644 apps/ui/pages/[schema]/[table]/index.vue
create mode 100644 apps/ui/pages/[schema]/analytics.vue
create mode 100644 apps/ui/pages/[schema]/index.vue
create mode 100644 apps/ui/pages/[schema]/schema.vue
create mode 100644 apps/ui/pages/account.vue
create mode 100644 apps/ui/pages/index.vue
create mode 100644 apps/ui/pages/login.vue
create mode 100644 apps/ui/public/favicon.ico
create mode 100644 apps/ui/server/routes/[schema]/api/trigger/[trigger].ts
create mode 100644 apps/ui/server/routes/[schema]/api/trigger/index.ts
create mode 100644 apps/ui/stores/metaStore.ts
create mode 100644 apps/ui/tsconfig.json
create mode 100644 backend/molgenis-emx2-analytics/build.gradle
create mode 100644 backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/Trigger.java
create mode 100644 backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/CreateTriggerAction.java
create mode 100644 backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/DeleteTriggerAction.java
create mode 100644 backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/UpdateTriggerAction.java
create mode 100644 backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/repository/TriggerRepository.java
create mode 100644 backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/repository/TriggerRepositoryImpl.java
create mode 100644 backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/service/AnalyticsService.java
create mode 100644 backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/service/AnalyticsServiceImpl.java
create mode 100644 backend/molgenis-emx2-analytics/src/test/java/org/molgenis/emx2/analytics/repository/TriggerRepositoryTest.java
create mode 100644 backend/molgenis-emx2-analytics/src/test/java/org/molgenis/emx2/analytics/service/TestAnalyticsService.java
create mode 100644 backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/AnalyticsApi.java
create mode 100644 backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/response/ResponseStatus.java
create mode 100644 backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/response/Status.java
create mode 100644 backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/transformers/ActionTransformer.java
create mode 100644 backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/transformers/JsonTransformer.java
diff --git a/apps/build.gradle b/apps/build.gradle
index 11fd5ceeee..a8e5319909 100644
--- a/apps/build.gradle
+++ b/apps/build.gradle
@@ -70,10 +70,11 @@ subprojects { subproject ->
dependsOn ":apps:${match[1]}:buildJavascript";
}
//run the build
- args = ['run', 'build']
+ def command = packageJson.contains("\"name\": \"ui\"") ? "generate" : "build"
+ args = ['run', command]
doLast {
copy {
- from "dist"
+ from packageJson.contains("\"name\": \"ui\"") ? ".output/public" : "dist"
into "${parent.buildDir}/generated/main/resources/public_html/apps/" + project.name
}
}
diff --git a/apps/catalogue.code-workspace b/apps/catalogue.code-workspace
index a0703163a8..e5ab0ae728 100644
--- a/apps/catalogue.code-workspace
+++ b/apps/catalogue.code-workspace
@@ -5,6 +5,12 @@
},
{
"path": "tailwind-components"
+ },
+ {
+ "path": "ui"
+ },
+ {
+ "path": "metadata-utils"
}
],
"settings": {}
diff --git a/apps/dev-proxy.config.js b/apps/dev-proxy.config.js
index a13c15b4f1..94e36f0c26 100644
--- a/apps/dev-proxy.config.js
+++ b/apps/dev-proxy.config.js
@@ -20,6 +20,10 @@ module.exports = {
target: HOST,
...opts,
},
+ "^/[a-zA-Z0-9_.%-]+/api/trigger": {
+ target: HOST,
+ ...opts,
+ },
"/api": {
target: `${HOST}/api`,
...opts,
diff --git a/apps/emx2-analytics/.gitignore b/apps/emx2-analytics/.gitignore
new file mode 100644
index 0000000000..a547bf36d8
--- /dev/null
+++ b/apps/emx2-analytics/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/apps/emx2-analytics/README.md b/apps/emx2-analytics/README.md
new file mode 100644
index 0000000000..29d1c45bb5
--- /dev/null
+++ b/apps/emx2-analytics/README.md
@@ -0,0 +1,49 @@
+# Analytics module
+
+Goal of the module is to allow service maintainers to track certain user actions.
+For example maintainer of a Data Catalogue application might what to know when and by who a 'Contact' button is clicked.
+
+This module when installed sets up 'Triggers' attched to page elements ( for example a Button in a given location).
+Besides the need to install and configure this module in a app ( or compoment ),
+the triggers ( what events to run when ) should be created using the analytics api or web ui.
+
+### Install the module
+
+use package manager to install the module '@molgenis/emx2-analytics'
+
+```yarn add @molgenis/emx2-analytics``` (or use the * option for yarn workspaces)
+
+### Setup the triggers in the app
+```import { setupAnalytics } from "@molgenis/emx2-analytics"; ```
+
+...
+
+``` setupAnalytics(schema, providers);```
+
+schema: The name of the emx schema/database
+
+providers: A list of analytics profiders and there config options, for example; ```providers = [{ id: "site-improve", options: { analyticsKey } }];```
+
+This setup should be run before the user interacts with the page
+
+During the setupAnalytics call 3 steps are taken
+ - 1. For each provider the nessasary code is (fetched and) loaded
+ - 2. The triggers configured for this schema are fetched from the backend
+ - 3. For each triggers the DOM elements are located in the page and a eventhandler gets attached for the configured provider
+
+ When the end user visits the page and triggers the event the attached eventhandler uses the provider script to send the event.
+
+ The whole analyics module works an a fire-and-forget basis, if something goes wrong the end user is not notified ( except for the browser console error log)
+
+
+## Development
+
+includes playground 'app', run via `yarn dev`
+
+## Build
+
+`yarn build`
+
+## Release
+
+todo
\ No newline at end of file
diff --git a/apps/emx2-analytics/index.html b/apps/emx2-analytics/index.html
new file mode 100644
index 0000000000..143557b528
--- /dev/null
+++ b/apps/emx2-analytics/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + Vue + TS
+
+
+
+
+
+
diff --git a/apps/emx2-analytics/package.json b/apps/emx2-analytics/package.json
new file mode 100644
index 0000000000..395949ed2a
--- /dev/null
+++ b/apps/emx2-analytics/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "@molgenis/emx2-analytics",
+ "description": "Molgenis EMX2 Analytics, client module",
+ "license": "LGPL-3.0-or-later",
+ "private": false,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vue-tsc -b && vite build",
+ "preview": "vite preview",
+ "format": "prettier src --write --config ../.prettierrc.js",
+ "checkFormat": "prettier src --check --config ../.prettierrc.js"
+ },
+ "dependencies": {
+ "vue": "^3.4.29"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.0.5",
+ "prettier": "2.8.8",
+ "typescript": "^5.2.2",
+ "vite": "^5.3.1",
+ "vite-plugin-dts": "^3.9.1",
+ "vue-tsc": "^2.0.21"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "files": [
+ "dist",
+ "src",
+ "package.json",
+ "README.md"
+ ],
+ "main": "./dist/analytics.umd.js",
+ "module": "./dist/analytics.es.js",
+ "typings": "./dist/lib/**/*.d.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/analytics.es.js",
+ "require": "./dist/analytics.umd.js"
+ },
+ "./dist/style.css": {
+ "import": "./dist/style.css",
+ "require": "./dist/style.css"
+ }
+ }
+}
diff --git a/apps/emx2-analytics/src/App.vue b/apps/emx2-analytics/src/App.vue
new file mode 100644
index 0000000000..e9221f9995
--- /dev/null
+++ b/apps/emx2-analytics/src/App.vue
@@ -0,0 +1,176 @@
+
+
+
+ Analytics
+
+
+
+
Config
+ Analytics key
+
+
+ (re) run setup
+ {{ analyticsKey }}
+
+
+
Add Trigger
+ Event id
+
+ Dom element css selector
+
+ Add Trigger
+ {{ addTriggerError }}
+
+
+
+
Triggers for {{ schemaName }}
+
+ No trigger setup
+
+
+
+ {{ trigger.name }}
+ {{ trigger.cssSelector }}
+
+ App
+ {{ trigger.appName }}
+
+
+
+
+
+
+
+ Test Button
+
+
+
+
+
diff --git a/apps/emx2-analytics/src/lib/analytics.ts b/apps/emx2-analytics/src/lib/analytics.ts
new file mode 100644
index 0000000000..9b0a036a33
--- /dev/null
+++ b/apps/emx2-analytics/src/lib/analytics.ts
@@ -0,0 +1,64 @@
+import { Provider, siteImproveOptions } from "../types/Provider";
+import { Trigger } from "../types/Trigger";
+import {
+ handleEvent as siteImprove,
+ initialize as siteImproveInit,
+} from "./providers/siteImprove";
+
+function setupAnalytics(schemaName: string, providers: Provider[]) {
+ for (let provider of providers) {
+ switch (
+ provider.id // todo explore fancy dynamic import instead of switch
+ ) {
+ case "site-improve":
+ siteImproveInit(provider.options as siteImproveOptions);
+ break;
+ default:
+ console.error(`Provider ${provider} not supported`);
+ }
+ }
+
+ fetch(`/${schemaName}/api/trigger`)
+ .then((response) => {
+ response.json().then((data) => {
+ data.forEach((trigger: Trigger) => {
+ try {
+ const elements = document.querySelectorAll(trigger.cssSelector);
+ elements.forEach((element) => {
+ console.log(
+ `add trigger for ${trigger.name} to ${element.nodeName}`
+ );
+ element.addEventListener("click", (e) => {
+ for (let provider of providers) {
+ handleEvent(e, trigger, element, provider);
+ }
+ });
+ });
+ } catch (e) {
+ console.error("Failed to select elements for trigger", trigger);
+ console.error(`Error: ${e} for ${trigger.name}`);
+ }
+ });
+ });
+ })
+ .catch((error) => {
+ console.error("Error:", error);
+ });
+}
+
+function handleEvent(
+ event: Event,
+ trigger: Trigger,
+ element: Element,
+ provider: Provider
+) {
+ switch (provider.id) {
+ case "site-improve":
+ siteImprove(event, trigger, element);
+ break;
+ default:
+ console.error(`Provider ${provider.id} not supported`);
+ }
+}
+
+export { setupAnalytics };
diff --git a/apps/emx2-analytics/src/lib/providers/siteImprove.ts b/apps/emx2-analytics/src/lib/providers/siteImprove.ts
new file mode 100644
index 0000000000..3667ef106c
--- /dev/null
+++ b/apps/emx2-analytics/src/lib/providers/siteImprove.ts
@@ -0,0 +1,38 @@
+import { siteImproveOptions } from "../../types/Provider";
+import { Trigger } from "../../types/Trigger";
+
+declare const _sz: any;
+
+function initialize(options: siteImproveOptions) {
+ const scriptSrc = `https://siteimproveanalytics.com/js/siteanalyze_${options.analyticsKey}.js`;
+ if (!scriptLoaded(scriptSrc)) {
+ // Load the script in scoped script context ( due to cors restrictions)
+ const script = document.createElement("script");
+ script.async = true;
+ script.src = scriptSrc;
+ document.body.appendChild(script);
+ console.log("site improve script loaded");
+ }
+}
+
+function handleEvent(event: Event, trigger: Trigger, element: Element) {
+ console.log("handel site improve event", event, trigger, element.tagName);
+
+ if (_sz) {
+ _sz.push(["event", "demo cat", "demo action", trigger.name]);
+ } else {
+ console.error("site improve not loaded");
+ }
+}
+
+function scriptLoaded(scriptSrc: string) {
+ const scripts = document.querySelectorAll("script[src]");
+ const regex = new RegExp(`^${scriptSrc}`);
+ return Boolean(
+ Object.values(scripts).filter((value) =>
+ regex.test((value as HTMLScriptElement).src)
+ ).length
+ );
+}
+
+export { initialize, handleEvent };
diff --git a/apps/emx2-analytics/src/main.ts b/apps/emx2-analytics/src/main.ts
new file mode 100644
index 0000000000..3c9bfeb788
--- /dev/null
+++ b/apps/emx2-analytics/src/main.ts
@@ -0,0 +1,5 @@
+import { createApp } from "vue";
+import "./style.css";
+import App from "./App.vue";
+
+createApp(App).mount("#app");
diff --git a/apps/emx2-analytics/src/style.css b/apps/emx2-analytics/src/style.css
new file mode 100644
index 0000000000..bb131d6b8f
--- /dev/null
+++ b/apps/emx2-analytics/src/style.css
@@ -0,0 +1,79 @@
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+ padding: 2em;
+}
+
+#app {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/apps/emx2-analytics/src/types/Provider.ts b/apps/emx2-analytics/src/types/Provider.ts
new file mode 100644
index 0000000000..2463255787
--- /dev/null
+++ b/apps/emx2-analytics/src/types/Provider.ts
@@ -0,0 +1,12 @@
+export type providerId = "site-improve";
+
+export interface Provider {
+ id: providerId;
+ options: ProviderOptions;
+}
+
+export interface ProviderOptions {}
+
+export interface siteImproveOptions extends ProviderOptions {
+ analyticsKey: string;
+}
diff --git a/apps/emx2-analytics/src/types/Trigger.ts b/apps/emx2-analytics/src/types/Trigger.ts
new file mode 100644
index 0000000000..016184e65b
--- /dev/null
+++ b/apps/emx2-analytics/src/types/Trigger.ts
@@ -0,0 +1,6 @@
+export interface Trigger {
+ name: string;
+ cssSelector: string;
+ schemaName: string;
+ appName?: string;
+}
diff --git a/apps/emx2-analytics/src/vite-env.d.ts b/apps/emx2-analytics/src/vite-env.d.ts
new file mode 100644
index 0000000000..11f02fe2a0
--- /dev/null
+++ b/apps/emx2-analytics/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/apps/emx2-analytics/tsconfig.app.json b/apps/emx2-analytics/tsconfig.app.json
new file mode 100644
index 0000000000..b8e9723f5f
--- /dev/null
+++ b/apps/emx2-analytics/tsconfig.app.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "preserve",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
+}
diff --git a/apps/emx2-analytics/tsconfig.json b/apps/emx2-analytics/tsconfig.json
new file mode 100644
index 0000000000..fcd80ad4d5
--- /dev/null
+++ b/apps/emx2-analytics/tsconfig.json
@@ -0,0 +1,35 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "types": ["vite/client", "node"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "preserve",
+ "baseUrl": "./",
+ "paths": {
+ "~/*": ["src/*"]
+ }
+ },
+
+ "exclude": ["node_modules"],
+ "files": [],
+ "references": [
+ {
+ "path": "./tsconfig.app.json"
+ },
+ {
+ "path": "./tsconfig.node.json"
+ }
+ ],
+
+}
diff --git a/apps/emx2-analytics/tsconfig.node.json b/apps/emx2-analytics/tsconfig.node.json
new file mode 100644
index 0000000000..3afdd6e384
--- /dev/null
+++ b/apps/emx2-analytics/tsconfig.node.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "noEmit": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/apps/emx2-analytics/vite.config.ts b/apps/emx2-analytics/vite.config.ts
new file mode 100644
index 0000000000..6de34891fb
--- /dev/null
+++ b/apps/emx2-analytics/vite.config.ts
@@ -0,0 +1,21 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import path from 'path'; // Import the 'path' module
+import dts from 'vite-plugin-dts'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [vue(), dts()],
+ server: {
+ proxy: require("../dev-proxy.config"),
+ },
+ build: {
+ lib: {
+ entry: path.resolve(__dirname, "src/lib/analytics.ts"),
+ name: "analytics",
+ fileName: (format) => `analytics.${format}.js`,
+ },
+ sourcemap: true,
+ emptyOutDir: true,
+ },
+});
diff --git a/apps/nuxt3-ssr/components/CollectionEventDisplay.vue b/apps/nuxt3-ssr/components/CollectionEventDisplay.vue
index 0dc1568909..714435c231 100644
--- a/apps/nuxt3-ssr/components/CollectionEventDisplay.vue
+++ b/apps/nuxt3-ssr/components/CollectionEventDisplay.vue
@@ -1,6 +1,5 @@
+
+
+
+
+
+
+
+ Home
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Components
+
+
+ {{
+ story.name
+ }}
+
+
+
+
+
+
+ Other
+ Data fetching
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/nuxt3-ssr/components/BreadCrumbs.vue b/apps/tailwind-components/components/BreadCrumbs.vue
similarity index 95%
rename from apps/nuxt3-ssr/components/BreadCrumbs.vue
rename to apps/tailwind-components/components/BreadCrumbs.vue
index 0d5a218942..f10d32a751 100644
--- a/apps/nuxt3-ssr/components/BreadCrumbs.vue
+++ b/apps/tailwind-components/components/BreadCrumbs.vue
@@ -1,4 +1,3 @@
-
+
+
+
+
+ {{ button.label }}
+
+
+
+
+ More
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/nuxt3-ssr/components/PageHeader.vue b/apps/tailwind-components/components/PageHeader.vue
similarity index 100%
rename from apps/nuxt3-ssr/components/PageHeader.vue
rename to apps/tailwind-components/components/PageHeader.vue
diff --git a/apps/nuxt3-ssr/components/SideModal.vue b/apps/tailwind-components/components/SideModal.vue
similarity index 95%
rename from apps/nuxt3-ssr/components/SideModal.vue
rename to apps/tailwind-components/components/SideModal.vue
index 0c89c22aca..dfc994f6f1 100644
--- a/apps/nuxt3-ssr/components/SideModal.vue
+++ b/apps/tailwind-components/components/SideModal.vue
@@ -1,5 +1,5 @@
diff --git a/apps/tailwind-components/components/table/TableEMX2.vue b/apps/tailwind-components/components/table/TableEMX2.vue
new file mode 100644
index 0000000000..264daeb6da
--- /dev/null
+++ b/apps/tailwind-components/components/table/TableEMX2.vue
@@ -0,0 +1,35 @@
+
+
+
+
diff --git a/apps/tailwind-components/components/table/cellTypes/EMX2.vue b/apps/tailwind-components/components/table/cellTypes/EMX2.vue
new file mode 100644
index 0000000000..9a9634ced3
--- /dev/null
+++ b/apps/tailwind-components/components/table/cellTypes/EMX2.vue
@@ -0,0 +1,25 @@
+
+
+
+ {{ data }}
+
+
+
+ {{ typeof data }}
+
+
diff --git a/apps/tailwind-components/composables/fetchMetadata.ts b/apps/tailwind-components/composables/fetchMetadata.ts
new file mode 100644
index 0000000000..3c728a02f9
--- /dev/null
+++ b/apps/tailwind-components/composables/fetchMetadata.ts
@@ -0,0 +1,40 @@
+import { StorageSerializers, useSessionStorage } from "@vueuse/core";
+
+import metadataGql from "../../nuxt3-ssr/gql/metadata";
+import { type ISchemaMetaData } from "../../meta-data-utils/src/types";
+
+const query = moduleToString(metadataGql);
+
+export default async (schemaId: string): Promise => {
+ // Use sessionStorage to cache data
+ const cached = useSessionStorage(schemaId, null, {
+ serializer: StorageSerializers.object,
+ });
+
+ if (!cached.value) {
+ const { data } = await $fetch(`/${schemaId}/graphql`, {
+ method: "POST",
+ body: {
+ query,
+ },
+ }).catch((error) => {
+ console.error(`Could not fetch metadata for schema ${schemaId}, `, error);
+ throw createError({
+ ...error,
+ statusMessage: `Could not fetch metadata for schema ${schemaId}`,
+ });
+ });
+
+
+ console.log(`Fetching metadata for schema ${schemaId}`);
+
+
+
+ // Update the cache
+ cached.value = data._schema;
+ } else {
+ console.log(`Getting value from cache for schema ${schemaId}`);
+ }
+
+ return cached.value;
+};
diff --git a/apps/tailwind-components/composables/fetchTableData.ts b/apps/tailwind-components/composables/fetchTableData.ts
new file mode 100644
index 0000000000..f1008251a5
--- /dev/null
+++ b/apps/tailwind-components/composables/fetchTableData.ts
@@ -0,0 +1,114 @@
+
+
+import { type ITableMetaData } from "../../meta-data-utils/src/types";
+import { type IQueryMetaData } from "../../molgenis-components/src/client/IQueryMetaData";
+
+export interface ITableDataResponse {
+ rows: Record[];
+ count: number;
+}
+
+
+export default async (schemaId: string, tableId: string, properties?: IQueryMetaData,): Promise => {
+
+ const limit = properties?.limit ? properties?.limit : 20;
+ const offset = properties?.offset ? properties?.offset : 0;
+ const expandLevel =
+ properties?.expandLevel || properties?.expandLevel == 0
+ ? properties?.expandLevel
+ : 2;
+ const search = properties?.searchTerms
+ ? ',search:"' + properties?.searchTerms.trim() + '"'
+ : "";
+
+ const columnIds = await getColumnIds(schemaId, tableId, expandLevel);
+ const query = `query ${tableId}( $filter:${tableId}Filter, $orderby:${tableId}orderby ) {
+ ${tableId}(
+ filter:$filter,
+ limit:${limit},
+ offset:${offset}${search},
+ orderby:$orderby
+ )
+ {
+ ${columnIds}
+ }
+ ${tableId}_agg( filter:$filter${search} ) {
+ count
+ }
+ }`;
+
+ const filter = properties?.filter ? properties?.filter : {};
+ const orderby = properties?.orderby ? properties?.orderby : {};
+
+ const { data } = await $fetch(`/${schemaId}/graphql`, {
+ method: "POST",
+ body: {
+ query,
+ variables: { filter, orderby },
+ },
+ }).catch((error) => {
+ const message = `Could not fetch data for table ${tableId} in schema ${schemaId}`;
+ console.error(message, error);
+ throw createError({
+ ...error,
+ statusMessage: message,
+ });
+ });
+
+
+ console.log(`Fetching data for table ${tableId} schema ${schemaId}`);
+
+ return {rows: data[tableId], count: data[`${tableId}_agg`].count};
+};
+
+export const getColumnIds = async (
+ schemaId: string,
+ tableId: string,
+ //allows expansion of ref fields to add their next layer of details.
+ expandLevel: number,
+ //rootLevel
+ rootLevel = true
+) => {
+ const metaData = await fetchMetadata(schemaId);
+
+ const columns = metaData.tables.find(
+ (table) => table.id === tableId && table.schemaId === schemaId
+ )?.columns || [];
+
+ let gqlFields = "";
+ for (const col of columns) {
+ //we always expand the subfields of key, but other 'ref' fields only if they do not break server
+ if (expandLevel > 0 || col.key) {
+ if (
+ !rootLevel &&
+ ["REF_ARRAY", "REFBACK", "ONTOLOGY_ARRAY"].includes(col.columnType)
+ ) {
+ //skip
+ } else if (["REF", "REF_ARRAY", "REFBACK"].includes(col.columnType)) {
+ gqlFields =
+ gqlFields +
+ " " +
+ col.id +
+ " {" +
+ (await getColumnIds(
+ col.refSchemaId || schemaId,
+ col.refTableId || tableId,
+ //indicate that sub queries should not be expanded on ref_array, refback, ontology_array
+ expandLevel - 1,
+ false
+ )) +
+ " }";
+ } else if (["ONTOLOGY", "ONTOLOGY_ARRAY"].includes(col.columnType)) {
+ gqlFields = gqlFields + " " + col.id + " {name, label}";
+ } else if (col.columnType === "FILE") {
+ gqlFields += ` ${col.id} { id, size, filename, extension, url }`;
+ } else if (col.columnType !== "HEADING") {
+ gqlFields += ` ${col.id}`;
+ }
+ }
+ }
+
+ return gqlFields;
+};
+
+
diff --git a/apps/tailwind-components/composables/fetchTableMetadata.ts b/apps/tailwind-components/composables/fetchTableMetadata.ts
new file mode 100644
index 0000000000..0160ca8246
--- /dev/null
+++ b/apps/tailwind-components/composables/fetchTableMetadata.ts
@@ -0,0 +1,7 @@
+import type { ITableMetaData } from "../../meta-data-utils/src/types";
+
+export default async (schemaId: string, tableId: string): Promise => {
+ const schemaMetadata = await fetchMetadata(schemaId);
+ const tableMetadata = schemaMetadata.tables.find((table) => table.id === tableId);
+ return tableMetadata || Promise.reject(`Table ${tableId} not found in schema ${schemaId}`);
+}
\ No newline at end of file
diff --git a/apps/tailwind-components/nuxt.config.ts b/apps/tailwind-components/nuxt.config.ts
index 3083a5ffd1..55ac9befef 100644
--- a/apps/tailwind-components/nuxt.config.ts
+++ b/apps/tailwind-components/nuxt.config.ts
@@ -8,9 +8,9 @@ export default defineNuxtConfig({
},
ssr: process.env.NUXT_PUBLIC_IS_SSR === 'false' ? false : true,
router: {
- options: process.env.NUXT_PUBLIC_IS_SSR === 'false' ?{
+ options: process.env.NUXT_PUBLIC_IS_SSR === 'false' ? {
hashMode: true
- } : {}
+ } : {}
},
nitro: {
prerender: {
@@ -34,5 +34,10 @@ export default defineNuxtConfig({
pathPrefix: false
},
"~/components",
- ]
+ ],
+ runtimeConfig: {
+ public: {
+ apiBase: "https://emx2.dev.molgenis.org/", // "http://localhost:8080/",
+ },
+ },
})
diff --git a/apps/tailwind-components/pages/DataFetch.other.vue b/apps/tailwind-components/pages/DataFetch.other.vue
new file mode 100644
index 0000000000..2507caccb7
--- /dev/null
+++ b/apps/tailwind-components/pages/DataFetch.other.vue
@@ -0,0 +1,123 @@
+
+
+
+ Data and Meta data
+
+
Params
+
+ schema id:
+
+
+ {{ option }}
+
+
+
+
+ table id:
+
+
+ {{ option }}
+
+
+
+
+
+ Loading...
+ Error: {{ error }}
+ Meta data Error: {{ metadataError }}
+
+
+
Data for {{ tableId }}:
+ {{ error }}
+
{{ tableData }}
+
+
Schema: {{ metadata.label }}
+
Tables:
+
+
+
+ {{ table.id }} (type: {{ table.tableType }})
+ Columns:
+
+
+ {{ column.id }} (type: {{ column.columnType }})
+
+
+
+
+
+
diff --git a/apps/tailwind-components/pages/FooterComponent.story.vue b/apps/tailwind-components/pages/FooterComponent.story.vue
new file mode 100644
index 0000000000..d12fbc6212
--- /dev/null
+++ b/apps/tailwind-components/pages/FooterComponent.story.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
+ I am in a slot
+
diff --git a/apps/tailwind-components/pages/Header.story.vue b/apps/tailwind-components/pages/Header.story.vue
new file mode 100644
index 0000000000..aae11944ea
--- /dev/null
+++ b/apps/tailwind-components/pages/Header.story.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/tailwind-components/pages/Logo.story.vue b/apps/tailwind-components/pages/Logo.story.vue
new file mode 100644
index 0000000000..d67554cb96
--- /dev/null
+++ b/apps/tailwind-components/pages/Logo.story.vue
@@ -0,0 +1,3 @@
+
+
+
diff --git a/apps/tailwind-components/pages/LogoMobile.story.vue b/apps/tailwind-components/pages/LogoMobile.story.vue
new file mode 100644
index 0000000000..b7b40d48ed
--- /dev/null
+++ b/apps/tailwind-components/pages/LogoMobile.story.vue
@@ -0,0 +1,3 @@
+
+
+
diff --git a/apps/tailwind-components/pages/Navigation.story.vue b/apps/tailwind-components/pages/Navigation.story.vue
new file mode 100644
index 0000000000..ea331b804c
--- /dev/null
+++ b/apps/tailwind-components/pages/Navigation.story.vue
@@ -0,0 +1,9 @@
+
+
+
diff --git a/apps/tailwind-components/pages/SideModal.story.vue b/apps/tailwind-components/pages/SideModal.story.vue
new file mode 100644
index 0000000000..8758e547b1
--- /dev/null
+++ b/apps/tailwind-components/pages/SideModal.story.vue
@@ -0,0 +1,86 @@
+
+
+
+
+ Open side modal 🙈
+
+
+
+ Props
+
+
+
+ full-screen
+
+
+
+
+
+ slide-in-right
+
+
+
+
+ left
+ center
+ right
+
+
+ buttonAlignment
+
+
+
+
+ {{ fullScreen }}
+
+
+
+ Good resolutions are useless attempts to interfere with scientific laws.
+ Their origin is pure vanity. Their result is absolutely nil. They give
+ us, now and then, some of those luxurious sterile emotions that have a
+ certain charm for the weak. That is all that can be said for them. They
+ are simply cheques that men draw on a bank where they have no account.
+
+
+
+
diff --git a/apps/tailwind-components/pages/input/String.story.vue b/apps/tailwind-components/pages/input/String.story.vue
index 083b1b04dc..f48cf175e8 100644
--- a/apps/tailwind-components/pages/input/String.story.vue
+++ b/apps/tailwind-components/pages/input/String.story.vue
@@ -1,6 +1,7 @@
@@ -11,18 +12,23 @@ const placeholder = "'A-001' or 'A-001,A-002'";
types are not only text, but date, email, number, password, search, etc.
However, checkbox and radio types are in a separate component.
+
-
Input String: default use
+ Input String: default use (model value: {{ demoValue }})
{{ label }}
-
+
-
Input String: disabled
+ Input String: disabled (model value: {{ demoValue }})
{{ label }}
@@ -30,10 +36,11 @@ const placeholder = "'A-001' or 'A-001,A-002'";
id="input-string-disabled"
:placeholder="placeholder"
:disabled="true"
+ v-model="demoValue"
/>
-
Input Text: as a required field
+ Input Text: as a required field (model value: {{ demoValue }})
{{ label }}
@@ -41,28 +48,47 @@ const placeholder = "'A-001' or 'A-001,A-002'";
id="input-string-required"
:label="label"
:placeholder="placeholder"
+ v-model="demoValue"
/>
-
Input String: positive validation result
+
+ Input String: positive validation result (model value: {{ demoValue }})
+
{{ label }}
-
+
-
Input String: negative validation result
+
+ Input String: negative validation result (model value: {{ demoValue }})
+
{{ label }}
-
+
-
Input String: visually hidden label
+
+ Input String: visually hidden label (model value: {{ demoValue }})
+
{{ label }}
-
+
diff --git a/apps/tailwind-components/pages/table/EMX2.story.vue b/apps/tailwind-components/pages/table/EMX2.story.vue
new file mode 100644
index 0000000000..fbeeb7b44a
--- /dev/null
+++ b/apps/tailwind-components/pages/table/EMX2.story.vue
@@ -0,0 +1,120 @@
+
+
+
+
+
Params
+
+ schema id:
+
+
+ {{ option }}
+
+
+
+
+ table id:
+
+
+ {{ option }}
+
+
+
+
+
+
+
+d
diff --git a/apps/tailwind-components/plugins/floating-vue.ts b/apps/tailwind-components/plugins/floating-vue.ts
new file mode 100644
index 0000000000..caf454615c
--- /dev/null
+++ b/apps/tailwind-components/plugins/floating-vue.ts
@@ -0,0 +1,7 @@
+import { defineNuxtPlugin } from "#app";
+import FloatingVue from "floating-vue";
+import "floating-vue/dist/style.css";
+
+export default defineNuxtPlugin(({ vueApp }) => {
+ vueApp.use(FloatingVue);
+});
diff --git a/apps/tailwind-components/server/routes/[schema]/api/trigger.ts b/apps/tailwind-components/server/routes/[schema]/api/trigger.ts
new file mode 100644
index 0000000000..549230b991
--- /dev/null
+++ b/apps/tailwind-components/server/routes/[schema]/api/trigger.ts
@@ -0,0 +1,9 @@
+import { joinURL } from "ufo";
+export default defineEventHandler((event) => {
+ const config = useRuntimeConfig(event);
+ console.log("proxy trigger request : ", event.path);
+ const schema = getRouterParam(event, "schema") || "";
+ const location = joinURL(config.public.apiBase, schema, "api", "trigger")
+ console.log("to : ", location);
+ return proxyRequest(event, location);
+});
diff --git a/apps/tailwind-components/server/routes/[schema]/graphql.ts b/apps/tailwind-components/server/routes/[schema]/graphql.ts
new file mode 100644
index 0000000000..75ddf4c0a7
--- /dev/null
+++ b/apps/tailwind-components/server/routes/[schema]/graphql.ts
@@ -0,0 +1,9 @@
+import { joinURL } from "ufo";
+export default defineEventHandler((event) => {
+ const config = useRuntimeConfig(event);
+ console.log("proxy schema gql request : ", event.path);
+ const schema = getRouterParam(event, "schema") || "";
+ console.log("to : ", joinURL(config.public.apiBase, schema, "graphql"));
+ const target = joinURL(config.public.apiBase, schema,"graphql");
+ return proxyRequest(event, target);
+});
diff --git a/apps/tailwind-components/server/routes/graphql.ts b/apps/tailwind-components/server/routes/graphql.ts
new file mode 100644
index 0000000000..bf2b865411
--- /dev/null
+++ b/apps/tailwind-components/server/routes/graphql.ts
@@ -0,0 +1,9 @@
+import { joinURL } from "ufo";
+export default defineEventHandler((event) => {
+ const config = useRuntimeConfig(event);
+ console.log("proxy gql request : ", event.path);
+ const schema = getRouterParam(event, "schema") || "";
+ console.log("to : ", joinURL(config.public.apiBase, "graphql"));
+ const target = joinURL(config.public.apiBase, "api/graphql");
+ return proxyRequest(event, target);
+});
diff --git a/apps/tailwind-components/server/tsconfig.json b/apps/tailwind-components/server/tsconfig.json
new file mode 100644
index 0000000000..b9ed69c19e
--- /dev/null
+++ b/apps/tailwind-components/server/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../.nuxt/tsconfig.server.json"
+}
diff --git a/apps/tailwind-components/types/types.ts b/apps/tailwind-components/types/types.ts
index 71bcf8c0be..a79c1817c6 100644
--- a/apps/tailwind-components/types/types.ts
+++ b/apps/tailwind-components/types/types.ts
@@ -4,5 +4,26 @@ export interface INode {
}
export interface ITreeNode extends INode {
- children: ITreeNode[];
+ children:
+ ITreeNode[];
}
+
+export type ButtonType =
+ | "primary"
+ | "secondary"
+ | "tertiary"
+ | "outline"
+ | "disabled"
+ | "filterWell";
+
+export type ButtonSize = "tiny" | "small" | "medium" | "large";
+
+export type ButtonIconPosition = "left" | "right";
+
+export type INotificationType =
+ | "light"
+ | "dark"
+ | "success"
+ | "error"
+ | "warning"
+ | "info";
\ No newline at end of file
diff --git a/apps/nuxt3-ssr/utils/queryLoader.ts b/apps/tailwind-components/utils/moduleToString.ts
similarity index 100%
rename from apps/nuxt3-ssr/utils/queryLoader.ts
rename to apps/tailwind-components/utils/moduleToString.ts
diff --git a/apps/ui/.gitignore b/apps/ui/.gitignore
new file mode 100644
index 0000000000..4a7f73a2ed
--- /dev/null
+++ b/apps/ui/.gitignore
@@ -0,0 +1,24 @@
+# Nuxt dev/build outputs
+.output
+.data
+.nuxt
+.nitro
+.cache
+dist
+
+# Node dependencies
+node_modules
+
+# Logs
+logs
+*.log
+
+# Misc
+.DS_Store
+.fleet
+.idea
+
+# Local env files
+.env
+.env.*
+!.env.example
diff --git a/apps/ui/app.vue b/apps/ui/app.vue
new file mode 100644
index 0000000000..f8eacfa737
--- /dev/null
+++ b/apps/ui/app.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/apps/ui/composables/session.ts b/apps/ui/composables/session.ts
new file mode 100644
index 0000000000..91e5f8812a
--- /dev/null
+++ b/apps/ui/composables/session.ts
@@ -0,0 +1,19 @@
+export const useSession = () => {
+ return useAsyncData("session", async () => {
+ console.log("init useSession");
+
+ const { data, error } = await $fetch("/api/graphql", {
+ method: "POST",
+ body: JSON.stringify({
+ query: `{_session { email, roles, token }}`,
+ }),
+ });
+
+ if (error) {
+ console.error("Error fetching session", error);
+ return null;
+ }
+
+ return data._session;
+ });
+};
diff --git a/apps/ui/layouts/default.vue b/apps/ui/layouts/default.vue
new file mode 100644
index 0000000000..b180933369
--- /dev/null
+++ b/apps/ui/layouts/default.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/middleware/adminOnly.ts b/apps/ui/middleware/adminOnly.ts
new file mode 100644
index 0000000000..0461252ba1
--- /dev/null
+++ b/apps/ui/middleware/adminOnly.ts
@@ -0,0 +1,7 @@
+export default defineNuxtRouteMiddleware(async (to, from) => {
+ const {data: session} = await useSession();
+ const isAdmin = computed(() => session.value.email === "admin");
+ if (!isAdmin.value) {
+ return navigateTo('/login')
+ }
+ })
\ No newline at end of file
diff --git a/apps/ui/nuxt.config.ts b/apps/ui/nuxt.config.ts
new file mode 100644
index 0000000000..25efffe248
--- /dev/null
+++ b/apps/ui/nuxt.config.ts
@@ -0,0 +1,33 @@
+// https://nuxt.com/docs/api/configuration/nuxt-config
+export default defineNuxtConfig({
+ extends: ["../tailwind-components"],
+ ssr: false,
+ devtools: { enabled: true },
+
+ tailwindcss: {
+ cssPath: '../tailwind-components/assets/css/main.css',
+ configPath: '../tailwind-components/tailwind.config.js'
+ },
+
+ // runtimeConfig: {
+ // public: {
+ // apiBase: "https://emx2.dev.molgenis.org/"
+ // // apiBase: "http://localhost:8080/"
+ // },
+ // },
+ vite: {
+ base: "."
+ },
+
+ modules: [ '@pinia/nuxt' ],
+
+ components: [
+ {
+ path: "../tailwind-components/components",
+ },
+ {
+ path: "../tailwind-components/components/global/icons",
+ global: true,
+ },
+ ],
+})
\ No newline at end of file
diff --git a/apps/ui/package.json b/apps/ui/package.json
new file mode 100644
index 0000000000..9a058a5262
--- /dev/null
+++ b/apps/ui/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "ui",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "nuxt build",
+ "dev": "nuxt dev",
+ "generate": "cross-env NUXT_APP_BASE_URL='/apps/ui/' nuxt generate",
+ "preview": "nuxt preview",
+ "postinstall": "nuxt prepare",
+ "format": "prettier app.vue layouts pages composables --write --config ../.prettierrc.js",
+ "checkFormat": "prettier app.vue layouts pages composables --check --config ../.prettierrc.js"
+ },
+ "dependencies": {
+ "@pinia/nuxt": "0.5.1",
+ "pinia": "2.1.7",
+ "vue": "3.4.27",
+ "vue-router": "4.3.2"
+ },
+ "license": "LGPL-3.0",
+ "devDependencies": {
+ "cross-env": "7.0.3",
+ "nuxt": "3.12.2"
+ }
+}
diff --git a/apps/ui/pages/[schema]/[table]/index.vue b/apps/ui/pages/[schema]/[table]/index.vue
new file mode 100644
index 0000000000..b1f68542a0
--- /dev/null
+++ b/apps/ui/pages/[schema]/[table]/index.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+ Loading...
+ Error: {{ error }}
+
+
+
+
+
+
+ {{
+ colMeta.label
+ }}
+
+
+
+
+ {{
+ row[colMeta.id]
+ }}
+
+
+
+
+
+
diff --git a/apps/ui/pages/[schema]/analytics.vue b/apps/ui/pages/[schema]/analytics.vue
new file mode 100644
index 0000000000..0d43a02ef9
--- /dev/null
+++ b/apps/ui/pages/[schema]/analytics.vue
@@ -0,0 +1,236 @@
+
+
+
+
+
+
+ Add
+
+
+
+
+
+
+ {{ trigger.cssSelector }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+
+
+
+
+ Update
+
+
+
+
+
+
+ Are you sure you want to delete this trigger?
+
+
+ Delete
+
+
+
+
diff --git a/apps/ui/pages/[schema]/index.vue b/apps/ui/pages/[schema]/index.vue
new file mode 100644
index 0000000000..52d9073205
--- /dev/null
+++ b/apps/ui/pages/[schema]/index.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ label
+ description
+
+
+
+
+ {{ table.label }}
+ {{ table.description }}
+
+
+
+
+
+
+
+
+
+ label
+ description
+
+
+
+
+ {{ ontology.label }}
+ {{ ontology.description }}
+
+
+
+
+
+
diff --git a/apps/ui/pages/[schema]/schema.vue b/apps/ui/pages/[schema]/schema.vue
new file mode 100644
index 0000000000..fa211ec7ee
--- /dev/null
+++ b/apps/ui/pages/[schema]/schema.vue
@@ -0,0 +1,6 @@
+
+
+ {{ schema }}
+
diff --git a/apps/ui/pages/account.vue b/apps/ui/pages/account.vue
new file mode 100644
index 0000000000..1c987f5b84
--- /dev/null
+++ b/apps/ui/pages/account.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+ Sign out
+ Sign in
+
+
+ {{ errorMsg }}
+
+
+
diff --git a/apps/ui/pages/index.vue b/apps/ui/pages/index.vue
new file mode 100644
index 0000000000..854523ddd3
--- /dev/null
+++ b/apps/ui/pages/index.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+ name
+ description
+
+
+
+
+ {{ database.label }}
+ {{ database.description }}
+
+
+
+
+
+
diff --git a/apps/ui/pages/login.vue b/apps/ui/pages/login.vue
new file mode 100644
index 0000000000..b4a8e9bab9
--- /dev/null
+++ b/apps/ui/pages/login.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/public/favicon.ico b/apps/ui/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..18993ad91cfd43e03b074dd0b5cc3f37ab38e49c
GIT binary patch
literal 4286
zcmeHLOKuuL5PjK%MHWVi6lD
zOGiREbCw`xmFozJ^aNatJY>w+g
ze6a2@u~m#^BZm@8wco9#Crlli0uLb^3E$t2-WIc^#(?t)*@`UpuofJ(Uyh@F>b3Ph
z$D^m8Xq~pTkGJ4Q`Q2)te3mgkWYZ^Ijq|hkiP^9`De={bQQ%heZC$QU2UpP(-tbl8
zPWD2abEew;oat@w`uP3J^YpsgT%~jT(Dk%oU}sa$7|n6hBjDj`+I;RX(>)%lm_7N{+B7Mu%H?422lE%MBJH!!YTN2oT7xr>>N-8OF$C&qU^
z>vLsa{$0X%q1fjOe3P1mCv#lN{xQ4_*HCSAZjTb1`}mlc+9rl8$B3OP%VT@mch_~G
z7Y+4b{r>9e=M+7vSI;BgB?ryZDY4m>&wcHSn81VH1N~`0gvwH{
z8dv#hG|OK`>1;j7tM#B)Z7zDN?{6=dUal}$e {
+ const config = useRuntimeConfig(event);
+ console.log("proxy single trigger request : ", event.path, ' type: ', event.method);
+ const location = joinURL(config.public.apiBase, event.path)
+ console.log("to : ", location);
+ return proxyRequest(event, location);
+});
\ No newline at end of file
diff --git a/apps/ui/server/routes/[schema]/api/trigger/index.ts b/apps/ui/server/routes/[schema]/api/trigger/index.ts
new file mode 100644
index 0000000000..b3a4b75a91
--- /dev/null
+++ b/apps/ui/server/routes/[schema]/api/trigger/index.ts
@@ -0,0 +1,9 @@
+import { joinURL } from "ufo";
+export default defineEventHandler((event) => {
+ const config = useRuntimeConfig(event);
+ console.log("proxy trigger request : ", event.path, ' type: ', event.method);
+ const schema = getRouterParam(event, "schema") || "";
+ const location = joinURL(config.public.apiBase, schema, "api", "trigger")
+ console.log("to : ", location);
+ return proxyRequest(event, location);
+});
\ No newline at end of file
diff --git a/apps/ui/stores/metaStore.ts b/apps/ui/stores/metaStore.ts
new file mode 100644
index 0000000000..c6c643e649
--- /dev/null
+++ b/apps/ui/stores/metaStore.ts
@@ -0,0 +1,69 @@
+import metadataGql from "../../nuxt3-ssr/gql/metadata";
+import { type ISchemaMetaData, type ITableMetaData } from "../../metadata-utils/src/types";
+
+const query = moduleToString(metadataGql);
+
+type Resp = {
+ data?: Record ;
+ error?: any;
+};
+
+export const useMetaStore = defineStore('meta-data',{
+
+ state: () => {
+ return {
+ metaData: {} as Record,
+ }
+ },
+
+ getters: {
+ getTableMeta: (state) => {
+ return (schemaId: string, tableId: string) => {
+ if (!state.metaData) {
+ throw new Error(`The store is not initialized`);
+ }
+ if(!state.metaData[schemaId]) {
+ throw new Error(`Schema with id ${schemaId} not found in store`);
+ }
+ const tableMeta = state.metaData[schemaId].tables.find(
+ (t: ITableMetaData) =>
+ t.id.toLowerCase() === tableId.toLowerCase()
+ );
+ if (!tableMeta) {
+ throw new Error(`Table with id ${tableId} not found in schema ${schemaId}`);
+ }
+ return tableMeta;
+ }
+ }
+ },
+
+ actions: {
+ async fetchSchemaMetaData(schemaId: string) {
+ const resp = await $fetch>(`/${schemaId}/graphql`, {
+ method: "POST",
+ body: {
+ query,
+ },
+ });
+
+ if (resp.error) {
+ throw createError({
+ ...resp.error,
+ statusMessage: `Could not fetch metadata for schema ${schemaId}`,
+ });
+ }
+
+ if(resp.data) {
+ return resp.data._schema;
+ }
+
+
+ throw createError('Could not fetch metadata for schema, no data found');
+
+ }
+ }
+})
+
+if (import.meta.hot) {
+ import.meta.hot.accept(acceptHMRUpdate(useMetaStore, import.meta.hot))
+}
\ No newline at end of file
diff --git a/apps/ui/tsconfig.json b/apps/ui/tsconfig.json
new file mode 100644
index 0000000000..a746f2a70c
--- /dev/null
+++ b/apps/ui/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ // https://nuxt.com/docs/guide/concepts/typescript
+ "extends": "./.nuxt/tsconfig.json"
+}
diff --git a/apps/yarn.lock b/apps/yarn.lock
index 83767c8dec..139363ccad 100644
--- a/apps/yarn.lock
+++ b/apps/yarn.lock
@@ -210,7 +210,7 @@
js-tokens "^4.0.0"
picocolors "^1.0.0"
-"@babel/parser@^7.12.0", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.4", "@babel/parser@^7.24.6", "@babel/parser@^7.24.7", "@babel/parser@^7.24.8", "@babel/parser@^7.25.0", "@babel/parser@^7.25.3":
+"@babel/parser@^7.12.0", "@babel/parser@^7.22.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.4", "@babel/parser@^7.24.6", "@babel/parser@^7.24.7", "@babel/parser@^7.24.8", "@babel/parser@^7.25.0", "@babel/parser@^7.25.3":
version "7.25.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.3.tgz#91fb126768d944966263f0657ab222a642b82065"
integrity sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==
@@ -315,7 +315,7 @@
debug "^4.3.1"
globals "^11.1.0"
-"@babel/types@^7.22.15", "@babel/types@^7.23.6", "@babel/types@^7.23.9", "@babel/types@^7.24.0", "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.3.4":
+"@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.23.6", "@babel/types@^7.23.9", "@babel/types@^7.24.0", "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.3.4":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125"
integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==
@@ -1167,6 +1167,15 @@
semver "^7.3.5"
tar "^6.1.11"
+"@microsoft/api-extractor-model@7.28.13":
+ version "7.28.13"
+ resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.28.13.tgz#96fbc52155e0d07e0eabbd9699065b77702fe33a"
+ integrity sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==
+ dependencies:
+ "@microsoft/tsdoc" "0.14.2"
+ "@microsoft/tsdoc-config" "~0.16.1"
+ "@rushstack/node-core-library" "4.0.2"
+
"@microsoft/api-extractor-model@7.29.4":
version "7.29.4"
resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.29.4.tgz#098f94f304db98f3cea8618fd1107946e212eaf5"
@@ -1176,6 +1185,25 @@
"@microsoft/tsdoc-config" "~0.17.0"
"@rushstack/node-core-library" "5.5.1"
+"@microsoft/api-extractor@7.43.0":
+ version "7.43.0"
+ resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.43.0.tgz#41c42677bc71cd8e0f23c63c56802d85044e65cd"
+ integrity sha512-GFhTcJpB+MI6FhvXEI9b2K0snulNLWHqC/BbcJtyNYcKUiw7l3Lgis5ApsYncJ0leALX7/of4XfmXk+maT111w==
+ dependencies:
+ "@microsoft/api-extractor-model" "7.28.13"
+ "@microsoft/tsdoc" "0.14.2"
+ "@microsoft/tsdoc-config" "~0.16.1"
+ "@rushstack/node-core-library" "4.0.2"
+ "@rushstack/rig-package" "0.5.2"
+ "@rushstack/terminal" "0.10.0"
+ "@rushstack/ts-command-line" "4.19.1"
+ lodash "~4.17.15"
+ minimatch "~3.0.3"
+ resolve "~1.22.1"
+ semver "~7.5.4"
+ source-map "~0.6.1"
+ typescript "5.4.2"
+
"@microsoft/api-extractor@^7.38.0":
version "7.47.5"
resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.47.5.tgz#e44f92f803c823538fd21dc98f54cf02d058a44b"
@@ -1195,6 +1223,16 @@
source-map "~0.6.1"
typescript "5.4.2"
+"@microsoft/tsdoc-config@~0.16.1":
+ version "0.16.2"
+ resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz#b786bb4ead00d54f53839a458ce626c8548d3adf"
+ integrity sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==
+ dependencies:
+ "@microsoft/tsdoc" "0.14.2"
+ ajv "~6.12.6"
+ jju "~1.4.0"
+ resolve "~1.19.0"
+
"@microsoft/tsdoc-config@~0.17.0":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.17.0.tgz#82605152b3c1d3f5cd4a11697bc298437484d55d"
@@ -1205,6 +1243,11 @@
jju "~1.4.0"
resolve "~1.22.2"
+"@microsoft/tsdoc@0.14.2":
+ version "0.14.2"
+ resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz#c3ec604a0b54b9a9b87e9735dfc59e1a5da6a5fb"
+ integrity sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==
+
"@microsoft/tsdoc@0.15.0", "@microsoft/tsdoc@~0.15.0":
version "0.15.0"
resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz#f29a55df17cb6e87cfbabce33ff6a14a9f85076d"
@@ -1294,7 +1337,7 @@
rc9 "^2.1.2"
semver "^7.6.2"
-"@nuxt/devtools@^1.3.9":
+"@nuxt/devtools@^1.3.3", "@nuxt/devtools@^1.3.9":
version "1.3.9"
resolved "https://registry.yarnpkg.com/@nuxt/devtools/-/devtools-1.3.9.tgz#9ceb62e3a1dc4d9aad141ab7c4aa76884258131e"
integrity sha512-tFKlbUPgSXw4tyD8xpztQtJeVn3egdKbFCV0xc92FbfGbclAyaa3XhKA2tMWXEGZQpykAWMRNrGWN24FtXFA6Q==
@@ -1355,7 +1398,33 @@
optionalDependencies:
ipx "^2.1.0"
-"@nuxt/kit@3.12.4", "@nuxt/kit@^3.11.1", "@nuxt/kit@^3.11.2", "@nuxt/kit@^3.12.2", "@nuxt/kit@^3.12.4":
+"@nuxt/kit@3.12.2":
+ version "3.12.2"
+ resolved "https://registry.yarnpkg.com/@nuxt/kit/-/kit-3.12.2.tgz#762cf62af4762c035a1f73965832e80e66005f5f"
+ integrity sha512-5kOqEzfc3FsAncjK2je7vuq4/QsR5ypViTnop52mlFLf0Ku1NMCrWCSWYowAh4P0yqTACMAZYa+HdRZHscU84g==
+ dependencies:
+ "@nuxt/schema" "3.12.2"
+ c12 "^1.11.1"
+ consola "^3.2.3"
+ defu "^6.1.4"
+ destr "^2.0.3"
+ globby "^14.0.1"
+ hash-sum "^2.0.0"
+ ignore "^5.3.1"
+ jiti "^1.21.6"
+ klona "^2.0.6"
+ knitwork "^1.1.0"
+ mlly "^1.7.1"
+ pathe "^1.1.2"
+ pkg-types "^1.1.1"
+ scule "^1.3.0"
+ semver "^7.6.2"
+ ufo "^1.5.3"
+ unctx "^2.3.1"
+ unimport "^3.7.2"
+ untyped "^1.4.2"
+
+"@nuxt/kit@3.12.4", "@nuxt/kit@^3.11.1", "@nuxt/kit@^3.11.2", "@nuxt/kit@^3.12.2", "@nuxt/kit@^3.12.4", "@nuxt/kit@^3.5.0":
version "3.12.4"
resolved "https://registry.yarnpkg.com/@nuxt/kit/-/kit-3.12.4.tgz#b7073611d533ac32b504d95664074be3587046b3"
integrity sha512-aNRD1ylzijY0oYolldNcZJXVyxdGzNTl+Xd0UYyFQCu9f4wqUZqQ9l+b7arCEzchr96pMK0xdpvLcS3xo1wDcw==
@@ -1381,6 +1450,24 @@
unimport "^3.9.0"
untyped "^1.4.2"
+"@nuxt/schema@3.12.2":
+ version "3.12.2"
+ resolved "https://registry.yarnpkg.com/@nuxt/schema/-/schema-3.12.2.tgz#dc2c3bced5a6965075dabfb372dd2f77bb3b33c6"
+ integrity sha512-IRBuOEPOIe1CANKnO2OUiqZ1Hp/0htPkLaigK7WT6ef/SdIFZUd68Tqqejqy2AFrbgU9G80k3U7eg2XUdaiQlQ==
+ dependencies:
+ compatx "^0.1.8"
+ consola "^3.2.3"
+ defu "^6.1.4"
+ hookable "^5.5.3"
+ pathe "^1.1.2"
+ pkg-types "^1.1.1"
+ scule "^1.3.0"
+ std-env "^3.7.0"
+ ufo "^1.5.3"
+ uncrypto "^0.1.3"
+ unimport "^3.7.2"
+ untyped "^1.4.2"
+
"@nuxt/schema@3.12.4", "@nuxt/schema@^3.11.1", "@nuxt/schema@^3.12.3", "@nuxt/schema@^3.12.4":
version "3.12.4"
resolved "https://registry.yarnpkg.com/@nuxt/schema/-/schema-3.12.4.tgz#295873c5e8bfbda0c9312bd16272373c936e6a71"
@@ -1480,6 +1567,46 @@
unplugin "^1.12.0"
vitest-environment-nuxt "^1.0.0"
+"@nuxt/vite-builder@3.12.2":
+ version "3.12.2"
+ resolved "https://registry.yarnpkg.com/@nuxt/vite-builder/-/vite-builder-3.12.2.tgz#dbd1da15d06f53d34d655b3fbfd29fe714bb6f00"
+ integrity sha512-gE7bKxbnd3OdlCHdZKgnbs2oOdcLHvEQ92LGnDCs9rCdsXazhQ7gcfow+FsKMp9MMu785O55gd4CiIgnn7N0BA==
+ dependencies:
+ "@nuxt/kit" "3.12.2"
+ "@rollup/plugin-replace" "^5.0.7"
+ "@vitejs/plugin-vue" "^5.0.4"
+ "@vitejs/plugin-vue-jsx" "^4.0.0"
+ autoprefixer "^10.4.19"
+ clear "^0.1.0"
+ consola "^3.2.3"
+ cssnano "^7.0.2"
+ defu "^6.1.4"
+ esbuild "^0.21.5"
+ escape-string-regexp "^5.0.0"
+ estree-walker "^3.0.3"
+ externality "^1.0.2"
+ fs-extra "^11.2.0"
+ get-port-please "^3.1.2"
+ h3 "^1.11.1"
+ knitwork "^1.1.0"
+ magic-string "^0.30.10"
+ mlly "^1.7.1"
+ ohash "^1.1.3"
+ pathe "^1.1.2"
+ perfect-debounce "^1.0.0"
+ pkg-types "^1.1.1"
+ postcss "^8.4.38"
+ rollup-plugin-visualizer "^5.12.0"
+ std-env "^3.7.0"
+ strip-literal "^2.1.0"
+ ufo "^1.5.3"
+ unenv "^1.9.0"
+ unplugin "^1.10.1"
+ vite "^5.3.1"
+ vite-node "^1.6.0"
+ vite-plugin-checker "^0.6.4"
+ vue-bundle-renderer "^2.1.0"
+
"@nuxt/vite-builder@3.12.4":
version "3.12.4"
resolved "https://registry.yarnpkg.com/@nuxt/vite-builder/-/vite-builder-3.12.4.tgz#6ee925959a84fc4e00f1e87e752f2b75e8832129"
@@ -1661,6 +1788,14 @@
tslib "^2.6.2"
webcrypto-core "^1.8.0"
+"@pinia/nuxt@0.5.1":
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/@pinia/nuxt/-/nuxt-0.5.1.tgz#ee7c979d365a5dfda882430ddfae405fbd78d8d5"
+ integrity sha512-6wT6TqY81n+7/x3Yhf0yfaJVKkZU42AGqOR0T3+UvChcaOJhSma7OWPN64v+ptYlznat+fS1VTwNAcbi2lzHnw==
+ dependencies:
+ "@nuxt/kit" "^3.5.0"
+ pinia ">=2.1.7"
+
"@pkgjs/parseargs@^0.11.0":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
@@ -1801,7 +1936,7 @@
estree-walker "^2.0.1"
picomatch "^2.2.2"
-"@rollup/pluginutils@^5.0.1", "@rollup/pluginutils@^5.0.5", "@rollup/pluginutils@^5.1.0":
+"@rollup/pluginutils@^5.0.1", "@rollup/pluginutils@^5.0.2", "@rollup/pluginutils@^5.0.4", "@rollup/pluginutils@^5.0.5", "@rollup/pluginutils@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0"
integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==
@@ -1890,6 +2025,18 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz#601fffee719a1e8447f908aca97864eec23b2784"
integrity sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==
+"@rushstack/node-core-library@4.0.2":
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-4.0.2.tgz#e26854a3314b279d57e8abdb4acce7797d02f554"
+ integrity sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==
+ dependencies:
+ fs-extra "~7.0.1"
+ import-lazy "~4.0.0"
+ jju "~1.4.0"
+ resolve "~1.22.1"
+ semver "~7.5.4"
+ z-schema "~5.0.2"
+
"@rushstack/node-core-library@5.5.1":
version "5.5.1"
resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-5.5.1.tgz#890db37eafaab582c79eb6bf421447b82b3a964b"
@@ -1904,6 +2051,14 @@
resolve "~1.22.1"
semver "~7.5.4"
+"@rushstack/rig-package@0.5.2":
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/@rushstack/rig-package/-/rig-package-0.5.2.tgz#0e23a115904678717a74049661931c0b37dd5495"
+ integrity sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==
+ dependencies:
+ resolve "~1.22.1"
+ strip-json-comments "~3.1.1"
+
"@rushstack/rig-package@0.5.3":
version "0.5.3"
resolved "https://registry.yarnpkg.com/@rushstack/rig-package/-/rig-package-0.5.3.tgz#ea4d8a3458540b1295500149c04e645f23134e5d"
@@ -1912,6 +2067,14 @@
resolve "~1.22.1"
strip-json-comments "~3.1.1"
+"@rushstack/terminal@0.10.0":
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/@rushstack/terminal/-/terminal-0.10.0.tgz#e81909fa0e5c8016b6df4739f0f381f44358269f"
+ integrity sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==
+ dependencies:
+ "@rushstack/node-core-library" "4.0.2"
+ supports-color "~8.1.1"
+
"@rushstack/terminal@0.13.3":
version "0.13.3"
resolved "https://registry.yarnpkg.com/@rushstack/terminal/-/terminal-0.13.3.tgz#9a05b8cf759f14161a49d3ccb09d556e4161caca"
@@ -1920,6 +2083,16 @@
"@rushstack/node-core-library" "5.5.1"
supports-color "~8.1.1"
+"@rushstack/ts-command-line@4.19.1":
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.19.1.tgz#288ee54dd607e558a8be07705869c16c31b5c3ef"
+ integrity sha512-J7H768dgcpG60d7skZ5uSSwyCZs/S2HrWP1Ds8d1qYAyaaeJmpmmLr9BVw97RjFzmQPOYnoXcKA4GkqDCkduQg==
+ dependencies:
+ "@rushstack/terminal" "0.10.0"
+ "@types/argparse" "1.0.38"
+ argparse "~1.0.9"
+ string-argv "~0.3.1"
+
"@rushstack/ts-command-line@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.22.4.tgz#63f24b8f6d214d3d4971c8cae7fc54a401abd87b"
@@ -2144,7 +2317,7 @@
dependencies:
"@types/node" "*"
-"@unhead/dom@1.9.16", "@unhead/dom@^1.9.16":
+"@unhead/dom@1.9.16", "@unhead/dom@^1.9.13", "@unhead/dom@^1.9.16":
version "1.9.16"
resolved "https://registry.yarnpkg.com/@unhead/dom/-/dom-1.9.16.tgz#2cafa10d213526e6c76e44333f1c9b6148141080"
integrity sha512-aZIAnnc89Csi1vV4mtlHYI765B7m1yuaXUuQiYHwr6glE9FLyy2X87CzEci4yPH/YbkKm0bGQRfcxXq6Eq0W7g==
@@ -2167,7 +2340,7 @@
dependencies:
"@unhead/schema" "1.9.16"
-"@unhead/ssr@^1.9.16":
+"@unhead/ssr@^1.9.13", "@unhead/ssr@^1.9.16":
version "1.9.16"
resolved "https://registry.yarnpkg.com/@unhead/ssr/-/ssr-1.9.16.tgz#f84192a9350288cc407582df852b6f0e58b0ebd5"
integrity sha512-8R1qt4VAemX4Iun/l7DnUBJqmxA/KaUSc2+/hRYPJYOopXdCWkoaxC1K1ROX2vbRF7qmjdU5ik/a27kSPN94gg==
@@ -2175,7 +2348,7 @@
"@unhead/schema" "1.9.16"
"@unhead/shared" "1.9.16"
-"@unhead/vue@^1.9.16":
+"@unhead/vue@^1.9.13", "@unhead/vue@^1.9.16":
version "1.9.16"
resolved "https://registry.yarnpkg.com/@unhead/vue/-/vue-1.9.16.tgz#94b2db11fd0658f0ae26d9e4edaa7047081c7cc4"
integrity sha512-kpMWWwm8cOwo4gw4An43pz30l2CqNtmJpX5Xsu79rwf6Viq8jHAjk6BGqyKy220M2bpa0Va4fnR532SgGO1YgQ==
@@ -2217,7 +2390,7 @@
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz#057d2ded94c4e71b94e9814f92dcd9306317aa46"
integrity sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==
-"@vitejs/plugin-vue@^5.0.5":
+"@vitejs/plugin-vue@^5.0.4", "@vitejs/plugin-vue@^5.0.5":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.1.2.tgz#f11091e0130eca6c1ca8cfb85ee71ea53b255d31"
integrity sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==
@@ -2360,6 +2533,13 @@
dependencies:
"@volar/source-map" "1.11.1"
+"@volar/language-core@2.4.0-alpha.18", "@volar/language-core@~2.4.0-alpha.18":
+ version "2.4.0-alpha.18"
+ resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.4.0-alpha.18.tgz#dafffd68ac07c26d69de16741187fd4c06bfa345"
+ integrity sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==
+ dependencies:
+ "@volar/source-map" "2.4.0-alpha.18"
+
"@volar/source-map@1.11.1", "@volar/source-map@~1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.11.1.tgz#535b0328d9e2b7a91dff846cab4058e191f4452f"
@@ -2367,6 +2547,11 @@
dependencies:
muggle-string "^0.3.1"
+"@volar/source-map@2.4.0-alpha.18":
+ version "2.4.0-alpha.18"
+ resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.4.0-alpha.18.tgz#a2413932ff6b1821ae8efcbd9249d4da3f99f223"
+ integrity sha512-MTeCV9MUwwsH0sNFiZwKtFrrVZUK6p8ioZs3xFzHc2cvDXHWlYN3bChdQtwKX+FY2HG6H3CfAu1pKijolzIQ8g==
+
"@volar/typescript@~1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.11.1.tgz#ba86c6f326d88e249c7f5cfe4b765be3946fd627"
@@ -2375,7 +2560,16 @@
"@volar/language-core" "1.11.1"
path-browserify "^1.0.1"
-"@vue-macros/common@^1.11.0":
+"@volar/typescript@~2.4.0-alpha.18":
+ version "2.4.0-alpha.18"
+ resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.4.0-alpha.18.tgz#806aca9ce1bd7c48dc5fcd0fcf7f33bdd04e5b35"
+ integrity sha512-sXh5Y8sqGUkgxpMWUGvRXggxYHAVxg0Pa1C42lQZuPDrW6vHJPR0VCK8Sr7WJsAW530HuNQT/ZIskmXtxjybMQ==
+ dependencies:
+ "@volar/language-core" "2.4.0-alpha.18"
+ path-browserify "^1.0.1"
+ vscode-uri "^3.0.8"
+
+"@vue-macros/common@^1.11.0", "@vue-macros/common@^1.8.0":
version "1.12.2"
resolved "https://registry.yarnpkg.com/@vue-macros/common/-/common-1.12.2.tgz#ffaaf91c5fc2f2e8aee64bdfa75a559970793428"
integrity sha512-+NGfhrPvPNOb3Wg9PNPEXPe0HTXmVe6XJawL1gi3cIjOSGIhpOdvmMT2cRuWb265IpA/PeL5Sqo0+DQnEDxLvw==
@@ -2420,6 +2614,17 @@
"@babel/parser" "^7.23.9"
"@vue/compiler-sfc" "^3.4.15"
+"@vue/compiler-core@3.4.27":
+ version "3.4.27"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.27.tgz#e69060f4b61429fe57976aa5872cfa21389e4d91"
+ integrity sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==
+ dependencies:
+ "@babel/parser" "^7.24.4"
+ "@vue/shared" "3.4.27"
+ entities "^4.5.0"
+ estree-walker "^2.0.2"
+ source-map-js "^1.2.0"
+
"@vue/compiler-core@3.4.37":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.37.tgz#55db3900e09424c65c39111a05a3c6e698f371e3"
@@ -2431,7 +2636,15 @@
estree-walker "^2.0.2"
source-map-js "^1.2.0"
-"@vue/compiler-dom@3.4.37", "@vue/compiler-dom@^3.3.0", "@vue/compiler-dom@^3.3.4":
+"@vue/compiler-dom@3.4.27":
+ version "3.4.27"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz#d51d35f40d00ce235d7afc6ad8b09dfd92b1cc1c"
+ integrity sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==
+ dependencies:
+ "@vue/compiler-core" "3.4.27"
+ "@vue/shared" "3.4.27"
+
+"@vue/compiler-dom@3.4.37", "@vue/compiler-dom@^3.3.0", "@vue/compiler-dom@^3.3.4", "@vue/compiler-dom@^3.4.0":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.37.tgz#a1fcf79e287cb828545082ff1afa8630480a3044"
integrity sha512-rIiSmL3YrntvgYV84rekAtU/xfogMUJIclUMeIKEtVBFngOL3IeZHhsH3UaFEgB5iFGpj6IW+8YuM/2Up+vVag==
@@ -2439,6 +2652,21 @@
"@vue/compiler-core" "3.4.37"
"@vue/shared" "3.4.37"
+"@vue/compiler-sfc@3.4.27":
+ version "3.4.27"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz#399cac1b75c6737bf5440dc9cf3c385bb2959701"
+ integrity sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==
+ dependencies:
+ "@babel/parser" "^7.24.4"
+ "@vue/compiler-core" "3.4.27"
+ "@vue/compiler-dom" "3.4.27"
+ "@vue/compiler-ssr" "3.4.27"
+ "@vue/shared" "3.4.27"
+ estree-walker "^2.0.2"
+ magic-string "^0.30.10"
+ postcss "^8.4.38"
+ source-map-js "^1.2.0"
+
"@vue/compiler-sfc@3.4.37", "@vue/compiler-sfc@^3.4.15", "@vue/compiler-sfc@^3.4.34":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.37.tgz#8afaf1a86cb849422c765d4369ba1e85fffe0234"
@@ -2454,6 +2682,14 @@
postcss "^8.4.40"
source-map-js "^1.2.0"
+"@vue/compiler-ssr@3.4.27":
+ version "3.4.27"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz#2a8ecfef1cf448b09be633901a9c020360472e3d"
+ integrity sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==
+ dependencies:
+ "@vue/compiler-dom" "3.4.27"
+ "@vue/shared" "3.4.27"
+
"@vue/compiler-ssr@3.4.37":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.37.tgz#b75e1c76c3184f86fa9f0ba4d61d13bc6afcbf8a"
@@ -2462,7 +2698,15 @@
"@vue/compiler-dom" "3.4.37"
"@vue/shared" "3.4.37"
-"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.6.3":
+"@vue/compiler-vue2@^2.7.16":
+ version "2.7.16"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz#2ba837cbd3f1b33c2bc865fbe1a3b53fb611e249"
+ integrity sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==
+ dependencies:
+ de-indent "^1.0.2"
+ he "^1.2.0"
+
+"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.5.1", "@vue/devtools-api@^6.6.3":
version "6.6.3"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.3.tgz#b23a588154cba8986bba82b6e1d0248bde3fd1a0"
integrity sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==
@@ -2512,7 +2756,7 @@
dependencies:
rfdc "^1.4.1"
-"@vue/language-core@1.8.27", "@vue/language-core@^1.8.20":
+"@vue/language-core@1.8.27", "@vue/language-core@^1.8.20", "@vue/language-core@^1.8.27":
version "1.8.27"
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.27.tgz#2ca6892cb524e024a44e554e4c55d7a23e72263f"
integrity sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==
@@ -2527,6 +2771,27 @@
path-browserify "^1.0.1"
vue-template-compiler "^2.7.14"
+"@vue/language-core@2.0.29":
+ version "2.0.29"
+ resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.0.29.tgz#19462d786cd7a1c21dbe575b46970a57094e0357"
+ integrity sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==
+ dependencies:
+ "@volar/language-core" "~2.4.0-alpha.18"
+ "@vue/compiler-dom" "^3.4.0"
+ "@vue/compiler-vue2" "^2.7.16"
+ "@vue/shared" "^3.4.0"
+ computeds "^0.0.1"
+ minimatch "^9.0.3"
+ muggle-string "^0.4.1"
+ path-browserify "^1.0.1"
+
+"@vue/reactivity@3.4.27":
+ version "3.4.27"
+ resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.27.tgz#6ece72331bf719953f5eaa95ec60b2b8d49e3791"
+ integrity sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==
+ dependencies:
+ "@vue/shared" "3.4.27"
+
"@vue/reactivity@3.4.37":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.37.tgz#5a199563eaab51ed9f94ddf0a82f9179bcc01676"
@@ -2534,6 +2799,14 @@
dependencies:
"@vue/shared" "3.4.37"
+"@vue/runtime-core@3.4.27":
+ version "3.4.27"
+ resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.27.tgz#1b6e1d71e4604ba7442dd25ed22e4a1fc6adbbda"
+ integrity sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==
+ dependencies:
+ "@vue/reactivity" "3.4.27"
+ "@vue/shared" "3.4.27"
+
"@vue/runtime-core@3.4.37":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.37.tgz#3fe734a666db7842bea4185a13f7697a2102b719"
@@ -2542,6 +2815,15 @@
"@vue/reactivity" "3.4.37"
"@vue/shared" "3.4.37"
+"@vue/runtime-dom@3.4.27":
+ version "3.4.27"
+ resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz#fe8d1ce9bbe8921d5dd0ad5c10df0e04ef7a5ee7"
+ integrity sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==
+ dependencies:
+ "@vue/runtime-core" "3.4.27"
+ "@vue/shared" "3.4.27"
+ csstype "^3.1.3"
+
"@vue/runtime-dom@3.4.37":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.37.tgz#219f84577027103de6ddc71351d8237c7c16adac"
@@ -2552,6 +2834,14 @@
"@vue/shared" "3.4.37"
csstype "^3.1.3"
+"@vue/server-renderer@3.4.27":
+ version "3.4.27"
+ resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.27.tgz#3306176f37e648ba665f97dda3ce705687be63d2"
+ integrity sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==
+ dependencies:
+ "@vue/compiler-ssr" "3.4.27"
+ "@vue/shared" "3.4.27"
+
"@vue/server-renderer@3.4.37":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.37.tgz#d341425bb5395a3f6ed70572ea5c3edefab92f28"
@@ -2560,7 +2850,12 @@
"@vue/compiler-ssr" "3.4.37"
"@vue/shared" "3.4.37"
-"@vue/shared@3.4.37", "@vue/shared@^3.3.0", "@vue/shared@^3.4.32":
+"@vue/shared@3.4.27":
+ version "3.4.27"
+ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.27.tgz#f05e3cd107d157354bb4ae7a7b5fc9cf73c63b50"
+ integrity sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==
+
+"@vue/shared@3.4.37", "@vue/shared@^3.3.0", "@vue/shared@^3.4.0", "@vue/shared@^3.4.29", "@vue/shared@^3.4.32":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.37.tgz#4f4c08a2e73da512a77b47165cf59ffbc1b5ade8"
integrity sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg==
@@ -2788,6 +3083,11 @@ acorn-walk@^8.2.0, acorn-walk@^8.3.2:
dependencies:
acorn "^8.11.0"
+acorn@8.12.0:
+ version "8.12.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c"
+ integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==
+
acorn@8.12.1, acorn@^8.10.0, acorn@^8.11.0, acorn@^8.11.3, acorn@^8.12.1, acorn@^8.6.0, acorn@^8.8.2, acorn@^8.9.0:
version "8.12.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
@@ -2832,6 +3132,16 @@ ajv@^8.0.0:
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
+ajv@~6.12.6:
+ version "6.12.6"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
ajv@~8.12.0:
version "8.12.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
@@ -3122,6 +3432,15 @@ ast-kit@^0.12.1:
"@babel/parser" "^7.24.6"
pathe "^1.1.2"
+ast-kit@^0.9.4:
+ version "0.9.5"
+ resolved "https://registry.yarnpkg.com/ast-kit/-/ast-kit-0.9.5.tgz#88c0ba76b6f7f24c04ccf9ae778e33afc187dc80"
+ integrity sha512-kbL7ERlqjXubdDd+szuwdlQ1xUxEz9mCz1+m07ftNVStgwRb2RWw+U6oKo08PAvOishMxiqz1mlJyLl8yQx2Qg==
+ dependencies:
+ "@babel/parser" "^7.22.7"
+ "@rollup/pluginutils" "^5.0.2"
+ pathe "^1.1.1"
+
ast-kit@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ast-kit/-/ast-kit-1.0.1.tgz#647c64d997f3d85ccbeb221a4cd409549d7da5ca"
@@ -3130,6 +3449,14 @@ ast-kit@^1.0.1:
"@babel/parser" "^7.24.8"
pathe "^1.1.2"
+ast-walker-scope@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/ast-walker-scope/-/ast-walker-scope-0.5.0.tgz#87e0ca4f34394d11ec4dea5925b8bda80b811819"
+ integrity sha512-NsyHMxBh4dmdEHjBo1/TBZvCKxffmZxRYhmclfu0PP6Aftre47jOHYaYaNqJcV0bxihxFXhDkzLHUwHc0ocd0Q==
+ dependencies:
+ "@babel/parser" "^7.22.7"
+ ast-kit "^0.9.4"
+
ast-walker-scope@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/ast-walker-scope/-/ast-walker-scope-0.6.1.tgz#e074c60b31d33de094f76661029b10162dbcca89"
@@ -4503,7 +4830,7 @@ cssnano-utils@^5.0.0:
resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-5.0.0.tgz#b53a0343dd5d21012911882db6ae7d2eae0e3687"
integrity sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==
-cssnano@^7.0.4:
+cssnano@^7.0.2, cssnano@^7.0.4:
version "7.0.5"
resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-7.0.5.tgz#97186daaf96f4db447227ee2d823ce6332f77beb"
integrity sha512-Aq0vqBLtpTT5Yxj+hLlLfNPFuRQCDIjx5JQAhhaedQKLNDvDGeVziF24PS+S1f0Z5KCxWvw0QVI3VNHNBITxVQ==
@@ -5554,7 +5881,7 @@ esbuild@^0.20.2:
"@esbuild/win32-ia32" "0.20.2"
"@esbuild/win32-x64" "0.20.2"
-esbuild@^0.21.3:
+esbuild@^0.21.3, esbuild@^0.21.5:
version "0.21.5"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
@@ -5912,7 +6239,7 @@ fast-glob@^2.2.6:
merge2 "^1.2.3"
micromatch "^3.1.10"
-fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2:
+fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1, fast-glob@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
@@ -7287,7 +7614,7 @@ is-ci@^1.0.10:
dependencies:
ci-info "^1.5.0"
-is-core-module@^2.13.0:
+is-core-module@^2.1.0, is-core-module@^2.13.0:
version "2.15.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea"
integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==
@@ -7746,6 +8073,11 @@ json-parse-even-better-errors@^2.3.0:
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
json-schema-traverse@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
@@ -8133,6 +8465,11 @@ lodash.defaults@^4.2.0:
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==
+lodash.get@^4.4.2:
+ version "4.4.2"
+ resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
+ integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
+
lodash.isarguments@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
@@ -8899,6 +9236,11 @@ muggle-string@^0.3.1:
resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.3.1.tgz#e524312eb1728c63dd0b2ac49e3282e6ed85963a"
integrity sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==
+muggle-string@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.4.1.tgz#3b366bd43b32f809dc20659534dd30e7c8a0d328"
+ integrity sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==
+
mute-stream@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
@@ -8965,7 +9307,7 @@ neo-async@^2.6.2:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
-nitropack@^2.9.7:
+nitropack@^2.9.6, nitropack@^2.9.7:
version "2.9.7"
resolved "https://registry.yarnpkg.com/nitropack/-/nitropack-2.9.7.tgz#66da772d14d0e364dd17adbd439f0b1e44112a8d"
integrity sha512-aKXvtNrWkOCMsQbsk4A0qQdBjrJ1ZcvwlTQevI/LAgLWLYc5L7Q/YiYxGLal4ITyNSlzir1Cm1D2ZxnYhmpMEw==
@@ -9233,6 +9575,69 @@ nuxi@^3.12.0:
optionalDependencies:
fsevents "~2.3.3"
+nuxt@3.12.2:
+ version "3.12.2"
+ resolved "https://registry.yarnpkg.com/nuxt/-/nuxt-3.12.2.tgz#c2057bead1e342e4d19120d68b11843a17ad5e7c"
+ integrity sha512-DkQvGbILEUwvXJ9TG2iTMvyO6D9ABLyA5bJ+ns8ZgWatfSXC7hXnL//PTYF7neYUzyYn0e5fLHcXgRLa/9YoLA==
+ dependencies:
+ "@nuxt/devalue" "^2.0.2"
+ "@nuxt/devtools" "^1.3.3"
+ "@nuxt/kit" "3.12.2"
+ "@nuxt/schema" "3.12.2"
+ "@nuxt/telemetry" "^2.5.4"
+ "@nuxt/vite-builder" "3.12.2"
+ "@unhead/dom" "^1.9.13"
+ "@unhead/ssr" "^1.9.13"
+ "@unhead/vue" "^1.9.13"
+ "@vue/shared" "^3.4.29"
+ acorn "8.12.0"
+ c12 "^1.11.1"
+ chokidar "^3.6.0"
+ cookie-es "^1.1.0"
+ defu "^6.1.4"
+ destr "^2.0.3"
+ devalue "^5.0.0"
+ esbuild "^0.21.5"
+ escape-string-regexp "^5.0.0"
+ estree-walker "^3.0.3"
+ fs-extra "^11.2.0"
+ globby "^14.0.1"
+ h3 "^1.11.1"
+ hookable "^5.5.3"
+ ignore "^5.3.1"
+ jiti "^1.21.6"
+ klona "^2.0.6"
+ knitwork "^1.1.0"
+ magic-string "^0.30.10"
+ mlly "^1.7.1"
+ nitropack "^2.9.6"
+ nuxi "^3.12.0"
+ nypm "^0.3.8"
+ ofetch "^1.3.4"
+ ohash "^1.1.3"
+ pathe "^1.1.2"
+ perfect-debounce "^1.0.0"
+ pkg-types "^1.1.1"
+ radix3 "^1.1.2"
+ scule "^1.3.0"
+ semver "^7.6.2"
+ std-env "^3.7.0"
+ strip-literal "^2.1.0"
+ ufo "^1.5.3"
+ ultrahtml "^1.5.3"
+ uncrypto "^0.1.3"
+ unctx "^2.3.1"
+ unenv "^1.9.0"
+ unimport "^3.7.2"
+ unplugin "^1.10.1"
+ unplugin-vue-router "^0.7.0"
+ unstorage "^1.10.2"
+ untyped "^1.4.2"
+ vue "^3.4.29"
+ vue-bundle-renderer "^2.1.0"
+ vue-devtools-stub "^0.1.0"
+ vue-router "^4.3.3"
+
nuxt@3.12.4:
version "3.12.4"
resolved "https://registry.yarnpkg.com/nuxt/-/nuxt-3.12.4.tgz#d1c2aa26aa90aefb8ac08bd9a5648851e4db91af"
@@ -9721,7 +10126,7 @@ path-key@^4.0.0:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18"
integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
-path-parse@^1.0.7:
+path-parse@^1.0.6, path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
@@ -9847,6 +10252,14 @@ pinia@2.1.7:
"@vue/devtools-api" "^6.5.0"
vue-demi ">=0.14.5"
+pinia@>=2.1.7:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.2.1.tgz#7cf860f6a23981c23e58605cee45496ce46d15d1"
+ integrity sha512-ltEU3xwiz5ojVMizdP93AHi84Rtfz0+yKd8ud75hr9LVyWX2alxp7vLbY1kFm7MXFmHHr/9B08Xf8Jj6IHTEiQ==
+ dependencies:
+ "@vue/devtools-api" "^6.6.3"
+ vue-demi "^0.14.10"
+
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
@@ -10150,9 +10563,9 @@ postcss-selector-parser@6.0.10:
util-deprecate "^1.0.2"
postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.1.0, postcss-selector-parser@^6.1.1:
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz#5be94b277b8955904476a2400260002ce6c56e38"
- integrity sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==
+ version "6.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de"
+ integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
@@ -10983,6 +11396,14 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.22.1, resolve@^1.22.
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
+resolve@~1.19.0:
+ version "1.19.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
+ integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
+ dependencies:
+ is-core-module "^2.1.0"
+ path-parse "^1.0.6"
+
restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@@ -11195,7 +11616,7 @@ scheduler@^0.19.1:
loose-envify "^1.1.0"
object-assign "^4.1.1"
-scule@^1.2.0, scule@^1.3.0:
+scule@^1.0.0, scule@^1.2.0, scule@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/scule/-/scule-1.3.0.tgz#6efbd22fd0bb801bdcc585c89266a7d2daa8fbd3"
integrity sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==
@@ -11229,7 +11650,7 @@ semver@^6.0.0, semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-semver@^7.3.4, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.2, semver@^7.6.3:
+semver@^7.3.4, semver@^7.3.5, semver@^7.5.0, semver@^7.5.3, semver@^7.5.4, semver@^7.6.2, semver@^7.6.3:
version "7.6.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
@@ -12464,6 +12885,11 @@ typescript@5.4.2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372"
integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==
+typescript@^5.2.2:
+ version "5.5.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
+ integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
+
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
@@ -12642,7 +13068,26 @@ unplugin-vue-router@^0.10.0:
unplugin "^1.12.0"
yaml "^2.5.0"
-unplugin@^1.10.0, unplugin@^1.11.0, unplugin@^1.12.0, unplugin@^1.3.1:
+unplugin-vue-router@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/unplugin-vue-router/-/unplugin-vue-router-0.7.0.tgz#27bd250c7dc698366cce70c5b72b97c3b3766c26"
+ integrity sha512-ddRreGq0t5vlSB7OMy4e4cfU1w2AwBQCwmvW3oP/0IHQiokzbx4hd3TpwBu3eIAFVuhX2cwNQwp1U32UybTVCw==
+ dependencies:
+ "@babel/types" "^7.22.19"
+ "@rollup/pluginutils" "^5.0.4"
+ "@vue-macros/common" "^1.8.0"
+ ast-walker-scope "^0.5.0"
+ chokidar "^3.5.3"
+ fast-glob "^3.3.1"
+ json5 "^2.2.3"
+ local-pkg "^0.4.3"
+ mlly "^1.4.2"
+ pathe "^1.1.1"
+ scule "^1.0.0"
+ unplugin "^1.5.0"
+ yaml "^2.3.2"
+
+unplugin@^1.10.0, unplugin@^1.10.1, unplugin@^1.11.0, unplugin@^1.12.0, unplugin@^1.3.1, unplugin@^1.5.0:
version "1.12.1"
resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.12.1.tgz#dc5834dc9337f47ddb7cf4cbbb9b8dac07e5bea4"
integrity sha512-aXEH9c5qi3uYZHo0niUtxDlT9ylG/luMW/dZslSCkbtC31wCyFkmM0kyoBBh+Grhn7CL+/kvKLfN61/EdxPxMQ==
@@ -12843,6 +13288,11 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
+validator@^13.7.0:
+ version "13.12.0"
+ resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f"
+ integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==
+
value-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
@@ -12897,6 +13347,17 @@ vite-node@1.3.1:
picocolors "^1.0.0"
vite "^5.0.0"
+vite-node@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.0.tgz#2c7e61129bfecc759478fa592754fd9704aaba7f"
+ integrity sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==
+ dependencies:
+ cac "^6.7.14"
+ debug "^4.3.4"
+ pathe "^1.1.1"
+ picocolors "^1.0.0"
+ vite "^5.0.0"
+
vite-node@^2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.0.5.tgz#36d909188fc6e3aba3da5fc095b3637d0d18e27b"
@@ -12908,6 +13369,27 @@ vite-node@^2.0.3:
tinyrainbow "^1.2.0"
vite "^5.0.0"
+vite-plugin-checker@^0.6.4:
+ version "0.6.4"
+ resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.6.4.tgz#aca186ab605aa15bd2c5dd9cc6d7c8fdcbe214ec"
+ integrity sha512-2zKHH5oxr+ye43nReRbC2fny1nyARwhxdm0uNYp/ERy4YvU9iZpNOsueoi/luXw5gnpqRSvjcEPxXbS153O2wA==
+ dependencies:
+ "@babel/code-frame" "^7.12.13"
+ ansi-escapes "^4.3.0"
+ chalk "^4.1.1"
+ chokidar "^3.5.1"
+ commander "^8.0.0"
+ fast-glob "^3.2.7"
+ fs-extra "^11.1.0"
+ npm-run-path "^4.0.1"
+ semver "^7.5.0"
+ strip-ansi "^6.0.0"
+ tiny-invariant "^1.1.0"
+ vscode-languageclient "^7.0.0"
+ vscode-languageserver "^7.0.0"
+ vscode-languageserver-textdocument "^1.0.1"
+ vscode-uri "^3.0.2"
+
vite-plugin-checker@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.7.2.tgz#093ffdf9ccf51b2c9eab7101480bd0217ae99536"
@@ -12940,6 +13422,19 @@ vite-plugin-dts@3.6.4:
kolorist "^1.8.0"
vue-tsc "^1.8.20"
+vite-plugin-dts@^3.9.1:
+ version "3.9.1"
+ resolved "https://registry.yarnpkg.com/vite-plugin-dts/-/vite-plugin-dts-3.9.1.tgz#625ad388ec3956708ccec7960550a7b0a8e8909e"
+ integrity sha512-rVp2KM9Ue22NGWB8dNtWEr+KekN3rIgz1tWD050QnRGlriUCmaDwa7qA5zDEjbXg5lAXhYMSBJtx3q3hQIJZSg==
+ dependencies:
+ "@microsoft/api-extractor" "7.43.0"
+ "@rollup/pluginutils" "^5.1.0"
+ "@vue/language-core" "^1.8.27"
+ debug "^4.3.4"
+ kolorist "^1.8.0"
+ magic-string "^0.30.8"
+ vue-tsc "^1.8.27"
+
vite-plugin-html@3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/vite-plugin-html/-/vite-plugin-html-3.2.2.tgz#661834fa09015d3fda48ba694dbaa809396f5f7a"
@@ -13004,7 +13499,7 @@ vite@4.5.3:
optionalDependencies:
fsevents "~2.3.2"
-"vite@^3.0.0 || ^4.0.0 || ^5.0.0-0", "vite@^3.1.0 || ^4.0.0 || ^5.0.0-0", vite@^5.0.0, vite@^5.3.4:
+"vite@^3.0.0 || ^4.0.0 || ^5.0.0-0", "vite@^3.1.0 || ^4.0.0 || ^5.0.0-0", vite@^5.0.0, vite@^5.3.1, vite@^5.3.4:
version "5.4.0"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.0.tgz#11dca8a961369ba8b5cae42d068c7ad684d5370f"
integrity sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==
@@ -13149,7 +13644,7 @@ vscode-languageserver@^7.0.0:
dependencies:
vscode-languageserver-protocol "3.16.0"
-vscode-uri@^3.0.2:
+vscode-uri@^3.0.2, vscode-uri@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==
@@ -13166,7 +13661,7 @@ vue-component-type-helpers@^2.0.0:
resolved "https://registry.yarnpkg.com/vue-component-type-helpers/-/vue-component-type-helpers-2.0.29.tgz#3e476321482526c63b3bbe3771eae1ad55f58a01"
integrity sha512-58i+ZhUAUpwQ+9h5Hck0D+jr1qbYl4voRt5KffBx8qzELViQ4XdT/Tuo+mzq8u63teAG8K0lLaOiL5ofqW38rg==
-vue-demi@>=0.14.5, vue-demi@>=0.14.6, vue-demi@>=0.14.8:
+vue-demi@>=0.14.5, vue-demi@>=0.14.6, vue-demi@>=0.14.8, vue-demi@^0.14.10:
version "0.14.10"
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04"
integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==
@@ -13193,7 +13688,14 @@ vue-resize@^2.0.0-alpha.1:
resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz#43eeb79e74febe932b9b20c5c57e0ebc14e2df3a"
integrity sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==
-vue-router@4.4.3, vue-router@^4.4.0:
+vue-router@4.3.2:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.3.2.tgz#08096c7765dacc6832f58e35f7a081a8b34116a7"
+ integrity sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==
+ dependencies:
+ "@vue/devtools-api" "^6.5.1"
+
+vue-router@4.4.3, vue-router@^4.3.3, vue-router@^4.4.0:
version "4.4.3"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.4.3.tgz#58a39dc804632bfb6d26f052aa8f6718bd130299"
integrity sha512-sv6wmNKx2j3aqJQDMxLFzs/u/mjA9Z5LCgy6BE0f7yFWMjrPLnS/sPNn8ARY/FXw6byV18EFutn5lTO6+UsV5A==
@@ -13220,7 +13722,7 @@ vue-template-compiler@^2.6.11, vue-template-compiler@^2.7.14:
de-indent "^1.0.2"
he "^1.2.0"
-vue-tsc@^1.8.20:
+vue-tsc@^1.8.20, vue-tsc@^1.8.27:
version "1.8.27"
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.27.tgz#feb2bb1eef9be28017bb9e95e2bbd1ebdd48481c"
integrity sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==
@@ -13229,6 +13731,15 @@ vue-tsc@^1.8.20:
"@vue/language-core" "1.8.27"
semver "^7.5.4"
+vue-tsc@^2.0.21:
+ version "2.0.29"
+ resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.0.29.tgz#bf7e9605af9fadec7fd6037d242217f5c6ad2c3b"
+ integrity sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==
+ dependencies:
+ "@volar/typescript" "~2.4.0-alpha.18"
+ "@vue/language-core" "2.0.29"
+ semver "^7.5.4"
+
vue-types@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/vue-types/-/vue-types-4.2.1.tgz#f8f7e5fb42d4a6acda6d92c9736b510e5534c753"
@@ -13256,7 +13767,18 @@ vue3-cookies@^1.0.6:
dependencies:
vue "^3.0.0"
-vue@3.4.37, vue@^3.0.0, vue@^3.4.32:
+vue@3.4.27:
+ version "3.4.27"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.27.tgz#40b7d929d3e53f427f7f5945386234d2854cc2a1"
+ integrity sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==
+ dependencies:
+ "@vue/compiler-dom" "3.4.27"
+ "@vue/compiler-sfc" "3.4.27"
+ "@vue/runtime-dom" "3.4.27"
+ "@vue/server-renderer" "3.4.27"
+ "@vue/shared" "3.4.27"
+
+vue@3.4.37, vue@^3.0.0, vue@^3.4.29, vue@^3.4.32:
version "3.4.37"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.37.tgz#64ce0eeb8de16a29fb74e504777ee8c0c1cf229e"
integrity sha512-3vXvNfkKTBsSJ7JP+LyR7GBuwQuckbWvuwAid3xbqK9ppsKt/DUvfqgZ48fgOLEfpy1IacL5f8QhUVl77RaI7A==
@@ -13560,7 +14082,7 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
-yaml@^2.3.4, yaml@^2.5.0:
+yaml@^2.3.2, yaml@^2.3.4, yaml@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.0.tgz#c6165a721cf8000e91c36490a41d7be25176cf5d"
integrity sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==
@@ -13615,6 +14137,17 @@ yocto-queue@^1.0.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110"
integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==
+z-schema@~5.0.2:
+ version "5.0.6"
+ resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-5.0.6.tgz#46d6a687b15e4a4369e18d6cb1c7b8618fc256c5"
+ integrity sha512-+XR1GhnWklYdfr8YaZv/iu+vY+ux7V5DS5zH1DQf6bO5ufrt/5cgNhVO5qyhsjFXvsqQb/f08DWE9b6uPscyAg==
+ dependencies:
+ lodash.get "^4.4.2"
+ lodash.isequal "^4.5.0"
+ validator "^13.7.0"
+ optionalDependencies:
+ commander "^10.0.0"
+
zen-observable-ts@^0.8.21:
version "0.8.21"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d"
diff --git a/backend/molgenis-emx2-analytics/build.gradle b/backend/molgenis-emx2-analytics/build.gradle
new file mode 100644
index 0000000000..d8593e486c
--- /dev/null
+++ b/backend/molgenis-emx2-analytics/build.gradle
@@ -0,0 +1,5 @@
+dependencies {
+ implementation project(':backend:molgenis-emx2')
+ implementation project(':backend:molgenis-emx2-sql')
+ implementation 'org.apache.commons:commons-text:1.10.0'
+}
diff --git a/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/Trigger.java b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/Trigger.java
new file mode 100644
index 0000000000..ce2a7b3d13
--- /dev/null
+++ b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/Trigger.java
@@ -0,0 +1,8 @@
+package org.molgenis.emx2.analytics.model;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
+@JsonSerialize
+public record Trigger(String name, String cssSelector, String schemaName, String appName) {}
diff --git a/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/CreateTriggerAction.java b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/CreateTriggerAction.java
new file mode 100644
index 0000000000..46a1d4b21d
--- /dev/null
+++ b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/CreateTriggerAction.java
@@ -0,0 +1,6 @@
+package org.molgenis.emx2.analytics.model.actions;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public record CreateTriggerAction(
+ @JsonProperty("name") String name, @JsonProperty("cssSelector") String cssSelector) {}
diff --git a/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/DeleteTriggerAction.java b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/DeleteTriggerAction.java
new file mode 100644
index 0000000000..9630bd8f78
--- /dev/null
+++ b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/DeleteTriggerAction.java
@@ -0,0 +1,5 @@
+package org.molgenis.emx2.analytics.model.actions;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public record DeleteTriggerAction(@JsonProperty("name") String name) {}
diff --git a/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/UpdateTriggerAction.java b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/UpdateTriggerAction.java
new file mode 100644
index 0000000000..bf39d6b04a
--- /dev/null
+++ b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/model/actions/UpdateTriggerAction.java
@@ -0,0 +1,5 @@
+package org.molgenis.emx2.analytics.model.actions;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public record UpdateTriggerAction(@JsonProperty("cssSelector") String cssSelector) {}
diff --git a/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/repository/TriggerRepository.java b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/repository/TriggerRepository.java
new file mode 100644
index 0000000000..131ca72153
--- /dev/null
+++ b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/repository/TriggerRepository.java
@@ -0,0 +1,15 @@
+package org.molgenis.emx2.analytics.repository;
+
+import java.util.List;
+import org.molgenis.emx2.Schema;
+import org.molgenis.emx2.analytics.model.Trigger;
+
+public interface TriggerRepository {
+ Trigger addTrigger(Trigger trigger);
+
+ List getTriggersForSchema(Schema schema);
+
+ boolean deleteTrigger(String triggerName);
+
+ boolean updateTrigger(Trigger trigger);
+}
diff --git a/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/repository/TriggerRepositoryImpl.java b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/repository/TriggerRepositoryImpl.java
new file mode 100644
index 0000000000..3a34357f56
--- /dev/null
+++ b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/repository/TriggerRepositoryImpl.java
@@ -0,0 +1,192 @@
+package org.molgenis.emx2.analytics.repository;
+
+import static org.molgenis.emx2.Column.column;
+import static org.molgenis.emx2.Constants.SYSTEM_SCHEMA;
+import static org.molgenis.emx2.FilterBean.f;
+import static org.molgenis.emx2.Operator.EQUALS;
+import static org.molgenis.emx2.Row.row;
+import static org.molgenis.emx2.TableMetadata.table;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.molgenis.emx2.*;
+import org.molgenis.emx2.analytics.model.Trigger;
+
+public class TriggerRepositoryImpl implements TriggerRepository {
+ static final String TRIGGER_TABLE_NAME = "AnalyticsTrigger";
+
+ private static final String NAME = "name";
+ private static final String CSS_SELECTOR = "cssSelector";
+ private static final String APP_NAME = "appName";
+ private static final String SCHEMA_NAME = "schemaName";
+
+ private final Database database;
+
+ public TriggerRepositoryImpl(Database database) {
+ this.database = database;
+ String currentUser = database.getActiveUser();
+ this.database.tx(
+ db -> {
+ try {
+ db.becomeAdmin();
+
+ Schema sysSchema = db.getSchema(SYSTEM_SCHEMA);
+ if (sysSchema == null) {
+ throw new MolgenisException("failed to setup Analytics TriggerRepository");
+ }
+
+ if (!sysSchema.getTableNames().contains(TRIGGER_TABLE_NAME)) {
+ sysSchema.create(
+ table(
+ TRIGGER_TABLE_NAME,
+ column(NAME).setPkey(),
+ column(CSS_SELECTOR).setType(ColumnType.TEXT).setRequired(true),
+ column(APP_NAME).setRequired(false),
+ column(SCHEMA_NAME).setRequired(false)));
+ }
+ } finally {
+ db.setActiveUser(currentUser);
+ }
+ });
+ }
+
+ @Override
+ public Trigger addTrigger(Trigger trigger) {
+ AtomicInteger inserted = new AtomicInteger();
+
+ this.database.tx(
+ db -> {
+ Schema sysSchema = db.getSchema(SYSTEM_SCHEMA);
+ if (sysSchema == null) {
+ throw new MolgenisException("failed to add analytics Trigger");
+ }
+ Table triggerTable = sysSchema.getTable(TRIGGER_TABLE_NAME);
+ Row triggerRow =
+ row(
+ NAME,
+ trigger.name(),
+ CSS_SELECTOR,
+ trigger.cssSelector(),
+ APP_NAME,
+ trigger.appName(),
+ SCHEMA_NAME,
+ trigger.schemaName());
+ inserted.set(triggerTable.insert(triggerRow));
+ });
+
+ if (inserted.get() == 1) {
+ return trigger;
+ } else {
+ throw new MolgenisException("failed to add trigger");
+ }
+ }
+
+ @Override
+ public List getTriggersForSchema(Schema schema) {
+ List triggers = new ArrayList<>();
+ this.database.tx(
+ db -> {
+ String currentUser = database.getActiveUser();
+
+ if (!schema.getInheritedRolesForActiveUser().contains(Privileges.VIEWER.toString())) {
+ throw new MolgenisException(
+ "failed fetch analytics triggers for schema " + schema.getName());
+ }
+ try {
+ db.becomeAdmin();
+
+ Schema sysSchema = db.getSchema(SYSTEM_SCHEMA);
+ if (sysSchema == null) {
+ throw new MolgenisException(
+ "failed fetch analytics triggers for schema " + schema.getName());
+ }
+
+ Table triggerTable = sysSchema.getTable(TRIGGER_TABLE_NAME);
+ List list =
+ triggerTable
+ .select(
+ SelectColumn.s(NAME),
+ SelectColumn.s(CSS_SELECTOR),
+ SelectColumn.s(SCHEMA_NAME),
+ SelectColumn.s(APP_NAME))
+ .where(f(SCHEMA_NAME, EQUALS, schema.getName()))
+ .retrieveRows()
+ .stream()
+ .map(
+ r ->
+ new Trigger(
+ r.getString(NAME),
+ r.getText(CSS_SELECTOR),
+ r.getString(SCHEMA_NAME),
+ r.getString(APP_NAME)))
+ .toList();
+ triggers.addAll(list);
+
+ } finally {
+ db.setActiveUser(currentUser);
+ }
+ });
+ return triggers;
+ }
+
+ @Override
+ public boolean deleteTrigger(String triggerName) {
+ AtomicInteger deleted = new AtomicInteger();
+
+ this.database.tx(
+ db -> {
+ Schema sysSchema = db.getSchema(SYSTEM_SCHEMA);
+ if (sysSchema == null) {
+ throw new MolgenisException("failed to delete analytics Trigger");
+ }
+
+ Table triggerTable = sysSchema.getTable(TRIGGER_TABLE_NAME);
+ List rowList =
+ triggerTable.query().where(f(NAME, EQUALS, triggerName)).retrieveRows();
+ if (1 != rowList.size()) {
+ throw new MolgenisException("failed to delete trigger");
+ }
+
+ deleted.set(triggerTable.delete(rowList));
+ });
+
+ if (deleted.get() == 1) {
+ return true;
+ } else {
+ throw new MolgenisException("failed to delete trigger");
+ }
+ }
+ ;
+
+ @Override
+ public boolean updateTrigger(Trigger trigger) {
+ AtomicInteger updated = new AtomicInteger();
+
+ this.database.tx(
+ db -> {
+ Schema sysSchema = db.getSchema(SYSTEM_SCHEMA);
+ if (sysSchema == null) {
+ throw new MolgenisException("failed to update analytics Trigger");
+ }
+
+ Table triggerTable = sysSchema.getTable(TRIGGER_TABLE_NAME);
+ List rowList =
+ triggerTable.query().where(f(NAME, EQUALS, trigger.name())).retrieveRows();
+ if (1 != rowList.size()) {
+ throw new MolgenisException("failed to update trigger");
+ }
+
+ Row toUpdate = rowList.get(0);
+ toUpdate.setText(CSS_SELECTOR, trigger.cssSelector());
+
+ updated.set(triggerTable.update(rowList));
+ });
+
+ if (updated.get() == 1) {
+ return true;
+ } else {
+ throw new MolgenisException("failed to update trigger");
+ }
+ }
+}
diff --git a/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/service/AnalyticsService.java b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/service/AnalyticsService.java
new file mode 100644
index 0000000000..dbbf2c3c51
--- /dev/null
+++ b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/service/AnalyticsService.java
@@ -0,0 +1,15 @@
+package org.molgenis.emx2.analytics.service;
+
+import org.molgenis.emx2.Schema;
+import org.molgenis.emx2.analytics.model.actions.CreateTriggerAction;
+import org.molgenis.emx2.analytics.model.actions.DeleteTriggerAction;
+import org.molgenis.emx2.analytics.model.actions.UpdateTriggerAction;
+
+public interface AnalyticsService {
+ void createTriggerForSchema(Schema schema, CreateTriggerAction createTriggerAction);
+
+ boolean deleteTriggerForSchema(Schema schema, DeleteTriggerAction deleteTriggerAction);
+
+ boolean updateTriggerForSchema(
+ Schema schema, String triggerName, UpdateTriggerAction updateTriggerAction);
+}
diff --git a/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/service/AnalyticsServiceImpl.java b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/service/AnalyticsServiceImpl.java
new file mode 100644
index 0000000000..0dec9ee843
--- /dev/null
+++ b/backend/molgenis-emx2-analytics/src/main/java/org/molgenis/emx2/analytics/service/AnalyticsServiceImpl.java
@@ -0,0 +1,38 @@
+package org.molgenis.emx2.analytics.service;
+
+import org.molgenis.emx2.Schema;
+import org.molgenis.emx2.analytics.model.Trigger;
+import org.molgenis.emx2.analytics.model.actions.CreateTriggerAction;
+import org.molgenis.emx2.analytics.model.actions.DeleteTriggerAction;
+import org.molgenis.emx2.analytics.model.actions.UpdateTriggerAction;
+import org.molgenis.emx2.analytics.repository.TriggerRepository;
+
+public class AnalyticsServiceImpl implements AnalyticsService {
+
+ private final TriggerRepository triggerRepository;
+
+ public AnalyticsServiceImpl(TriggerRepository triggerRepository) {
+ this.triggerRepository = triggerRepository;
+ }
+
+ @Override
+ public void createTriggerForSchema(Schema schema, CreateTriggerAction createTriggerAction) {
+ Trigger trigger =
+ new Trigger(
+ createTriggerAction.name(), createTriggerAction.cssSelector(), schema.getName(), null);
+ this.triggerRepository.addTrigger(trigger);
+ }
+
+ @Override
+ public boolean deleteTriggerForSchema(Schema schema, DeleteTriggerAction deleteTriggerAction) {
+ return this.triggerRepository.deleteTrigger(deleteTriggerAction.name());
+ }
+
+ @Override
+ public boolean updateTriggerForSchema(
+ Schema schema, String triggerName, UpdateTriggerAction updateTriggerAction) {
+ Trigger trigger =
+ new Trigger(triggerName, updateTriggerAction.cssSelector(), schema.getName(), null);
+ return triggerRepository.updateTrigger(trigger);
+ }
+}
diff --git a/backend/molgenis-emx2-analytics/src/test/java/org/molgenis/emx2/analytics/repository/TriggerRepositoryTest.java b/backend/molgenis-emx2-analytics/src/test/java/org/molgenis/emx2/analytics/repository/TriggerRepositoryTest.java
new file mode 100644
index 0000000000..9f6bfa2407
--- /dev/null
+++ b/backend/molgenis-emx2-analytics/src/test/java/org/molgenis/emx2/analytics/repository/TriggerRepositoryTest.java
@@ -0,0 +1,54 @@
+package org.molgenis.emx2.analytics.repository;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Collections;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.molgenis.emx2.Database;
+import org.molgenis.emx2.Schema;
+import org.molgenis.emx2.analytics.model.Trigger;
+import org.molgenis.emx2.sql.SqlDatabase;
+import org.molgenis.emx2.sql.TestDatabaseFactory;
+
+@Tag("slow")
+class TriggerRepositoryTest {
+
+ static Database database;
+ static Schema testTriggerRepo;
+ static TriggerRepository triggerRepository;
+
+ @BeforeAll
+ static void setUp() {
+ database = new SqlDatabase(true);
+ database.setActiveUser(database.getAdminUserName());
+
+ database = TestDatabaseFactory.getTestDatabase();
+ testTriggerRepo =
+ database.hasSchema("testTriggerRepo")
+ ? database.getSchema("testTriggerRepo")
+ : database.createSchema("testTriggerRepo");
+ triggerRepository = new TriggerRepositoryImpl(database);
+ }
+
+ @Test
+ void addAndGetTrigger() {
+ try {
+ Trigger t = new Trigger("triggerName", "triggerDescription", "testTriggerRepo", null);
+ triggerRepository.addTrigger(t);
+ assertEquals(
+ Collections.singletonList(t), triggerRepository.getTriggersForSchema(testTriggerRepo));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ String deleteQuery =
+ """
+ DELETE
+ FROM "_SYSTEM_"."AnalyticsTrigger"
+ WHERE name LIKE 'triggerName' ESCAPE '#';
+ """;
+ ((SqlDatabase) database).getJooq().execute(deleteQuery);
+ }
+ }
+}
diff --git a/backend/molgenis-emx2-analytics/src/test/java/org/molgenis/emx2/analytics/service/TestAnalyticsService.java b/backend/molgenis-emx2-analytics/src/test/java/org/molgenis/emx2/analytics/service/TestAnalyticsService.java
new file mode 100644
index 0000000000..78c45aa4de
--- /dev/null
+++ b/backend/molgenis-emx2-analytics/src/test/java/org/molgenis/emx2/analytics/service/TestAnalyticsService.java
@@ -0,0 +1,24 @@
+package org.molgenis.emx2.analytics.service;
+
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.Test;
+import org.molgenis.emx2.Schema;
+import org.molgenis.emx2.analytics.model.Trigger;
+import org.molgenis.emx2.analytics.model.actions.CreateTriggerAction;
+import org.molgenis.emx2.analytics.repository.TriggerRepository;
+
+public class TestAnalyticsService {
+
+ @Test
+ public void createTriggerForSchema() {
+ TriggerRepository triggerRepository = mock(TriggerRepository.class);
+ Schema schema = mock(Schema.class);
+ when(schema.getName()).thenReturn("test-schema");
+ AnalyticsServiceImpl service = new AnalyticsServiceImpl(triggerRepository);
+ service.createTriggerForSchema(schema, new CreateTriggerAction("triggerName", "my selector"));
+
+ verify(triggerRepository, times(1))
+ .addTrigger(new Trigger("triggerName", "my selector", "test-schema", null));
+ }
+}
diff --git a/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlDatabase.java b/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlDatabase.java
index c09eeb507f..22ea4b0ec6 100644
--- a/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlDatabase.java
+++ b/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlDatabase.java
@@ -3,6 +3,7 @@
import static org.jooq.impl.DSL.name;
import static org.molgenis.emx2.ColumnType.STRING;
import static org.molgenis.emx2.Constants.MG_USER_PREFIX;
+import static org.molgenis.emx2.Constants.SYSTEM_SCHEMA;
import static org.molgenis.emx2.sql.SqlDatabaseExecutor.*;
import static org.molgenis.emx2.sql.SqlSchemaMetadataExecutor.executeCreateSchema;
@@ -160,6 +161,13 @@ public void init() { // setup default stuff
setUserPassword(ADMIN_USER, initialAdminPassword);
}
+ this.tx(
+ tdb -> {
+ if (!this.hasSchema(SYSTEM_SCHEMA)) {
+ this.createSchema(SYSTEM_SCHEMA);
+ }
+ });
+
// get the settings
clearCache();
diff --git a/backend/molgenis-emx2-webapi/build.gradle b/backend/molgenis-emx2-webapi/build.gradle
index 77911f63a5..9f6c089469 100644
--- a/backend/molgenis-emx2-webapi/build.gradle
+++ b/backend/molgenis-emx2-webapi/build.gradle
@@ -10,6 +10,7 @@ dependencies {
implementation project(':backend:molgenis-emx2-fairdatapoint')
implementation project(':backend:molgenis-emx2-tasks')
implementation project(':backend:molgenis-emx2-email')
+ implementation project(':backend:molgenis-emx2-analytics')
implementation 'org.eclipse.rdf4j:rdf4j-rio-api:4.3.12'
implementation 'com.sparkjava:spark-core:2.9.4'
implementation 'io.swagger.parser.v3:swagger-parser:2.1.22'
diff --git a/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/AnalyticsApi.java b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/AnalyticsApi.java
new file mode 100644
index 0000000000..a23782030d
--- /dev/null
+++ b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/AnalyticsApi.java
@@ -0,0 +1,114 @@
+package org.molgenis.emx2.web;
+
+import static org.molgenis.emx2.web.MolgenisWebservice.*;
+import static spark.Spark.*;
+
+import java.util.List;
+import org.molgenis.emx2.Database;
+import org.molgenis.emx2.Schema;
+import org.molgenis.emx2.analytics.model.Trigger;
+import org.molgenis.emx2.analytics.model.actions.CreateTriggerAction;
+import org.molgenis.emx2.analytics.model.actions.DeleteTriggerAction;
+import org.molgenis.emx2.analytics.model.actions.UpdateTriggerAction;
+import org.molgenis.emx2.analytics.repository.TriggerRepositoryImpl;
+import org.molgenis.emx2.analytics.service.AnalyticsServiceImpl;
+import org.molgenis.emx2.sql.SqlDatabase;
+import org.molgenis.emx2.web.response.ResponseStatus;
+import org.molgenis.emx2.web.response.Status;
+import org.molgenis.emx2.web.transformers.ActionTransformer;
+import org.molgenis.emx2.web.transformers.JsonTransformer;
+import spark.Request;
+import spark.Response;
+
+public class AnalyticsApi {
+
+ private static final ActionTransformer actionTransformer = new ActionTransformer();
+ public static final ResponseStatus STATUS_SUCCESS = new ResponseStatus(Status.SUCCESS);
+ public static final ResponseStatus STATUS_FAILED = new ResponseStatus(Status.FAILED);
+ private static final String TRIGGER_PARAM = ":trigger";
+
+ private AnalyticsApi() {
+ // hide constructor
+ }
+
+ public static void create() {
+
+ new TriggerRepositoryImpl(new SqlDatabase(false));
+
+ JsonTransformer jsonTransformer = new JsonTransformer();
+
+ post("/:schema/api/trigger", AnalyticsApi::addTrigger, jsonTransformer);
+ get("/:schema/api/trigger", AnalyticsApi::listSchemaTriggers, jsonTransformer);
+ delete("/:schema/api/trigger/" + TRIGGER_PARAM, AnalyticsApi::deleteTrigger, jsonTransformer);
+ put("/:schema/api/trigger/" + TRIGGER_PARAM, AnalyticsApi::updateTrigger, jsonTransformer);
+
+ post("apps/:app/:schema/api/trigger", AnalyticsApi::addTrigger, jsonTransformer);
+ get("apps/:app/:schema/api/trigger", AnalyticsApi::listSchemaTriggers, jsonTransformer);
+ delete(
+ "apps/:app/:schema/api/trigger/" + TRIGGER_PARAM,
+ AnalyticsApi::deleteTrigger,
+ jsonTransformer);
+ put(
+ "apps/:app/:schema/api/trigger/" + TRIGGER_PARAM,
+ AnalyticsApi::updateTrigger,
+ jsonTransformer);
+ }
+
+ private static ResponseStatus deleteTrigger(Request request, Response response) {
+ response.type("application/json");
+ var action = new DeleteTriggerAction(sanitize(request.params(TRIGGER_PARAM)));
+ MolgenisSession session = sessionManager.getSession(request);
+ String schemaName = sanitize(request.params(SCHEMA));
+ Database database = session.getDatabase();
+ Schema schema = database.getSchema(schemaName);
+
+ TriggerRepositoryImpl triggerRepository = new TriggerRepositoryImpl(database);
+ AnalyticsServiceImpl analyticsService = new AnalyticsServiceImpl(triggerRepository);
+
+ return analyticsService.deleteTriggerForSchema(schema, action) ? STATUS_SUCCESS : STATUS_FAILED;
+ }
+
+ private static List listSchemaTriggers(Request request, Response response) {
+ response.type("application/json");
+ MolgenisSession session = sessionManager.getSession(request);
+ String schemaName = sanitize(request.params(SCHEMA));
+ Database database = session.getDatabase();
+ Schema schema = database.getSchema(schemaName);
+
+ TriggerRepositoryImpl triggerRepository = new TriggerRepositoryImpl(database);
+
+ return triggerRepository.getTriggersForSchema(schema);
+ }
+
+ private static ResponseStatus addTrigger(Request request, Response response) {
+ response.type("application/json");
+ var createTriggerAction =
+ actionTransformer.transform(request.body(), CreateTriggerAction.class);
+ MolgenisSession session = sessionManager.getSession(request);
+ String schemaName = sanitize(request.params(SCHEMA));
+ Database database = session.getDatabase();
+ Schema schema = database.getSchema(schemaName);
+
+ TriggerRepositoryImpl triggerRepository = new TriggerRepositoryImpl(database);
+ AnalyticsServiceImpl analyticsService = new AnalyticsServiceImpl(triggerRepository);
+ analyticsService.createTriggerForSchema(schema, createTriggerAction);
+
+ return STATUS_SUCCESS;
+ }
+
+ private static ResponseStatus updateTrigger(Request request, Response response) {
+ response.type("application/json");
+ var action = actionTransformer.transform(request.body(), UpdateTriggerAction.class);
+ MolgenisSession session = sessionManager.getSession(request);
+ String schemaName = sanitize(request.params(SCHEMA));
+ Database database = session.getDatabase();
+ Schema schema = database.getSchema(schemaName);
+
+ TriggerRepositoryImpl triggerRepository = new TriggerRepositoryImpl(database);
+ AnalyticsServiceImpl analyticsService = new AnalyticsServiceImpl(triggerRepository);
+ analyticsService.updateTriggerForSchema(
+ schema, sanitize(request.params(TRIGGER_PARAM)), action);
+
+ return STATUS_SUCCESS;
+ }
+}
diff --git a/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/Constants.java b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/Constants.java
index 7239b16220..bf619f2047 100644
--- a/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/Constants.java
+++ b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/Constants.java
@@ -3,7 +3,9 @@
public class Constants {
static final String TABLE = "table";
public static final String CONTENT_TYPE = "Content-type";
- public static final String MOLGENIS_TOKEN[] = new String[] {"x-molgenis-token", "auth-key"};
+ public static final String X_MOLGENIS_TOKEN = "x-molgenis-token";
+ public static final String AUTH_KEY_TOKEN = "auth-key";
+ public static final String[] MOLGENIS_TOKEN = new String[] {X_MOLGENIS_TOKEN, AUTH_KEY_TOKEN};
public static final String LANDING_PAGE = "LANDING_PAGE";
private Constants() {
diff --git a/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/GraphqlApi.java b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/GraphqlApi.java
index 6b3a2d2b96..19a8ccf189 100644
--- a/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/GraphqlApi.java
+++ b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/GraphqlApi.java
@@ -44,6 +44,11 @@ private GraphqlApi() {
public static void createGraphQLservice(MolgenisSessionManager sm) {
sessionManager = sm;
+ // per schema graphql calls from app
+ final String appGqlPath = "apps/:app/:schema/graphql"; // NOSONAR
+ get(appGqlPath, GraphqlApi::handleSchemaRequests);
+ post(appGqlPath, GraphqlApi::handleSchemaRequests);
+
// per database graphql
final String databasePath = "/api/graphql";
get(databasePath, GraphqlApi::handleDatabaseRequests);
diff --git a/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/MolgenisWebservice.java b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/MolgenisWebservice.java
index 71ffb6096a..7893044a49 100644
--- a/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/MolgenisWebservice.java
+++ b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/MolgenisWebservice.java
@@ -106,6 +106,7 @@ public static void start(int port) {
FAIRDataPointApi.create(sessionManager);
BootstrapThemeService.create();
ProfilesApi.create();
+ AnalyticsApi.create();
get(
"/:schema",
diff --git a/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/response/ResponseStatus.java b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/response/ResponseStatus.java
new file mode 100644
index 0000000000..1c54593605
--- /dev/null
+++ b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/response/ResponseStatus.java
@@ -0,0 +1,3 @@
+package org.molgenis.emx2.web.response;
+
+public record ResponseStatus(Status status) {}
diff --git a/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/response/Status.java b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/response/Status.java
new file mode 100644
index 0000000000..7ee708668b
--- /dev/null
+++ b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/response/Status.java
@@ -0,0 +1,6 @@
+package org.molgenis.emx2.web.response;
+
+public enum Status {
+ SUCCESS,
+ FAILED
+}
diff --git a/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/transformers/ActionTransformer.java b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/transformers/ActionTransformer.java
new file mode 100644
index 0000000000..fa062575db
--- /dev/null
+++ b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/transformers/ActionTransformer.java
@@ -0,0 +1,18 @@
+package org.molgenis.emx2.web.transformers;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.molgenis.emx2.MolgenisException;
+
+public class ActionTransformer {
+
+ ObjectMapper objectMapper = new ObjectMapper();
+
+ public T transform(String input, Class valueType) {
+ try {
+ return objectMapper.readValue(input, valueType);
+ } catch (JsonProcessingException e) {
+ throw new MolgenisException("Could not parse JSON", e);
+ }
+ }
+}
diff --git a/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/transformers/JsonTransformer.java b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/transformers/JsonTransformer.java
new file mode 100644
index 0000000000..24068c120e
--- /dev/null
+++ b/backend/molgenis-emx2-webapi/src/main/java/org/molgenis/emx2/web/transformers/JsonTransformer.java
@@ -0,0 +1,14 @@
+package org.molgenis.emx2.web.transformers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import spark.ResponseTransformer;
+
+public class JsonTransformer implements ResponseTransformer {
+
+ private final ObjectMapper jacksonMapper = new ObjectMapper();
+
+ @Override
+ public String render(Object model) throws Exception {
+ return jacksonMapper.writeValueAsString(model);
+ }
+}
diff --git a/backend/molgenis-emx2-webapi/src/test/java/org.molgenis.emx2.web/WebApiSmokeTests.java b/backend/molgenis-emx2-webapi/src/test/java/org.molgenis.emx2.web/WebApiSmokeTests.java
index 3584ccca37..cdb5fc5c19 100644
--- a/backend/molgenis-emx2-webapi/src/test/java/org.molgenis.emx2.web/WebApiSmokeTests.java
+++ b/backend/molgenis-emx2-webapi/src/test/java/org.molgenis.emx2.web/WebApiSmokeTests.java
@@ -30,6 +30,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.*;
@@ -701,15 +702,8 @@ public void testMolgenisWebservice_redirectToFirstMenuItem() {
public void testTokenBasedAuth() throws JsonProcessingException {
// check if we can use temporary token
- String result =
- given()
- .body(
- "{\"query\":\"mutation{signin(email:\\\"shopmanager\\\",password:\\\"shopmanager\\\"){message,token}}\"}")
- .when()
- .post("/api/graphql")
- .getBody()
- .asString();
- String token = new ObjectMapper().readTree(result).at("/data/signin/token").textValue();
+ String token = getToken("shopmanager", "shopmanager");
+ String result;
// without token we are anonymous
assertTrue(
@@ -958,15 +952,8 @@ private Response downloadPet(String requestString) {
@Disabled("unstable")
public void testScriptExecution() throws JsonProcessingException, InterruptedException {
// get token for admin
- String result =
- given()
- .body(
- "{\"query\":\"mutation{signin(email:\\\"admin\\\",password:\\\"admin\\\"){message,token}}\"}")
- .when()
- .post("/api/graphql")
- .getBody()
- .asString();
- String token = new ObjectMapper().readTree(result).at("/data/signin/token").textValue();
+ String token = getToken("admin", "admin");
+ String result;
// submit simple
result =
@@ -1046,16 +1033,8 @@ public void testScriptScheduling() throws JsonProcessingException, InterruptedEx
db.getSchema(SYSTEM_SCHEMA).getTable("Jobs").truncate();
db.getSchema(SYSTEM_SCHEMA).getTable("Scripts").delete(row("name", "test"));
- // get token for admin
- String result =
- given()
- .body(
- "{\"query\":\"mutation{signin(email:\\\"admin\\\",password:\\\"admin\\\"){message,token}}\"}")
- .when()
- .post("/api/graphql")
- .getBody()
- .asString();
- String token = new ObjectMapper().readTree(result).at("/data/signin/token").textValue();
+ String token = getToken("admin", "admin");
+ String result;
// simply retrieve the results using get
// todo: also allow anonymous
@@ -1169,6 +1148,20 @@ public void testScriptScheduling() throws JsonProcessingException, InterruptedEx
assertTrue(result.contains("[]"), "script should be unscheduled");
}
+ private static String getToken(String email, String password) throws JsonProcessingException {
+ String mutation =
+ """
+ mutation { signin(email: "%s" ,password: "%s" ) { message, token } }
+ """
+ .formatted(email, password);
+
+ Map request = new HashMap<>();
+ request.put("query", mutation);
+
+ String result = given().body(request).when().post("/api/graphql").getBody().asString();
+ return new ObjectMapper().readTree(result).at("/data/signin/token").textValue();
+ }
+
@Test
void testJSONLDonJSONLDEndpoint() {
given()
@@ -1308,6 +1301,72 @@ void testProfileApi() {
assertTrue(result.contains("Samples"));
}
+ @Test
+ void testAnalyticsApi() throws JsonProcessingException {
+
+ db.getSchema(SYSTEM_SCHEMA).getTable("AnalyticsTrigger").truncate();
+ String adminToken = getToken("admin", "admin");
+
+ // add a trigger
+ Map addRequest = new HashMap<>();
+ addRequest.put("name", "my-trigger");
+ addRequest.put("cssSelector", "#my-favorite-button");
+
+ String resp =
+ given()
+ .header(X_MOLGENIS_TOKEN, adminToken)
+ .when()
+ .body(addRequest)
+ .post("/pet store/api/trigger")
+ .getBody()
+ .asString();
+ assertEquals("{\"status\":\"SUCCESS\"}", resp);
+
+ // fetch a triggers
+ String triggers = given().get("/pet store/api/trigger").getBody().asString();
+ assertEquals(
+ "[{\"name\":\"my-trigger\",\"cssSelector\":\"#my-favorite-button\",\"schemaName\":\"pet store\",\"appName\":null}]",
+ triggers);
+
+ // update a trigger
+ Map updateRequest = new HashMap<>();
+ updateRequest.put("cssSelector", "#my-update-button");
+
+ String updateResp =
+ given()
+ .header(X_MOLGENIS_TOKEN, adminToken)
+ .when()
+ .body(updateRequest)
+ .put("/pet store/api/trigger/my-trigger")
+ .getBody()
+ .asString();
+ assertEquals("{\"status\":\"SUCCESS\"}", updateResp);
+
+ // re-fetch a triggers to check update
+ String updated = given().get("/pet store/api/trigger").getBody().asString();
+ assertEquals(
+ "[{\"name\":\"my-trigger\",\"cssSelector\":\"#my-update-button\",\"schemaName\":\"pet store\",\"appName\":null}]",
+ updated);
+
+ // delete a trigger
+ given()
+ .header(X_MOLGENIS_TOKEN, adminToken)
+ .delete("/pet store/api/trigger/my-trigger")
+ .getBody()
+ .asString();
+ assertEquals("{\"status\":\"SUCCESS\"}", resp);
+
+ // refetch triggers
+ String triggersAfterDelete = given().get("/pet store/api/trigger").getBody().asString();
+ assertEquals("[]", triggersAfterDelete);
+ }
+
+ @Test
+ void signIn() throws JsonProcessingException {
+ String token = getToken("admin", "admin");
+ assertTrue(token.length() > 10);
+ }
+
private Row waitForScriptToComplete(String scriptName) throws InterruptedException {
Table jobs = db.getSchema(SYSTEM_SCHEMA).getTable("Jobs");
Filter f = f("script", f("name", EQUALS, scriptName));
diff --git a/settings.gradle b/settings.gradle
index e177dc1770..88d14da5b6 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -75,5 +75,6 @@ include(':backend:molgenis-emx2-fairdatapoint')
include(':backend:molgenis-emx2-graphgenome')
include(':backend:molgenis-emx2-email')
include(':backend:molgenis-emx2-nonparallel-tests')
+include(':backend:molgenis-emx2-analytics')
include(':tools:pyclient')