Compare commits

..
Author SHA1 Message Date
james.maes d646c35903 Merge pull request 'ci: pin shared workflow to v1' (#20) from ci/pin-v1-main into main
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (push) Successful in 16s
QRun-IO/ci-policy: native / native-result (push) Successful in 12s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Successful in 14s
build / maven (push) Successful in 2m58s
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
maven / build (push) Successful in 2m58s
2026-11-06 16:05:36 +00:00
james.maesandClaude Opus 5.5 a773770e0b ci: pin shared workflow to v1
build / maven (push) Successful in 2m38s
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (pull_request) Successful in 8s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 7s
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 8s
maven / build (push) Successful in 2m38s
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:07:29 +00:00
james.maes 4dda6a31f9 Merge pull request 'ci: use shared ci-workflows Maven workflow' (#18) from ci/ci-workflows into main
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 11s
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Successful in 5s
build / maven (push) Successful in 1m21s
QRun-IO/ci-policy: ci / Scoped workflow [workflow d20a578c7d818bc7ca32a2ac2c675da4e4f27385] (push) Successful in 8s
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
maven / build (push) Successful in 1m21s
2026-11-05 23:15:51 +00:00
james.maesandClaude Opus 5.5 7dc0340f93 ci: use shared ci-workflows Maven workflow
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1b3648d9421543350067e95e54959dfb471b26d5] (pull_request) Successful in 5s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 4s
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
build / maven (push) Successful in 1m26s
QRun-IO/ci-policy: Shared Java Maven CI / verify (pull_request) Successful in 1m25s
QRun-IO/ci-policy: Shared Java Maven CI / secrets (pull_request) Failing after 1s
QRun-IO/ci-policy: Shared Java Maven CI / gate (pull_request) Failing after 1s
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Failing after 16m8s
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 2s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
maven / build (push) Successful in 1m26s
Replace the platform-ci release callers with one caller of
KofTwentyTwo/ci-workflows maven.yaml (qqq-orb port).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:03:47 +00:00
forge-recovery 901fdb701a ci: enroll shared Gitea lifecycle
QRun-IO/ci-policy: ci / ci-result (push) Successful in 7s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
Shared Java Maven CI / secrets (push) Successful in 24s
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
QRun-IO/ci-policy: Shared Java Maven CI / secrets (push) Successful in 21s
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI / verify (push) Successful in 58s
Shared Java Maven CI / verify (push) Successful in 1m2s
QRun-IO/ci-policy: ci / java-maven-6 (push) Successful in 24m16s
Shared Java Maven CI / gate (push) Successful in 18s
qrun-release / release (push) Successful in 24m33s
qrun-release / postpublish (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI / gate (push) Successful in 16s
2026-11-02 22:13:22 +00:00
94f321032a ci: add the isolated postpublish job to the QRun release caller
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
Shared Java Maven CI / secrets (push) Successful in 16s
QRun-IO/ci-policy: Shared Java Maven CI / secrets (pull_request) Successful in 16s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 5s
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
Shared Java Maven CI / gate (push) Canceled after 0s
Shared Java Maven CI / verify (push) Canceled after 0s
qrun-release / release (push) Canceled after 2h38m57s
qrun-release / postpublish (push) Canceled after 0s
QRun-IO/ci-policy: Shared Java Maven CI / verify (pull_request) Successful in 59s
QRun-IO/ci-policy: Shared Java Maven CI / gate (pull_request) Successful in 9s
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Successful in 2h44m52s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 5s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 15:48:23 +00:00
abf66bf27b ci: move the QRun release caller to the thin v2 contract
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-2 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 5s
Shared Java Maven CI / secrets (push) Successful in 14s
QRun-IO/ci-policy: Shared Java Maven CI / secrets (pull_request) Successful in 14s
QRun-IO/ci-policy: Shared Java Maven CI / verify (pull_request) Failing after 1m19s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 5s
QRun-IO/ci-policy: Shared Java Maven CI / gate (pull_request) Canceled after 0s
qrun-release / release (push) Canceled after 1h11m50s
Shared Java Maven CI / gate (push) Canceled after 0s
Shared Java Maven CI / verify (push) Canceled after 0s
QRun-IO/ci-policy: ci / node-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-4 (pull_request) Canceled after 1h11m35s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 06:17:27 +00:00
forge-recoveryandCodex 9c0510a98c ci: add the identical QRun release caller
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-2 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI / verify (pull_request) Failing after 1m35s
QRun-IO/ci-policy: Shared Java Maven CI / secrets (pull_request) Successful in 21s
QRun-IO/ci-policy: Shared Java Maven CI / gate (pull_request) Failing after 0s
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 5s
QRun-IO/ci-policy: ci / source-config-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-4 (pull_request) Failing after 15m33s
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 04:09:33 +00:00
james.maes e22adf9d36 ci: use shared advanced CodeQL scanning
Approved scanner-only rollout. Preserve Java scanning and add workflow scanning using the immutable shared worker.
2026-10-29 16:19:19 -05:00
24 changed files with 312 additions and 1446 deletions

No files matched your search

+1 -1
View File
@@ -11,4 +11,4 @@ workflows:
branches:
ignore: automation/qbit-rc-pending
tags:
only: /^v\d+\.\d+\.\d+$/
only: /v.*/
+7
View File
@@ -0,0 +1,7 @@
name: build
on:
push:
jobs:
maven:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
secrets: inherit
+23
View File
@@ -0,0 +1,23 @@
name: CodeQL
'on':
push:
branches:
- main
- develop
- release/**
- hotfix/**
pull_request: {}
schedule:
- cron: 41 16 * * 0
workflow_dispatch: {}
permissions:
contents: read
actions: read
security-events: write
packages: read
concurrency:
group: codeql-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
analyze:
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
+2 -3
View File
@@ -6,12 +6,11 @@ browsers: true
api_checks: false
docs_only: false
release_on_main: false
release_tag_policy: stable
test_branch_policy: feature-no-hotfix
release_tag_policy: v-prefix
test_branch_policy: feature
publish:
snapshot: true
release_candidate: true
release: true
hotfix: true
asciidoc: false
release_completion: false
+1 -9
View File
@@ -24,10 +24,6 @@
<properties>
<revision>0.1.0-SNAPSHOT</revision>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
<maven.compiler.showWarnings>true</maven.compiler.showWarnings>
</properties>
<parent>
@@ -82,6 +78,7 @@
</scm>
<!-- Keep QQQ modules aligned with the published core/frontend release. -->
<dependencyManagement>
<dependencies>
<dependency>
@@ -100,11 +97,6 @@
<artifactId>qqq-backend-core</artifactId>
<version>0.40.0</version>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-user-role-permissions</artifactId>
<version>0.31.0</version>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-middleware-api</artifactId>
@@ -23,10 +23,13 @@ package com.kingsrook.qbits.customapps;
import java.util.List;
import java.util.Optional;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitConfig;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
@@ -57,6 +60,17 @@ public class CustomAppsQBitConfig implements QBitConfig
/***************************************************************************
*
***************************************************************************/
public static boolean isApiModuleAvailableAndDoesQBitIncludeApiVersions()
{
Optional<QBitMetaData> optionalQBitConfig = QContext.getQInstance().getQBits().values().stream().filter(qb -> qb.getConfig() instanceof CustomAppsQBitConfig).findFirst();
return (CustomAppsQBitConfig.getApiMiddlewareModuleAvailable() && optionalQBitConfig.isPresent() && ((CustomAppsQBitConfig) (optionalQBitConfig.get().getConfig())).getIncludeApiVersions());
}
/***************************************************************************
**
***************************************************************************/
@@ -24,14 +24,43 @@ package com.kingsrook.qbits.customapps;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.kingsrook.qbits.customapps.definition.CustomAppsRegistry;
import com.kingsrook.qbits.customapps.model.CustomApp;
import com.kingsrook.qbits.customapps.model.CustomAppBackendConfig;
import com.kingsrook.qbits.customapps.model.CustomAppBackendEnum;
import com.kingsrook.qbits.customapps.model.CustomAppContainer;
import com.kingsrook.qbits.customapps.model.CustomAppIcon;
import com.kingsrook.qbits.customapps.model.CustomAppSection;
import com.kingsrook.qbits.customapps.widgets.LookerWidgetRenderer;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaDataProducer;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
@@ -89,6 +118,214 @@ public class CustomAppsQBitProducer implements QBitMetaDataProducer<CustomAppsQB
/***************************************************************************
*
***************************************************************************/
public List<QAppMetaData> getCustomAppList(QInstance qInstance) throws QException
{
///////////////////////////////////////////////////
// look up data needed to set up the custom apps //
///////////////////////////////////////////////////
List<CustomAppContainer> customAppContainerList = QueryAction.execute(CustomAppContainer.TABLE_NAME, CustomAppContainer.class, new QQueryFilter().withOrderBy(new QFilterOrderBy("sequenceNo")));
List<CustomAppSection> customAppSectionList = QueryAction.execute(CustomAppSection.TABLE_NAME, CustomAppSection.class, new QQueryFilter().withOrderBy(new QFilterOrderBy("sequenceNo")));
List<CustomApp> customAppList = QueryAction.execute(CustomApp.TABLE_NAME, CustomApp.class, new QQueryFilter().withOrderBy(new QFilterOrderBy("sequenceNo")));
List<CustomAppIcon> customAppIcons = QueryAction.execute(CustomAppIcon.TABLE_NAME, CustomAppIcon.class, new QQueryFilter());
List<CustomAppBackendConfig> customAppBackendConfigList = QueryAction.execute(CustomAppBackendConfig.TABLE_NAME, CustomAppBackendConfig.class, new QQueryFilter());
Map<Integer, CustomAppBackendConfig> customAppBackendConfigMap = customAppBackendConfigList.stream().collect(Collectors.toMap(CustomAppBackendConfig::getId, Function.identity()));
/////////////////////
// icon id to icon //
/////////////////////
Map<Integer, CustomAppIcon> customAppIconMap = customAppIcons.stream().collect(Collectors.toMap(CustomAppIcon::getId, Function.identity()));
//////////////////////////////
// container id to sections //
//////////////////////////////
ListingHash<Integer, CustomAppSection> sectionsMap = new ListingHash<>();
customAppSectionList.forEach(section -> sectionsMap.add(section.getCustomAppContainerId(), section));
////////////////////////
// section id to apps //
////////////////////////
ListingHash<Integer, CustomApp> appsMap = new ListingHash<>();
customAppList.forEach(app -> appsMap.add(app.getCustomAppSectionId(), app));
///////////////////////////////////
// iterate over containers first //
///////////////////////////////////
List<QAppMetaData> containers = new ArrayList<>();
for(CustomAppContainer container : customAppContainerList)
{
////////////////////////////////////////
// iterate over sections for this app //
////////////////////////////////////////
List<QAppSection> sections = new ArrayList<>();
for(CustomAppSection section : sectionsMap.get(container.getId()))
{
///////////////////////
// iterate over apps //
///////////////////////
List<String> appNameList = new ArrayList<>();
if(appsMap.containsKey(section.getId()))
{
for(CustomApp app : appsMap.get(section.getId()))
{
//////////////////////////////////////////////////
// turn the label into a valid name for the app //
//////////////////////////////////////////////////
String appName = nameToCamelCase(app.getName());
appName = "qca" + StringUtils.ucFirst(appName);
/////////////////////////////////////////
// create a custom widget for this app //
/////////////////////////////////////////
String widgetName = appName + "Widget";
QWidgetMetaDataInterface widget = buildWidget(qInstance, app, customAppBackendConfigMap, widgetName);
CustomAppIcon appIcon = customAppIconMap.get(app.getCustomAppIconId());
QAppMetaData appMetaData = new QAppMetaData()
.withIcon(new QIcon(appIcon.getIconId()))
.withName(appName)
.withLabel(app.getName())
.withWidgets(List.of(widget.getName()));
appNameList.add(appMetaData.getName());
/////////////////////////////////////
// add to qinstance if not already //
/////////////////////////////////////
if(!qInstance.getApps().containsKey(appMetaData.getName()))
{
qInstance.getApps().put(appMetaData.getName(), appMetaData);
}
}
}
/////////////////////////////////////////////////
// build section with its apps and add to list //
/////////////////////////////////////////////////
String sectionName = nameToCamelCase(section.getName());
sectionName = "cas" + StringUtils.ucFirst(sectionName);
QAppSection appSection = new QAppSection()
.withApps(appNameList)
.withName(sectionName)
.withLabel(section.getName());
sections.add(appSection);
}
///////////////////////
// build the new app //
///////////////////////
String containerName = nameToCamelCase(container.getName());
containerName = "cac" + StringUtils.ucFirst(containerName);
CustomAppIcon containerIcon = customAppIconMap.get(container.getCustomAppIconId());
QAppMetaData containerApp = new QAppMetaData()
.withName(containerName)
.withLabel(container.getName())
.withIcon(new QIcon().withName(containerIcon.getIconId()))
.withSections(sections);
containers.add(containerApp);
/////////////////////////////////////
// add to qinstance if not already //
/////////////////////////////////////
if(!qInstance.getApps().containsKey(containerApp.getName()))
{
qInstance.getApps().put(containerApp.getName(), containerApp);
}
}
return (containers);
}
/*******************************************************************************
** builds a widget to be used for a customapps app
*******************************************************************************/
private QWidgetMetaDataInterface buildWidget(QInstance qInstance, CustomApp customApp, Map<Integer, CustomAppBackendConfig> customAppBackendConfigMap, String widgetName)
{
CustomAppBackendConfig customAppBackendConfig = customAppBackendConfigMap.get(customApp.getCustomAppBackendConfigId());
switch(CustomAppBackendEnum.getById(customAppBackendConfig.getCustomAppBackendId()))
{
case LOOKER:
return (buildLookerWidget(qInstance, customApp, widgetName));
default:
throw new IllegalArgumentException("Unknown custom app backend: " + customAppBackendConfig.getCustomAppBackendId());
}
}
/*******************************************************************************
** builds a widget to be used for a customapps app
*******************************************************************************/
private QWidgetMetaDataInterface buildLookerWidget(QInstance qInstance, CustomApp customApp, String widgetName)
{
String componentSourceUrl = new QMetaDataVariableInterpreter().interpret("${env.Q_CUSTOM_APPS_SOURCE_URL}");
if(StringUtils.hasContent(componentSourceUrl))
{
LOG.debug("Using component source url from environment", logPair("url", componentSourceUrl));
}
else
{
componentSourceUrl = "/dynamic-qfmd-components/q-custom-apps.bundle.js";
}
//////////////////////////////
// add the dashboard to url //
//////////////////////////////
Integer dashboardId = customApp.getLookerDashboardId();
componentSourceUrl += "?dashboardId=" + dashboardId;
Map<String, Serializable> defaultValues = MapBuilder.of(
"componentName", "CustomApp",
"componentSourceUrl", componentSourceUrl,
"dashboardId", dashboardId,
"sx", new HashMap<>(Map.of("height", "calc(90vh)", "minHeight", "600px"))
);
QWidgetMetaData lookerWidget = new QWidgetMetaData()
.withName(widgetName)
.withType(WidgetType.CUSTOM_COMPONENT.getType())
.withCodeReference(new QCodeReference(LookerWidgetRenderer.class))
.withGridColumns(12)
.withIsCard(false)
.withLabel(null)
.withDefaultValues(defaultValues);
if(qInstance.getWidget(widgetName) == null)
{
qInstance.add(lookerWidget);
}
return (lookerWidget);
}
/*******************************************************************************
** transform an app name into a camelCase name
*******************************************************************************/
private static String nameToCamelCase(String label)
{
String[] parts = label
.replaceAll("[^a-zA-Z0-9]+", " ")
.trim()
.split("\\s+");
StringBuilder result = new StringBuilder(parts[0].toLowerCase());
for(int i = 1; i < parts.length; i++)
{
result.append(parts[i].substring(0, 1).toUpperCase()).append(parts[i].substring(1).toLowerCase());
}
return result.toString();
}
/*******************************************************************************
** Getter for qBitConfig
*******************************************************************************/
@@ -1,128 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qbits.customapps.customizers;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
/*******************************************************************************
**
*******************************************************************************/
public class CustomAppContainerPlaceBeforeContainerPossibleValueSource implements QCustomPossibleValueProvider<String>
{
private static final QLogger LOG = QLogger.getLogger(CustomAppContainerPlaceBeforeContainerPossibleValueSource.class);
public static final String NAME = "placeBeforeContainer";
/*******************************************************************************
**
*******************************************************************************/
@Override
public QPossibleValue<String> getPossibleValue(Serializable idValue)
{
try
{
for(QAppMetaData app : getNavApps())
{
if(app.getLabel().equals(idValue))
{
return appToPossibleValue(app);
}
}
}
catch(Exception e)
{
LOG.error("Unable to find app with label: {}", idValue);
}
return null;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QPossibleValue<String>> search(SearchPossibleValueSourceInput input) throws QException
{
List<QPossibleValue<String>> rs = new ArrayList<>();
for(QAppMetaData app : getNavApps())
{
rs.add(appToPossibleValue(app));
}
return rs;
}
/*******************************************************************************
**
*******************************************************************************/
private static List<QAppMetaData> getNavApps() throws QException
{
//////////////////////////////////////////////////////////////////////////
// determine which apps are in the nav bar based on not having a parent //
//////////////////////////////////////////////////////////////////////////
List<QAppMetaData> navApps = new ArrayList<>();
Map<String, QAppMetaData> apps = QContext.getQInstance().getApps();
for(String appName : apps.keySet())
{
QAppMetaData app = apps.get(appName);
if(app.getParentAppName() == null)
{
navApps.add(app);
}
}
//////////////////////////////////////////
// sort the navApps by their sort order //
//////////////////////////////////////////
navApps.sort(Comparator.comparing(QAppMetaData::getSortOrder));
return (navApps);
}
/*******************************************************************************
**
*******************************************************************************/
private static QPossibleValue<String> appToPossibleValue(QAppMetaData app)
{
return new QPossibleValue<>(app.getLabel(), app.getLabel());
}
}
@@ -1,123 +0,0 @@
/*
* Copyright © 2022-2024. ColdTrack <contact@coldtrack.com>. All Rights Reserved.
*/
package com.kingsrook.qbits.customapps.customizers;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qbits.customapps.model.CustomAppContainer;
import com.kingsrook.qbits.customapps.utils.CustomAppPermissionUtils;
import com.kingsrook.qbits.customapps.utils.CustomAppsUtils;
import com.kingsrook.qbits.userrolepermissions.model.Permission;
import com.kingsrook.qqq.backend.core.actions.customizers.RecordCustomizerUtilityInterface;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
**
*******************************************************************************/
public class CustomAppContainerTableCustomizer implements TableCustomizerInterface
{
/*******************************************************************************
** custom actions to run after table meta data was created by the meta data action
**
*******************************************************************************/
@Override
public void postMetaDataAction(TableMetaDataInput tableMetaDataInput, TableMetaDataOutput tableMetaDataOutput) throws QException
{
/////////////////////////////////
// add dynamic chips for icons //
/////////////////////////////////
CustomAppsUtils.addDynamicIconChipAdornments(tableMetaDataOutput.getTable(), "customAppIconId");
}
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
if(!isPreview)
{
preInsertOrUpdate(records, Optional.empty());
}
return (records);
}
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{
if(!isPreview)
{
preInsertOrUpdate(records, oldRecordList);
}
return (records);
}
/*******************************************************************************
**
*******************************************************************************/
private void preInsertOrUpdate(List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
Optional<Map<Serializable, QRecord>> oldRecordMap = oldRecordListToMap("id", oldRecordList);
for(QRecord record : records)
{
///////////////////////////////////////////////////////////////////////
// create a permission for this container (or insert if none exists) //
///////////////////////////////////////////////////////////////////////
String name = ValueUtils.getValueAsString(RecordCustomizerUtilityInterface.getValueFromRecordElseFromOldRecord("name", record, record.getValue("id"), oldRecordMap));
CustomAppContainer container = new CustomAppContainer(record).withName(name);
Permission permission = CustomAppPermissionUtils.buildOrGetPermission(container.getName(), CustomAppContainer.TABLE_NAME);
record.setValue("permissionId", permission.getId());
}
}
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
{
List<Serializable> permissionIds = new ArrayList<>();
for(QRecord record : records)
{
////////////////////////////////////////////
// delete a permission for this container //
////////////////////////////////////////////
CustomAppContainer container = new CustomAppContainer(record);
Integer permissionId = container.getPermissionId();
permissionIds.add(permissionId);
}
new DeleteAction().execute(new DeleteInput(Permission.TABLE_NAME).withPrimaryKeys(permissionIds));
return TableCustomizerInterface.super.postDelete(deleteInput, records);
}
}
@@ -1,89 +0,0 @@
/*
* Copyright © 2022-2025. ColdTrack <contact@coldtrack.com>. All Rights Reserved.
*/
package com.kingsrook.qbits.customapps.customizers;
import java.util.List;
import com.kingsrook.qbits.customapps.metadata.CustomAppsGenericCompositeWidgetMetaDataProducer;
import com.kingsrook.qbits.customapps.model.CustomAppIcon;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.CompositeWidgetData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.icon.IconBlockData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.icon.IconStyles;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.icon.IconValues;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text.TextBlockData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text.TextStyles;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text.TextValues;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
**
*******************************************************************************/
public class CustomAppIconTableCustomizer implements TableCustomizerInterface
{
public static final String LABEL_COLOR = "rgb(52, 71, 103)";
public static final String VALUE_COLOR = "rgb(123, 128, 154)";
/***************************************************************************
*
***************************************************************************/
@Override
public List<QRecord> postQuery(QueryOrGetInputInterface queryInput, List<QRecord> records) throws QException
{
for(QRecord record : records)
{
setIconPreview(record);
}
return records;
}
/***************************************************************************
*
***************************************************************************/
private static IconBlockData iconBlock(String color, String iconName)
{
return new IconBlockData().withValues(new IconValues().withName(iconName))
.withStyles(new IconStyles().withColor(color).withFontSize("125px"));
}
/***************************************************************************
*
***************************************************************************/
public static TextBlockData valueTextBlock(String color, String text)
{
return new TextBlockData().withValues(new TextValues(text))
.withStyles(new TextStyles().withColor(color).withSize("14").withWeight("bold"));
}
/***************************************************************************
*
***************************************************************************/
private static void setIconPreview(QRecord record)
{
CustomAppIcon customAppIcon = new CustomAppIcon(record);
if(customAppIcon.getIconId() != null && customAppIcon.getName() != null)
{
CustomAppsGenericCompositeWidgetMetaDataProducer.setDividerValue(record, "divider");
CompositeWidgetData previewComposite = new CompositeWidgetData().withLayout(CompositeWidgetData.Layout.FLEX_ROW)
.withBlock(valueTextBlock(LABEL_COLOR, "Icon Preview: "))
.withBlock(iconBlock(VALUE_COLOR, customAppIcon.getIconId()));
record.setValue("iconPreview", previewComposite);
}
}
}
@@ -1,117 +0,0 @@
/*
* Copyright © 2022-2024. ColdTrack <contact@coldtrack.com>. All Rights Reserved.
*/
package com.kingsrook.qbits.customapps.customizers;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qbits.customapps.model.CustomApp;
import com.kingsrook.qbits.customapps.utils.CustomAppPermissionUtils;
import com.kingsrook.qbits.customapps.utils.CustomAppsUtils;
import com.kingsrook.qbits.userrolepermissions.model.Permission;
import com.kingsrook.qqq.backend.core.actions.customizers.RecordCustomizerUtilityInterface;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
**
*******************************************************************************/
public class CustomAppTableCustomizer implements TableCustomizerInterface
{
/*******************************************************************************
** custom actions to run after table meta data was created by the meta data action
**
*******************************************************************************/
@Override
public void postMetaDataAction(TableMetaDataInput tableMetaDataInput, TableMetaDataOutput tableMetaDataOutput) throws QException
{
CustomAppsUtils.addDynamicIconChipAdornments(tableMetaDataOutput.getTable(), "customAppIconId");
}
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
if(!isPreview)
{
preInsertOrUpdate(records, Optional.empty());
}
return (records);
}
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{
if(!isPreview)
{
preInsertOrUpdate(records, oldRecordList);
}
return (records);
}
/*******************************************************************************
**
*******************************************************************************/
private void preInsertOrUpdate(List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
Optional<Map<Serializable, QRecord>> oldRecordMap = oldRecordListToMap("id", oldRecordList);
for(QRecord record : records)
{
///////////////////////////////////////////////////////////////////////
// create a permission for this app (or insert if none exists) //
///////////////////////////////////////////////////////////////////////
String appName = ValueUtils.getValueAsString(RecordCustomizerUtilityInterface.getValueFromRecordElseFromOldRecord("name", record, record.getValue("id"), oldRecordMap));
CustomApp app = new CustomApp(record).withName(appName);
Permission permission = CustomAppPermissionUtils.buildOrGetPermission(app.getName(), CustomApp.TABLE_NAME);
record.setValue("permissionId", permission.getId());
}
}
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
{
for(QRecord record : records)
{
//////////////////////////////////////
// delete a permission for this app //
//////////////////////////////////////
CustomApp app = new CustomApp(record);
Integer permissionId = app.getPermissionId();
new DeleteAction().execute(new DeleteInput(Permission.TABLE_NAME).withPrimaryKey(permissionId));
}
return TableCustomizerInterface.super.postDelete(deleteInput, records);
}
}
@@ -124,7 +124,7 @@ public class CustomAppsRegistry implements QSupplementalInstanceMetaData, QHelpC
@Override
public void acceptHelpContent(QInstance qInstance, QHelpContent helpContent, Map<String, String> nameValuePairs)
{
// not yet needed
// todo?
}
}
@@ -1,36 +0,0 @@
/*
* Copyright © 2022-2025. ColdTrack <contact@coldtrack.com>. All Rights Reserved.
*/
package com.kingsrook.qbits.customapps.metadata;
import com.kingsrook.qbits.customapps.customizers.CustomAppContainerPlaceBeforeContainerPossibleValueSource;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
/*******************************************************************************
** Meta Data Producer for CustomAppContainerPlaceBeforeContainerPVS
**
**
*******************************************************************************/
public class CustomAppContainerPlaceBeforeContainerPVSMetaDataProducer extends MetaDataProducer<QPossibleValueSource>
{
public static final String NAME = "CustomAppContainerPlaceBeforeContainerPVS";
/*******************************************************************************
**
*******************************************************************************/
@Override
public QPossibleValueSource produce(QInstance qInstance) throws QException
{
return (new QPossibleValueSource().withName(NAME).withType(QPossibleValueSourceType.CUSTOM).withCustomCodeReference(new QCodeReference(CustomAppContainerPlaceBeforeContainerPossibleValueSource.class)));
}
}
@@ -1,308 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qbits.customapps.metadata;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.kingsrook.qbits.customapps.model.CustomApp;
import com.kingsrook.qbits.customapps.model.CustomAppBackendConfig;
import com.kingsrook.qbits.customapps.model.CustomAppBackendEnum;
import com.kingsrook.qbits.customapps.model.CustomAppContainer;
import com.kingsrook.qbits.customapps.model.CustomAppIcon;
import com.kingsrook.qbits.customapps.model.CustomAppSection;
import com.kingsrook.qbits.customapps.utils.CustomAppsUtils;
import com.kingsrook.qbits.customapps.widgets.LookerWidgetRenderer;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.DenyBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
* Meta Data Producer for ViewScreenGenericCompositeWidget
*
* builds up a record containing data used to build the custom apps layout
*******************************************************************************/
public class CustomAppsDynamicMetaDataProducer
{
private static final QLogger LOG = QLogger.getLogger(CustomAppsDynamicMetaDataProducer.class);
public static final String NAME = "CustomAppsDynamicMetaDataProducer";
/***************************************************************************
*
***************************************************************************/
public CustomAppData getCustomAppList(QInstance qInstance) throws QException
{
///////////////////////////////////////////////////
// look up data needed to set up the custom apps //
///////////////////////////////////////////////////
List<CustomAppContainer> customAppContainerList = QueryAction.execute(CustomAppContainer.TABLE_NAME, CustomAppContainer.class, new QQueryFilter().withOrderBy(new QFilterOrderBy("sequenceNo")).withOrderBy(new QFilterOrderBy("name")));
List<CustomAppSection> customAppSectionList = QueryAction.execute(CustomAppSection.TABLE_NAME, CustomAppSection.class, new QQueryFilter().withOrderBy(new QFilterOrderBy("sequenceNo")));
List<CustomApp> customAppList = QueryAction.execute(CustomApp.TABLE_NAME, CustomApp.class, new QQueryFilter().withOrderBy(new QFilterOrderBy("sequenceNo")));
List<CustomAppIcon> customAppIcons = QueryAction.execute(CustomAppIcon.TABLE_NAME, CustomAppIcon.class, new QQueryFilter());
List<CustomAppBackendConfig> customAppBackendConfigList = QueryAction.execute(CustomAppBackendConfig.TABLE_NAME, CustomAppBackendConfig.class, new QQueryFilter());
Map<Integer, CustomAppBackendConfig> customAppBackendConfigMap = customAppBackendConfigList.stream().collect(Collectors.toMap(CustomAppBackendConfig::getId, Function.identity()));
/////////////////////
// icon id to icon //
/////////////////////
Map<Integer, CustomAppIcon> customAppIconMap = customAppIcons.stream().collect(Collectors.toMap(CustomAppIcon::getId, Function.identity()));
//////////////////////////////
// container id to sections //
//////////////////////////////
ListingHash<Integer, CustomAppSection> sectionsMap = new ListingHash<>();
customAppSectionList.forEach(section -> sectionsMap.add(section.getCustomAppContainerId(), section));
////////////////////////
// section id to apps //
////////////////////////
ListingHash<Integer, CustomApp> appsMap = new ListingHash<>();
customAppList.forEach(app -> appsMap.add(app.getCustomAppSectionId(), app));
///////////////////////////////////
// iterate over containers first //
///////////////////////////////////
List<QAppMetaData> containers = new ArrayList<>();
Map<String, QAppMetaData> appMap = new HashMap<>();
Map<String, QWidgetMetaDataInterface> widgetMap = new HashMap<>();
Map<String, String> positionMap = new HashMap<>();
for(CustomAppContainer container : customAppContainerList)
{
//////////////////////////
// if no sections, skip //
//////////////////////////
if(sectionsMap.get(container.getId()) == null)
{
continue;
}
////////////////////////////////////////
// iterate over sections for this app //
////////////////////////////////////////
List<QAppSection> sections = new ArrayList<>();
for(CustomAppSection section : sectionsMap.get(container.getId()))
{
//////////////////////
// if no apps, skip //
//////////////////////
if(appsMap.get(section.getId()) == null)
{
continue;
}
///////////////////////
// iterate over apps //
///////////////////////
int appCount = 0;
List<String> appNameList = new ArrayList<>();
if(appsMap.containsKey(section.getId()))
{
for(CustomApp app : appsMap.get(section.getId()))
{
//////////////////////////////////////////////////
// turn the label into a valid name for the app //
//////////////////////////////////////////////////
String appName = CustomAppsUtils.getCamelCaseName(app.getName(), CustomApp.TABLE_NAME);
/////////////////////////////////////////////////////
// make sure user has permission to this app //
////////////////////////////////////////////////////
if(!QContext.getQSession().hasPermission(appName + ".hasAccess"))
{
continue;
}
/////////////////////////////////////////
// create a custom widget for this app //
/////////////////////////////////////////
String widgetName = appName + "Widget";
QWidgetMetaDataInterface widget = buildWidget(qInstance, app, customAppBackendConfigMap, widgetName);
CustomAppIcon appIcon = customAppIconMap.get(app.getCustomAppIconId());
QAppMetaData appMetaData = new QAppMetaData()
.withPermissionRules(qInstance.getDefaultPermissionRules().withLevel(PermissionLevel.HAS_ACCESS_PERMISSION).withPermissionBaseName(appName).withDenyBehavior(DenyBehavior.HIDDEN))
.withIcon(new QIcon(appIcon.getIconId()))
.withName(appName)
.withLabel(app.getName())
.withWidgets(List.of(widget.getName()));
appNameList.add(appMetaData.getName());
appCount++;
////////////////////////
// add to output data //
////////////////////////
widgetMap.put(widgetName, widget);
appMap.put(appMetaData.getName(), appMetaData);
}
}
///////////////////////////////////////////////////////
// if no apps for this section, continue to next one //
///////////////////////////////////////////////////////
if(appCount == 0)
{
continue;
}
/////////////////////////////////////////////////
// build section with its apps and add to list //
/////////////////////////////////////////////////
String sectionName = CustomAppsUtils.getCamelCaseName(section.getName(), CustomAppSection.TABLE_NAME);
QAppSection appSection = new QAppSection()
.withApps(appNameList)
.withName(sectionName)
.withLabel(section.getName());
sections.add(appSection);
}
/////////////////////////
// build the container //
/////////////////////////
String containerName = CustomAppsUtils.getCamelCaseName(container.getName(), CustomAppContainer.TABLE_NAME);
//////////////////////////
// if no sections, skip //
//////////////////////////
if(sections.isEmpty())
{
continue;
}
/////////////////////////////////////////////////////
// make sure user has permission to this container //
/////////////////////////////////////////////////////
if(!QContext.getQSession().hasPermission(containerName + ".hasAccess"))
{
continue;
}
CustomAppIcon containerIcon = customAppIconMap.get(container.getCustomAppIconId());
QAppMetaData containerApp = new QAppMetaData()
.withPermissionRules(qInstance.getDefaultPermissionRules().withLevel(PermissionLevel.HAS_ACCESS_PERMISSION).withPermissionBaseName(containerName).withDenyBehavior(DenyBehavior.HIDDEN))
.withName(containerName)
.withLabel(container.getName())
.withIcon(new QIcon().withName(containerIcon.getIconId()))
.withSections(sections);
containers.add(containerApp);
positionMap.put(containerName, container.getPlaceBeforeContainer());
}
return (new CustomAppData(containers, positionMap, appMap, widgetMap));
}
/*******************************************************************************
** builds a widget to be used for a customapps app
*******************************************************************************/
private QWidgetMetaDataInterface buildWidget(QInstance qInstance, CustomApp customApp, Map<Integer, CustomAppBackendConfig> customAppBackendConfigMap, String widgetName)
{
CustomAppBackendConfig customAppBackendConfig = customAppBackendConfigMap.get(customApp.getCustomAppBackendConfigId());
switch(CustomAppBackendEnum.getById(customAppBackendConfig.getCustomAppBackendId()))
{
case LOOKER:
return (buildLookerWidget(qInstance, customApp, widgetName));
default:
throw new IllegalArgumentException("Unknown custom app backend: " + customAppBackendConfig.getCustomAppBackendId());
}
}
/*******************************************************************************
** builds a widget to be used for a customapps app
*******************************************************************************/
private QWidgetMetaDataInterface buildLookerWidget(QInstance qInstance, CustomApp customApp, String widgetName)
{
String componentSourceUrl = new QMetaDataVariableInterpreter().interpret("${env.Q_CUSTOM_APPS_SOURCE_URL}");
if(StringUtils.hasContent(componentSourceUrl))
{
LOG.debug("Using component source url from environment", logPair("url", componentSourceUrl));
}
else
{
componentSourceUrl = "/dynamic-qfmd-components/q-embed-looker-dashboards.bundle.js";
}
//////////////////////////////
// add the dashboard to url //
/////////////////////////
Integer dashboardId = customApp.getLookerDashboardId();
componentSourceUrl += "?dashboardId=" + dashboardId;
Map<String, Serializable> defaultValues = MapBuilder.of(
"componentName", "EmbedLookerDashboards",
"componentSourceUrl", componentSourceUrl,
"dashboardId", dashboardId,
// "authenticationUrl", this.customAppsQBitConfig.getAuthenticationUrl(),
"sx", new HashMap<>(Map.of("height", "calc(90vh)", "minHeight", "600px"))
);
QWidgetMetaData lookerWidget = new QWidgetMetaData()
.withName(widgetName)
.withPermissionRules(qInstance.getDefaultPermissionRules().withLevel(PermissionLevel.HAS_ACCESS_PERMISSION).withPermissionBaseName(widgetName + ".hasAccess").withDenyBehavior(DenyBehavior.HIDDEN))
.withType(WidgetType.CUSTOM_COMPONENT.getType())
.withCodeReference(new QCodeReference(LookerWidgetRenderer.class))
.withGridColumns(12)
.withIsCard(false)
.withLabel(null)
.withDefaultValues(defaultValues);
if(qInstance.getWidget(widgetName) == null)
{
qInstance.addWidget(lookerWidget);
}
return (lookerWidget);
}
public record CustomAppData(List<QAppMetaData> containerList, Map<String, String> positionMap, Map<String, QAppMetaData> appMap, Map<String, QWidgetMetaDataInterface> widgetMap) {}
}
@@ -1,112 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qbits.customapps.metadata;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.CompositeWidgetData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.divider.DividerBlockData;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
/*******************************************************************************
* Meta Data Producer for ViewScreenGenericCompositeWidget
*
* e.g., generic widget to be used on table virtual fields, to display a composite
* block widget.
*******************************************************************************/
public class CustomAppsGenericCompositeWidgetMetaDataProducer extends MetaDataProducer<QWidgetMetaData>
{
public static final String NAME = "CustomAppsGenericCompositeWidget";
/*******************************************************************************
**
*******************************************************************************/
@Override
public QWidgetMetaData produce(QInstance qInstance) throws QException
{
QWidgetMetaData genericCompositeBlockWidget = new QWidgetMetaData()
.withName(NAME)
.withType(WidgetType.COMPOSITE.getType())
.withShowReloadButton(false)
.withShowExportButton(false)
.withIsCard(false)
.withCodeReference(new QCodeReference(NoopWidgetRenderer.class));
return genericCompositeBlockWidget;
}
/***************************************************************************
*
***************************************************************************/
public static <T extends QFieldMetaData> T applyToField(T field)
{
field.withFieldAdornment(new FieldAdornment(AdornmentType.WIDGET)
.withValue(AdornmentType.WidgetValues.WIDGET_NAME, CustomAppsGenericCompositeWidgetMetaDataProducer.NAME));
return (field);
}
/***************************************************************************
* for a virtual field that is being used to draw a horizontal divider line,
* set the appropriate widget-data value for the field in a record.
***************************************************************************/
public static void setDividerValue(QRecord record, String fieldName)
{
CompositeWidgetData compositeWidgetData = new CompositeWidgetData()
.withBlock(new DividerBlockData());
record.setValue(fieldName, compositeWidgetData);
}
/***************************************************************************
** this widget - is not rendered in the traditional way, but rather,
** its data comes from the record's field, so noop to render here.
***************************************************************************/
public static class NoopWidgetRenderer extends AbstractWidgetRenderer
{
/***************************************************************************
**
***************************************************************************/
@Override
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
{
return null;
}
}
}
@@ -24,29 +24,22 @@ package com.kingsrook.qbits.customapps.model;
import java.io.Serializable;
import java.time.Instant;
import com.kingsrook.qbits.customapps.customizers.CustomAppTableCustomizer;
import com.kingsrook.qbits.userrolepermissions.model.Permission;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
/*******************************************************************************
** QRecord Entity for CustomApp table
** QRecord Entity for CustomAppSection table
*******************************************************************************/
@QMetaDataProducingEntity(
producePossibleValueSource = true,
@@ -64,6 +57,7 @@ public class CustomApp extends QRecordEntity implements Serializable
***************************************************************************/
public static class TableMetaDataCustomizer implements MetaDataCustomizerInterface<QTableMetaData>
{
/***************************************************************************
**
***************************************************************************/
@@ -77,16 +71,8 @@ public class CustomApp extends QRecordEntity implements Serializable
.withRecordLabelFormat("%s")
.withRecordLabelFields("name")
.withSection(SectionFactory.defaultT1("id"))
.withSection(SectionFactory.defaultT2("name", "customAppIconId", "customAppBackendConfigId", "customAppSectionId", "sequenceNo", "lookerDashboardId", "permissionId"))
.withSection(new QFieldSection("users", new QIcon().withName("person"), Tier.T2).withWidgetName("customAppPermissions"))
.withSection(SectionFactory.defaultT3("createDate", "modifyDate"))
.withCustomizer(TableCustomizers.POST_META_DATA_ACTION, new QCodeReference(CustomAppTableCustomizer.class))
.withCustomizer(TableCustomizers.POST_DELETE_RECORD, new QCodeReference(CustomAppTableCustomizer.class))
.withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(CustomAppTableCustomizer.class))
.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(CustomAppTableCustomizer.class));
table.getField("customAppIconId").withFieldAdornment(AdornmentType.CHIP);
.withSection(SectionFactory.defaultT2("name", "customAppIconId", "customAppBackendConfigId", "customAppSectionId", "sequenceNo", "lookerDashboardId"))
.withSection(SectionFactory.defaultT3("createDate", "modifyDate"));
return (table);
}
@@ -100,21 +86,18 @@ public class CustomApp extends QRecordEntity implements Serializable
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR, isRequired = true)
private String name;
@QField(label = "Backend Config", isRequired = true, possibleValueSourceName = CustomAppBackendConfig.TABLE_NAME)
@QField(isRequired = true, possibleValueSourceName = CustomAppBackendConfig.TABLE_NAME)
private Integer customAppBackendConfigId;
@QField(label = "Section", isRequired = true, possibleValueSourceName = CustomAppSection.TABLE_NAME)
@QField(isRequired = true, possibleValueSourceName = CustomAppSection.TABLE_NAME)
private Integer customAppSectionId;
@QField(label = "Icon", isRequired = true, possibleValueSourceName = CustomAppIcon.TABLE_NAME)
@QField(isRequired = true, possibleValueSourceName = CustomAppIcon.TABLE_NAME)
private Integer customAppIconId;
@QField(isRequired = true)
private Integer sequenceNo;
@QField(isEditable = false, possibleValueSourceName = Permission.TABLE_NAME)
private Integer permissionId;
@QField(isRequired = true)
private Integer lookerDashboardId;
@@ -422,35 +405,4 @@ public class CustomApp extends QRecordEntity implements Serializable
return (this);
}
/*******************************************************************************
** Getter for permissionId
*******************************************************************************/
public Integer getPermissionId()
{
return (this.permissionId);
}
/*******************************************************************************
** Setter for permissionId
*******************************************************************************/
public void setPermissionId(Integer permissionId)
{
this.permissionId = permissionId;
}
/*******************************************************************************
** Fluent setter for permissionId
*******************************************************************************/
public CustomApp withPermissionId(Integer permissionId)
{
this.permissionId = permissionId;
return (this);
}
}
@@ -97,7 +97,7 @@ public class CustomAppBackendConfig extends QRecordEntity implements Serializabl
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR, isRequired = true)
private String name;
@QField(label = "Backend", isRequired = true, possibleValueSourceName = CustomAppBackendPossibleValueMetaDataProducer.NAME)
@QField(isRequired = true, possibleValueSourceName = CustomAppBackendPossibleValueMetaDataProducer.NAME)
private Integer customAppBackendId;
@QField(isEditable = false)
@@ -24,19 +24,11 @@ package com.kingsrook.qbits.customapps.model;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import com.kingsrook.qbits.customapps.customizers.CustomAppContainerTableCustomizer;
import com.kingsrook.qbits.customapps.metadata.CustomAppContainerPlaceBeforeContainerPVSMetaDataProducer;
import com.kingsrook.qbits.userrolepermissions.model.Permission;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QAssociation;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
@@ -45,11 +37,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.Child
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildRecordListWidget;
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildTable;
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
@@ -70,8 +59,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
)
public class CustomAppContainer extends QRecordEntity implements Serializable
{
public static final String TABLE_NAME = "customAppContainer";
public static final String ASSOCIATION_NAME_CUSTOM_APP_SECTIONS = "customAppSections";
public static final String TABLE_NAME = "customAppContainer";
@@ -96,20 +84,9 @@ public class CustomAppContainer extends QRecordEntity implements Serializable
.withRecordLabelFormat("%s")
.withRecordLabelFields("name")
.withSection(SectionFactory.defaultT1("id"))
.withSection(SectionFactory.defaultT2("name", "customAppIconId", "placeBeforeContainer", "sequenceNo", "permissionId"))
.withSection(SectionFactory.defaultT2("name", "customAppIconId", "sequenceNo"))
.withSection(SectionFactory.customT2("apps", new QIcon("polyline")).withWidgetName(sectionsChildJoinName))
.withSection(new QFieldSection("users", new QIcon().withName("person"), Tier.T2).withWidgetName("customAppContainerPermissions"))
.withSection(SectionFactory.defaultT3("createDate", "modifyDate"))
.withCustomizer(TableCustomizers.POST_META_DATA_ACTION, new QCodeReference(CustomAppContainerTableCustomizer.class))
.withCustomizer(TableCustomizers.POST_DELETE_RECORD, new QCodeReference(CustomAppContainerTableCustomizer.class))
.withCustomizer(TableCustomizers.POST_QUERY_RECORD, new QCodeReference(CustomAppContainerTableCustomizer.class))
.withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(CustomAppContainerTableCustomizer.class))
.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(CustomAppContainerTableCustomizer.class))
.withAssociation(new Association().withName(ASSOCIATION_NAME_CUSTOM_APP_SECTIONS).withJoinName(sectionsChildJoinName).withAssociatedTableName(CustomAppSection.TABLE_NAME));
table.getField("customAppIconId").withFieldAdornment(AdornmentType.CHIP);
.withSection(SectionFactory.defaultT3("createDate", "modifyDate"));
return (table);
}
@@ -126,24 +103,15 @@ public class CustomAppContainer extends QRecordEntity implements Serializable
@QField(label = "Icon", isRequired = true, possibleValueSourceName = CustomAppIcon.TABLE_NAME)
private Integer customAppIconId;
@QField(possibleValueSourceName = CustomAppContainerPlaceBeforeContainerPVSMetaDataProducer.NAME)
private String placeBeforeContainer;
@QField()
@QField(isRequired = true)
private Integer sequenceNo;
@QField(isEditable = false, possibleValueSourceName = Permission.TABLE_NAME)
private Integer permissionId;
@QField(isEditable = false)
private Instant createDate;
@QField(isEditable = false)
private Instant modifyDate;
@QAssociation(name = ASSOCIATION_NAME_CUSTOM_APP_SECTIONS)
private List<CustomAppSection> sections;
/*******************************************************************************
@@ -290,31 +258,31 @@ public class CustomAppContainer extends QRecordEntity implements Serializable
/*******************************************************************************
** Getter for placeBeforeContainer
** Getter for sequenceNo
*******************************************************************************/
public String getPlaceBeforeContainer()
public Integer getSequenceNo()
{
return (this.placeBeforeContainer);
return (this.sequenceNo);
}
/*******************************************************************************
** Setter for placeBeforeContainer
** Setter for sequenceNo
*******************************************************************************/
public void setPlaceBeforeContainer(String placeBeforeContainer)
public void setSequenceNo(Integer sequenceNo)
{
this.placeBeforeContainer = placeBeforeContainer;
this.sequenceNo = sequenceNo;
}
/*******************************************************************************
** Fluent setter for placeBeforeContainer
** Fluent setter for sequenceNo
*******************************************************************************/
public CustomAppContainer withPlaceBeforeContainer(String placeBeforeContainer)
public CustomAppContainer withSequenceNo(Integer sequenceNo)
{
this.placeBeforeContainer = placeBeforeContainer;
this.sequenceNo = sequenceNo;
return (this);
}
@@ -349,97 +317,4 @@ public class CustomAppContainer extends QRecordEntity implements Serializable
return (this);
}
/*******************************************************************************
** Getter for permissionId
*******************************************************************************/
public Integer getPermissionId()
{
return (this.permissionId);
}
/*******************************************************************************
** Setter for permissionId
*******************************************************************************/
public void setPermissionId(Integer permissionId)
{
this.permissionId = permissionId;
}
/*******************************************************************************
** Fluent setter for permissionId
*******************************************************************************/
public CustomAppContainer withPermissionId(Integer permissionId)
{
this.permissionId = permissionId;
return (this);
}
/*******************************************************************************
** Getter for sequenceNo
*******************************************************************************/
public Integer getSequenceNo()
{
return (this.sequenceNo);
}
/*******************************************************************************
** Setter for sequenceNo
*******************************************************************************/
public void setSequenceNo(Integer sequenceNo)
{
this.sequenceNo = sequenceNo;
}
/*******************************************************************************
** Fluent setter for sequenceNo
*******************************************************************************/
public CustomAppContainer withSequenceNo(Integer sequenceNo)
{
this.sequenceNo = sequenceNo;
return (this);
}
/*******************************************************************************
** Getter for sections
*******************************************************************************/
public List<CustomAppSection> getSections()
{
return (this.sections);
}
/*******************************************************************************
** Setter for sections
*******************************************************************************/
public void setSections(List<CustomAppSection> sections)
{
this.sections = sections;
}
/*******************************************************************************
** Fluent setter for sections
*******************************************************************************/
public CustomAppContainer withSections(List<CustomAppSection> sections)
{
this.sections = sections;
return (this);
}
}
@@ -24,22 +24,15 @@ package com.kingsrook.qbits.customapps.model;
import java.io.Serializable;
import java.time.Instant;
import com.kingsrook.qbits.customapps.customizers.CustomAppIconTableCustomizer;
import com.kingsrook.qbits.customapps.metadata.CustomAppsGenericCompositeWidgetMetaDataProducer;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QVirtualFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSectionAlternativeType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
@@ -78,25 +71,9 @@ public class CustomAppIcon extends QRecordEntity implements Serializable
.withRecordLabelFormat("%s")
.withRecordLabelFields("name")
.withSection(SectionFactory.defaultT1("id"))
.withSection(SectionFactory.defaultT2("name", "iconId").withGridColumns(12)
.withAlternative(QFieldSectionAlternativeType.RECORD_VIEW, (s) ->
{
s.getFieldNames().add("divider");
s.getFieldNames().add("iconPreview");
}
))
.withSection(SectionFactory.defaultT2("name", "iconId"))
.withSection(SectionFactory.defaultT3("createDate", "modifyDate"));
table.withCustomizer(TableCustomizers.POST_QUERY_RECORD, new QCodeReference(CustomAppIconTableCustomizer.class));
////////////////////
// virtual fields //
////////////////////
table.withVirtualField(CustomAppsGenericCompositeWidgetMetaDataProducer.applyToField(
new QVirtualFieldMetaData("iconPreview", QFieldType.STRING)));
table.withVirtualField(CustomAppsGenericCompositeWidgetMetaDataProducer.applyToField(
new QVirtualFieldMetaData("divider", QFieldType.STRING)));
return (table);
}
}
@@ -24,9 +24,7 @@ package com.kingsrook.qbits.customapps.model;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QAssociation;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
@@ -39,7 +37,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.Child
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildRecordListWidget;
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildTable;
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
@@ -62,8 +59,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
)
public class CustomAppSection extends QRecordEntity implements Serializable
{
public static final String TABLE_NAME = "customAppSection";
public static final String ASSOCIATION_NAME_CUSTOM_APPS = "customApps";
public static final String TABLE_NAME = "customAppSection";
@@ -89,9 +85,7 @@ public class CustomAppSection extends QRecordEntity implements Serializable
.withSection(SectionFactory.defaultT1("id"))
.withSection(SectionFactory.defaultT2("customAppContainerId", "name", "sequenceNo"))
.withSection(SectionFactory.customT2("apps", new QIcon("polyline")).withWidgetName(appsChildJoinName))
.withSection(SectionFactory.defaultT3("createDate", "modifyDate"))
.withAssociation(new Association().withName(ASSOCIATION_NAME_CUSTOM_APPS).withJoinName(appsChildJoinName).withAssociatedTableName(CustomApp.TABLE_NAME));
.withSection(SectionFactory.defaultT3("createDate", "modifyDate"));
return (table);
}
@@ -105,7 +99,7 @@ public class CustomAppSection extends QRecordEntity implements Serializable
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR, isRequired = true)
private String name;
@QField(label = "Container", isRequired = true, possibleValueSourceName = CustomAppContainer.TABLE_NAME)
@QField(isRequired = true, possibleValueSourceName = CustomAppContainer.TABLE_NAME)
private Integer customAppContainerId;
@QField(isRequired = true)
@@ -117,9 +111,6 @@ public class CustomAppSection extends QRecordEntity implements Serializable
@QField(isEditable = false)
private Instant modifyDate;
@QAssociation(name = ASSOCIATION_NAME_CUSTOM_APPS)
private List<CustomApp> apps;
/*******************************************************************************
@@ -325,35 +316,4 @@ public class CustomAppSection extends QRecordEntity implements Serializable
return (this);
}
/*******************************************************************************
** Getter for apps
*******************************************************************************/
public List<CustomApp> getApps()
{
return (this.apps);
}
/*******************************************************************************
** Setter for apps
*******************************************************************************/
public void setApps(List<CustomApp> apps)
{
this.apps = apps;
}
/*******************************************************************************
** Fluent setter for apps
*******************************************************************************/
public CustomAppSection withApps(List<CustomApp> apps)
{
this.apps = apps;
return (this);
}
}
@@ -1,74 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qbits.customapps.utils;
import java.util.Map;
import com.kingsrook.qbits.userrolepermissions.model.Permission;
import com.kingsrook.qbits.userrolepermissions.model.PermissionObjectType;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
/*******************************************************************************
**
*******************************************************************************/
public class CustomAppPermissionUtils
{
/***************************************************************************
**
***************************************************************************/
public static Permission buildOrGetPermission(String name, String tableName) throws QException
{
String objectName = CustomAppsUtils.getCamelCaseName(name, tableName);
String permissionName = objectName + ".hasAccess";
/////////////////////////////////////
// if permission exists, return it //
/////////////////////////////////////
GetOutput output = new GetAction().execute(new GetInput(Permission.TABLE_NAME).withUniqueKey(Map.of("name", permissionName)));
if(output.getRecord() != null)
{
return (new Permission(output.getRecord()));
}
//////////////////////////////////////////////////////
// otherwise create a new permission to be inserted //
//////////////////////////////////////////////////////
Permission permission = new Permission()
.withName(permissionName)
.withDescription("Permission to access " + name)
.withObjectType(PermissionObjectType.APP.getId())
.withObjectLabel(name);
InsertInput permissionInput = new InsertInput(Permission.TABLE_NAME).withRecordEntity(permission);
InsertOutput permissionOutput = new InsertAction().execute(permissionInput);
return (new Permission(permissionOutput.getRecords().get(0)));
}
}
@@ -1,114 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qbits.customapps.utils;
import java.util.List;
import java.util.Optional;
import com.kingsrook.qbits.customapps.model.CustomApp;
import com.kingsrook.qbits.customapps.model.CustomAppContainer;
import com.kingsrook.qbits.customapps.model.CustomAppIcon;
import com.kingsrook.qbits.customapps.model.CustomAppSection;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType.ChipValues.iconAndColorValues;
/*******************************************************************************
**
*******************************************************************************/
public class CustomAppsUtils
{
/***************************************************************************
**
***************************************************************************/
public static void addDynamicIconChipAdornments(QFrontendTableMetaData table, String fieldName) throws QException
{
QFrontendFieldMetaData field = table.getFields().get(fieldName);
Optional<FieldAdornment> chipAdornmentOpt = field.getAdornments().stream().filter(a -> a.getType().equals(AdornmentType.CHIP)).findFirst();
if(chipAdornmentOpt.isPresent())
{
////////////////////////////////////////////////////////////
// look up all icons in the system and add chips for them //
////////////////////////////////////////////////////////////
List<CustomAppIcon> customAppIcons = QueryAction.execute(CustomAppIcon.TABLE_NAME, CustomAppIcon.class, new QQueryFilter().withOrderBy(new QFilterOrderBy("name")));
for(CustomAppIcon icon : customAppIcons)
{
chipAdornmentOpt.get().withValues(iconAndColorValues(icon.getId(), icon.getIconId(), AdornmentType.ChipValues.COLOR_SECONDARY));
}
}
}
/***************************************************************************
**
***************************************************************************/
public static String getCamelCaseName(String name, String tableName)
{
name = nameToCamelCase(name);
switch(tableName)
{
case CustomApp.TABLE_NAME -> name = "qca" + StringUtils.ucFirst(name);
case CustomAppSection.TABLE_NAME -> name = "qcas" + StringUtils.ucFirst(name);
case CustomAppContainer.TABLE_NAME -> name = "qcac" + StringUtils.ucFirst(name);
default ->
{
////////////////////////////////////////////////////////////
// dont add a prefix in this case and lowercase the start //
////////////////////////////////////////////////////////////
name = StringUtils.lcFirst(name);
}
}
return (name);
}
/*******************************************************************************
** transform an app name into a camelCase name
*******************************************************************************/
private static String nameToCamelCase(String label)
{
String[] parts = label
.replaceAll("[^a-zA-Z0-9]+", " ")
.trim()
.split("\\s+");
StringBuilder result = new StringBuilder(parts[0].toLowerCase());
for(int i = 1; i < parts.length; i++)
{
result.append(parts[i].substring(0, 1).toUpperCase()).append(parts[i].substring(1).toLowerCase());
}
return result.toString();
}
}
@@ -8,7 +8,6 @@ package com.kingsrook.qbits.customapps.widgets;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.QWidgetData;
@@ -29,18 +28,9 @@ public class LookerWidgetRenderer extends AbstractWidgetRenderer
try
{
Map<String, String> queryParams = input.getQueryParams();
if(!queryParams.containsKey("dashboardId") || queryParams.get("dashboardId") == null)
{
throw (new QUserFacingException("LookerWidgetRenderer requires a dashboardId to be provided."));
}
int dashboardId = Integer.parseInt(queryParams.get("dashboardId"));
Integer dashboardId = Integer.parseInt(queryParams.get("dashboardId"));
return (new RenderWidgetOutput(new LookerWidgetData().withDashboardId(dashboardId)));
}
catch(QException qe)
{
throw (qe);
}
catch(Exception e)
{
throw (new QException("Error rendering LookerWidget", e));
-59
View File
@@ -1,59 +0,0 @@
DROP TABLE IF EXISTS custom_app_container;
CREATE TABLE custom_app_container
(
id INTEGER AUTO_INCREMENT PRIMARY KEY,
create_date TIMESTAMP DEFAULT now(),
modify_date TIMESTAMP DEFAULT now(),
custom_app_icon_id INTEGER NOT NULL,
name VARCHAR(100) NOT NULL,
sequence_no INTEGER NOT NULL,
permission_id INTEGER,
place_before_container VARCHAR(100)
);
DROP TABLE IF EXISTS custom_app_section;
CREATE TABLE custom_app_section
(
id INTEGER AUTO_INCREMENT PRIMARY KEY,
create_date TIMESTAMP DEFAULT now(),
modify_date TIMESTAMP DEFAULT now(),
custom_app_container_id INTEGER NOT NULL,
name VARCHAR(100) NOT NULL,
sequence_no INTEGER NOT NULL
);
DROP TABLE IF EXISTS custom_app;
CREATE TABLE custom_app
(
id INTEGER AUTO_INCREMENT PRIMARY KEY,
create_date TIMESTAMP DEFAULT now(),
modify_date TIMESTAMP DEFAULT now(),
custom_app_section_id INTEGER NOT NULL,
custom_app_backend_config_id INTEGER NOT NULL,
name VARCHAR(100) NOT NULL,
custom_app_icon_id VARCHAR(100) NOT NULL,
sequence_no INTEGER NOT NULL,
looker_dashboard_id INTEGER,
permission_id INTEGER
);
DROP TABLE IF EXISTS custom_app_backend_config;
CREATE TABLE custom_app_backend_config
(
id INTEGER AUTO_INCREMENT PRIMARY KEY,
create_date TIMESTAMP DEFAULT now(),
modify_date TIMESTAMP DEFAULT now(),
custom_app_backend_id INTEGER NOT NULL,
name VARCHAR(100) NOT NULL
);
DROP TABLE IF EXISTS custom_app_icon;
CREATE TABLE custom_app_icon
(
id INTEGER AUTO_INCREMENT PRIMARY KEY,
create_date TIMESTAMP DEFAULT now(),
modify_date TIMESTAMP DEFAULT now(),
icon_id VARCHAR(100) NOT NULL,
name VARCHAR(100) NOT NULL
);