From ea8be83c6f441ab9686a100c5ca6cfd118f8efc4 Mon Sep 17 00:00:00 2001
From: Brent Salisbury
Date: Sun, 3 Nov 2024 16:54:59 -0500
Subject: [PATCH 1/3] Feature: enable the UI to operate with a local git repo
only
Signed-off-by: Brent Salisbury
---
.env.example | 4 +
package-lock.json | 631 +++++-------------
package.json | 3 +
src/app/api/local/clone-repo/route.ts | 45 ++
src/app/api/local/git/branches/route.ts | 123 ++++
src/app/api/local/pr/knowledge/route.ts | 73 ++
src/app/api/local/pr/skill/route.ts | 70 ++
.../configuration-local/page.tsx | 14 +
.../contribute-local/knowledge/page.tsx | 14 +
.../contribute-local/skill/page.tsx | 14 +
src/app/experimental/dashboard-local/page.tsx | 17 +
src/components/AppLayout.tsx | 8 +-
.../CloneRepoLocal/CloneRepoLocal.module.css | 9 +
.../CloneRepoLocal/CloneRepoLocal.tsx | 115 ++++
.../Knowledge/SubmitLocal/Submit.tsx | 112 ++++
.../ContributeLocal/Knowledge/index.tsx | 611 +++++++++++++++++
.../ContributeLocal/Knowledge/knowledge.css | 31 +
.../Skill/SubmitLocal/SubmitLocal.tsx | 106 +++
.../ContributeLocal/Skill/index.tsx | 484 ++++++++++++++
.../ContributeLocal/Skill/skills.css | 18 +
.../Experimental/DashboardLocal/index.tsx | 161 +++++
21 files changed, 2213 insertions(+), 450 deletions(-)
create mode 100644 src/app/api/local/clone-repo/route.ts
create mode 100644 src/app/api/local/git/branches/route.ts
create mode 100644 src/app/api/local/pr/knowledge/route.ts
create mode 100644 src/app/api/local/pr/skill/route.ts
create mode 100644 src/app/experimental/contribute-local/configuration-local/page.tsx
create mode 100644 src/app/experimental/contribute-local/knowledge/page.tsx
create mode 100644 src/app/experimental/contribute-local/skill/page.tsx
create mode 100644 src/app/experimental/dashboard-local/page.tsx
create mode 100644 src/components/Experimental/CloneRepoLocal/CloneRepoLocal.module.css
create mode 100644 src/components/Experimental/CloneRepoLocal/CloneRepoLocal.tsx
create mode 100644 src/components/Experimental/ContributeLocal/Knowledge/SubmitLocal/Submit.tsx
create mode 100644 src/components/Experimental/ContributeLocal/Knowledge/index.tsx
create mode 100644 src/components/Experimental/ContributeLocal/Knowledge/knowledge.css
create mode 100644 src/components/Experimental/ContributeLocal/Skill/SubmitLocal/SubmitLocal.tsx
create mode 100644 src/components/Experimental/ContributeLocal/Skill/index.tsx
create mode 100644 src/components/Experimental/ContributeLocal/Skill/skills.css
create mode 100644 src/components/Experimental/DashboardLocal/index.tsx
diff --git a/.env.example b/.env.example
index 9ad509e3..b52e8d0f 100644
--- a/.env.example
+++ b/.env.example
@@ -18,4 +18,8 @@ TAXONOMY_DOCUMENTS_REPO=github.com/instructlab-public/taxonomy-knowledge-docs
NEXT_PUBLIC_AUTHENTICATION_ORG=
NEXT_PUBLIC_TAXONOMY_REPO_OWNER=
NEXT_PUBLIC_TAXONOMY_REPO=
+
NEXT_PUBLIC_EXPERIMENTAL_FEATURES=false
+# The following requires experimental and dev mode to be enabled
+# NEXT_PUBLIC_BASE_CLONE_DIRECTORY=/base/path/
+# NEXT_PUBLIC_LOCAL_REPO_PATH=/base/path/cloned_dir_name
diff --git a/package-lock.json b/package-lock.json
index 5ebd1426..3373f501 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,8 @@
"@patternfly/react-styles": "^6.0.0",
"@patternfly/react-table": "^6.0.0",
"axios": "^1.7.7",
+ "fs": "^0.0.1-security",
+ "isomorphic-git": "^1.27.1",
"js-yaml": "^4.1.0",
"next": "^15.0.2",
"next-auth": "^4.24.10",
@@ -31,6 +33,7 @@
"@next/eslint-plugin-next": "^14.2.3",
"@playwright/test": "^1.47.2",
"@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.4",
+ "@types/js-cookie": "^3.0.6",
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.5.0",
"@types/react": "18.3.1",
@@ -341,15 +344,6 @@
"kuler": "^2.0.0"
}
},
- "node_modules/@emnapi/runtime": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
- "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -590,27 +584,6 @@
"@img/sharp-libvips-darwin-arm64": "1.0.4"
}
},
- "node_modules/@img/sharp-darwin-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
- "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.0.4"
- }
- },
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
@@ -626,291 +599,6 @@
"url": "https://opencollective.com/libvips"
}
},
- "node_modules/@img/sharp-libvips-darwin-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
- "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
- "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
- "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-s390x": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
- "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
- "cpu": [
- "s390x"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
- "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
- "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
- "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-linux-arm": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
- "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.0.5"
- }
- },
- "node_modules/@img/sharp-linux-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
- "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linux-s390x": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
- "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
- "cpu": [
- "s390x"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linux-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
- "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
- "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
- "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-wasm32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
- "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
- "cpu": [
- "wasm32"
- ],
- "optional": true,
- "dependencies": {
- "@emnapi/runtime": "^1.2.0"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-ia32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
- "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
- "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -1041,111 +729,6 @@
"node": ">= 10"
}
},
- "node_modules/@next/swc-darwin-x64": {
- "version": "15.0.2",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.2.tgz",
- "integrity": "sha512-KUpBVxIbjzFiUZhiLIpJiBoelqzQtVZbdNNsehhUn36e2YzKHphnK8eTUW1s/4aPy5kH/UTid8IuVbaOpedhpw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-gnu": {
- "version": "15.0.2",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.2.tgz",
- "integrity": "sha512-9J7TPEcHNAZvwxXRzOtiUvwtTD+fmuY0l7RErf8Yyc7kMpE47MIQakl+3jecmkhOoIyi/Rp+ddq7j4wG6JDskQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-musl": {
- "version": "15.0.2",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.2.tgz",
- "integrity": "sha512-BjH4ZSzJIoTTZRh6rG+a/Ry4SW0HlizcPorqNBixBWc3wtQtj4Sn9FnRZe22QqrPnzoaW0ctvSz4FaH4eGKMww==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-gnu": {
- "version": "15.0.2",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.2.tgz",
- "integrity": "sha512-i3U2TcHgo26sIhcwX/Rshz6avM6nizrZPvrDVDY1bXcLH1ndjbO8zuC7RoHp0NSK7wjJMPYzm7NYL1ksSKFreA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-musl": {
- "version": "15.0.2",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.2.tgz",
- "integrity": "sha512-AMfZfSVOIR8fa+TXlAooByEF4OB00wqnms1sJ1v+iu8ivwvtPvnkwdzzFMpsK5jA2S9oNeeQ04egIWVb4QWmtQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-arm64-msvc": {
- "version": "15.0.2",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.2.tgz",
- "integrity": "sha512-JkXysDT0/hEY47O+Hvs8PbZAeiCQVxKfGtr4GUpNAhlG2E0Mkjibuo8ryGD29Qb5a3IOnKYNoZlh/MyKd2Nbww==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-x64-msvc": {
- "version": "15.0.2",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.2.tgz",
- "integrity": "sha512-foaUL0NqJY/dX0Pi/UcZm5zsmSk5MtP/gxx3xOPyREkMFN+CTjctPfu3QaqrQHinaKdPnMWPJDKt4VjDfTBe/Q==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@@ -1204,16 +787,16 @@
}
},
"node_modules/@patternfly/react-core": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-5.4.1.tgz",
- "integrity": "sha512-PJjwN4OCR7jTdWKi0RzuFdtlSQ8gBR+0REczuDHHPW8ky0bs1cIcqGsn5p/b6OgPlztl3UaXqRYLsroiEMasOw==",
+ "version": "5.4.8",
+ "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-5.4.8.tgz",
+ "integrity": "sha512-4KRsQsH39VmTiFPLdN34QqNZg6gKrTamJxKtWEPO1VKA0TpoRMwpFEGk9BDyxipxYST6WzXznAaLCidGkCDlWw==",
"dependencies": {
- "@patternfly/react-icons": "^5.4.0",
- "@patternfly/react-styles": "^5.4.0",
- "@patternfly/react-tokens": "^5.4.0",
- "focus-trap": "7.5.4",
+ "@patternfly/react-icons": "^5.4.2",
+ "@patternfly/react-styles": "^5.4.1",
+ "@patternfly/react-tokens": "^5.4.1",
+ "focus-trap": "7.6.0",
"react-dropzone": "^14.2.3",
- "tslib": "^2.6.3"
+ "tslib": "^2.7.0"
},
"peerDependencies": {
"react": "^17 || ^18",
@@ -1234,6 +817,11 @@
"resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.4.1.tgz",
"integrity": "sha512-XA8PXksD8uiA3RTwxdUwJXOCf+V6sVd+2HKapWAdRLvtSV+Sdk7NgCvalb4IAQncsddLopjPQD8gAHA298+N8w=="
},
+ "node_modules/@patternfly/react-core/node_modules/@patternfly/react-tokens": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.4.1.tgz",
+ "integrity": "sha512-eygdHE7Krta1mijAv/E8RHiKIgysD0eeNTo8EXUYC8/M4e5K6sqpr2p6rQBF8QiRMN8FnbXvZT3K2OQ28pYt9Q=="
+ },
"node_modules/@patternfly/react-icons": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-6.0.0.tgz",
@@ -1282,24 +870,11 @@
"react-dom": "^17 || ^18"
}
},
- "node_modules/@patternfly/react-table/node_modules/@patternfly/react-tokens": {
+ "node_modules/@patternfly/react-tokens": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.0.0.tgz",
"integrity": "sha512-xd0ynDkiIW2rp8jz4TNvR4Dyaw9kSMkZdsuYcLlFXCVmvX//Mnl4rhBnid/2j2TaqK0NbkyTTPnPY/BU7SfLVQ=="
},
- "node_modules/@patternfly/react-table/node_modules/focus-trap": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.0.tgz",
- "integrity": "sha512-1td0l3pMkWJLFipobUcGaf+5DTY4PLDDrcqoSaKP8ediO/CoWCCYk/fT/Y2A4e6TNB+Sh6clRJCjOPPnKoNHnQ==",
- "dependencies": {
- "tabbable": "^6.2.0"
- }
- },
- "node_modules/@patternfly/react-tokens": {
- "version": "5.4.0",
- "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.4.0.tgz",
- "integrity": "sha512-KONkwCVOMyklhuuaYeYgcAsGtCBQXnsBGZeolhOdSzr2Mj0RVSW0oMrQPgZuPVzhhC/kbqgClHJJl6xuG9xheA=="
- },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -1427,6 +1002,12 @@
"hoist-non-react-statics": "^3.3.0"
}
},
+ "node_modules/@types/js-cookie": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+ "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+ "dev": true
+ },
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
@@ -1985,6 +1566,11 @@
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
"license": "MIT"
},
+ "node_modules/async-lock": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz",
+ "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ=="
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -2169,6 +1755,11 @@
}
]
},
+ "node_modules/clean-git-ref": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz",
+ "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw=="
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -2253,6 +1844,17 @@
"node": ">= 0.6"
}
},
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -2363,6 +1965,20 @@
}
}
},
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/deep-equal": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
@@ -2457,6 +2073,11 @@
"node": ">=8"
}
},
+ "node_modules/diff3": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz",
+ "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g=="
+ },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -3770,9 +3391,9 @@
"license": "MIT"
},
"node_modules/focus-trap": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz",
- "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.0.tgz",
+ "integrity": "sha512-1td0l3pMkWJLFipobUcGaf+5DTY4PLDDrcqoSaKP8ediO/CoWCCYk/fT/Y2A4e6TNB+Sh6clRJCjOPPnKoNHnQ==",
"dependencies": {
"tabbable": "^6.2.0"
}
@@ -3850,6 +3471,11 @@
"node": ">=12.20.0"
}
},
+ "node_modules/fs": {
+ "version": "0.0.1-security",
+ "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
+ "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -4174,7 +3800,6 @@
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
@@ -4674,6 +4299,30 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/isomorphic-git": {
+ "version": "1.27.1",
+ "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.27.1.tgz",
+ "integrity": "sha512-X32ph5zIWfT75QAqW2l3JCIqnx9/GWd17bRRehmn3qmWc34OYbSXY6Cxv0o9bIIY+CWugoN4nQFHNA+2uYf2nA==",
+ "dependencies": {
+ "async-lock": "^1.4.1",
+ "clean-git-ref": "^2.0.1",
+ "crc-32": "^1.2.0",
+ "diff3": "0.0.3",
+ "ignore": "^5.1.4",
+ "minimisted": "^2.0.0",
+ "pako": "^1.0.10",
+ "pify": "^4.0.1",
+ "readable-stream": "^3.4.0",
+ "sha.js": "^2.4.9",
+ "simple-get": "^4.0.1"
+ },
+ "bin": {
+ "isogit": "cli.cjs"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/iterator.prototype": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz",
@@ -4960,6 +4609,17 @@
"node": ">= 0.6"
}
},
+ "node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -4980,12 +4640,19 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/minimisted": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz",
+ "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==",
+ "dependencies": {
+ "minimist": "^1.2.5"
+ }
+ },
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -5373,7 +5040,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
@@ -5471,6 +5137,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -5573,6 +5244,14 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/playwright": {
"version": "1.47.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz",
@@ -6091,6 +5770,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/sha.js": {
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+ "dependencies": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ },
+ "bin": {
+ "sha.js": "bin.js"
+ }
+ },
"node_modules/sharp": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
@@ -6228,6 +5919,49 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/simple-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/simple-get": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "decompress-response": "^6.0.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@@ -7142,7 +6876,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true,
"license": "ISC"
},
"node_modules/yallist": {
diff --git a/package.json b/package.json
index 1f60f2fd..c73857dc 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,8 @@
"@patternfly/react-styles": "^6.0.0",
"@patternfly/react-table": "^6.0.0",
"axios": "^1.7.7",
+ "fs": "^0.0.1-security",
+ "isomorphic-git": "^1.27.1",
"js-yaml": "^4.1.0",
"next": "^15.0.2",
"next-auth": "^4.24.10",
@@ -39,6 +41,7 @@
"@next/eslint-plugin-next": "^14.2.3",
"@playwright/test": "^1.47.2",
"@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.4",
+ "@types/js-cookie": "^3.0.6",
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.5.0",
"@types/react": "18.3.1",
diff --git a/src/app/api/local/clone-repo/route.ts b/src/app/api/local/clone-repo/route.ts
new file mode 100644
index 00000000..309582c5
--- /dev/null
+++ b/src/app/api/local/clone-repo/route.ts
@@ -0,0 +1,45 @@
+// src/pages/api/clone-repo.ts
+import { NextRequest, NextResponse } from 'next/server';
+import * as git from 'isomorphic-git';
+import http from 'isomorphic-git/http/node';
+import fs from 'fs';
+import path from 'path';
+
+// Retrieve the base directory from the environment variable
+const BASE_DIRECTORY = process.env.NEXT_PUBLIC_BASE_CLONE_DIRECTORY;
+
+export async function POST(req: NextRequest) {
+ const { repoUrl, directory } = await req.json();
+
+ if (!repoUrl || !directory) {
+ return NextResponse.json({ message: 'Repository URL and directory are required' }, { status: 400 });
+ }
+
+ if (!BASE_DIRECTORY) {
+ return NextResponse.json({ message: 'Base directory is not configured on the server' }, { status: 500 });
+ }
+
+ try {
+ const clonePath = path.resolve(BASE_DIRECTORY, directory);
+
+ // Ensure clonePath is within BASE_DIRECTORY
+ if (!clonePath.startsWith(BASE_DIRECTORY)) {
+ return NextResponse.json({ message: 'Invalid directory path' }, { status: 403 });
+ }
+
+ await git.clone({
+ fs,
+ http,
+ dir: clonePath,
+ url: repoUrl,
+ singleBranch: true,
+ depth: 1
+ });
+
+ // Include the full path in the response for client display
+ return NextResponse.json({ message: `Repository cloned successfully.`, fullPath: clonePath }, { status: 200 });
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
+ return NextResponse.json({ message: `Failed to clone repository: ${errorMessage}` }, { status: 500 });
+ }
+}
diff --git a/src/app/api/local/git/branches/route.ts b/src/app/api/local/git/branches/route.ts
new file mode 100644
index 00000000..9d880342
--- /dev/null
+++ b/src/app/api/local/git/branches/route.ts
@@ -0,0 +1,123 @@
+// src/app/api/local/git/branches/route.ts
+import { NextRequest, NextResponse } from 'next/server';
+import * as git from 'isomorphic-git';
+import fs from 'fs';
+import path from 'path';
+
+// Get the repository path from the environment variable
+const REPO_DIR = process.env.NEXT_PUBLIC_LOCAL_REPO_PATH || '/path/to/local/repo';
+
+export async function GET() {
+ try {
+ // Ensure the repository path exists
+ if (!fs.existsSync(REPO_DIR)) {
+ return NextResponse.json({ error: 'Repository path does not exist.' }, { status: 400 });
+ }
+
+ // List all branches in the repository
+ const branches = await git.listBranches({ fs, dir: REPO_DIR });
+
+ // Return the list of branches as JSON
+ return NextResponse.json({ branches }, { status: 200 });
+ } catch (error) {
+ console.error('Failed to list branches:', error);
+ return NextResponse.json({ error: 'Failed to list branches' }, { status: 500 });
+ }
+}
+
+// Handle POST requests for merge or branch comparison
+export async function POST(req: NextRequest) {
+ const { branchName, action } = await req.json();
+
+ try {
+ if (action === 'merge') {
+ // Ensure valid branch name
+ if (!branchName || branchName === 'main') {
+ return NextResponse.json({ error: 'Invalid branch name for merge' }, { status: 400 });
+ }
+
+ // Initialize the repository and checkout main branch
+ await git.init({ fs, dir: REPO_DIR });
+ await git.checkout({ fs, dir: REPO_DIR, ref: 'main' });
+
+ // Perform the merge
+ await git.merge({
+ fs,
+ dir: REPO_DIR,
+ ours: 'main',
+ theirs: branchName,
+ author: {
+ name: 'Instruct Lab Local',
+ email: 'local@instructlab.ai'
+ }
+ });
+
+ return NextResponse.json({ message: `Successfully merged ${branchName} into main.` }, { status: 200 });
+ } else if (action === 'diff') {
+ // Ensure valid branch name
+ if (!branchName || branchName === 'main') {
+ return NextResponse.json({ error: 'Invalid branch name for comparison' }, { status: 400 });
+ }
+
+ // Fetch the commit SHA for `main` and the target branch
+ const mainCommit = await git.resolveRef({ fs, dir: REPO_DIR, ref: 'main' });
+ const branchCommit = await git.resolveRef({ fs, dir: REPO_DIR, ref: branchName });
+
+ const mainFiles = await getFilesFromTree(mainCommit);
+ const branchFiles = await getFilesFromTree(branchCommit);
+
+ const changes = [];
+
+ // Identify modified and deleted files
+ for (const file in mainFiles) {
+ if (branchFiles[file]) {
+ if (mainFiles[file] !== branchFiles[file]) {
+ changes.push({ file, status: 'modified' });
+ }
+ } else {
+ changes.push({ file, status: 'deleted' });
+ }
+ }
+
+ // Identify added files
+ for (const file in branchFiles) {
+ if (!mainFiles[file]) {
+ changes.push({ file, status: 'added' });
+ }
+ }
+
+ return NextResponse.json({ changes }, { status: 200 });
+ } else {
+ return NextResponse.json({ error: 'Invalid action specified' }, { status: 400 });
+ }
+ } catch (error) {
+ console.error(`Failed to ${action === 'merge' ? 'merge branch' : 'compare branches'}:`, error);
+ return NextResponse.json(
+ {
+ error: `Failed to ${action === 'merge' ? 'merge branch' : 'compare branches'}`
+ },
+ { status: 500 }
+ );
+ }
+}
+
+// Helper function to recursively gather file paths and their oids from a tree
+async function getFilesFromTree(commitOid: string) {
+ const fileMap: Record = {};
+
+ async function walkTree(dir: string) {
+ const tree = await git.readTree({ fs, dir: REPO_DIR, oid: commitOid, filepath: dir });
+
+ for (const entry of tree.tree) {
+ const fullPath = path.join(dir, entry.path);
+ if (entry.type === 'blob') {
+ fileMap[fullPath] = entry.oid;
+ } else if (entry.type === 'tree') {
+ await walkTree(fullPath); // Recursively walk subdirectories
+ }
+ }
+ }
+
+ await walkTree('');
+ return fileMap;
+}
diff --git a/src/app/api/local/pr/knowledge/route.ts b/src/app/api/local/pr/knowledge/route.ts
new file mode 100644
index 00000000..a378d3fd
--- /dev/null
+++ b/src/app/api/local/pr/knowledge/route.ts
@@ -0,0 +1,73 @@
+// src/app/api/local/pr/knowledge/route.ts
+
+import { NextResponse } from 'next/server';
+import { NextRequest } from 'next/server';
+import * as git from 'isomorphic-git';
+import fs from 'fs';
+import path from 'path';
+import yaml from 'js-yaml';
+
+// Define paths and configuration
+const REPO_DIR = process.env.NEXT_PUBLIC_LOCAL_REPO_PATH || '/path/to/local/repo'; // Update with actual local path
+const KNOWLEDGE_DIR = 'knowledge';
+
+export async function POST(req: NextRequest) {
+ try {
+ // Extract the QnA data from the request body TODO: what is documentOutline?
+ const { content, attribution, name, email, submissionSummary, documentOutline, filePath } = await req.json(); // eslint-disable-line @typescript-eslint/no-unused-vars
+
+ // Define branch name and file paths
+ const branchName = `knowledge-contribution-${Date.now()}`;
+ const newYamlFilePath = path.join(KNOWLEDGE_DIR, filePath, 'qna.yaml');
+ const newAttributionFilePath = path.join(KNOWLEDGE_DIR, filePath, 'attribution.txt');
+
+ // Convert data to YAML and plain text formats
+ const yamlString = yaml.dump(content);
+ const attributionContent = `
+Title of work: ${attribution.title_of_work}
+Link to work: ${attribution.link_to_work}
+Revision: ${attribution.revision}
+License of the work: ${attribution.license_of_the_work}
+Creator names: ${attribution.creator_names}
+`;
+
+ // Initialize the repository if it doesn’t exist
+ await git.init({ fs, dir: REPO_DIR });
+
+ // Create a new branch
+ await git.branch({ fs, dir: REPO_DIR, ref: branchName });
+
+ // Checkout the new branch
+ await git.checkout({ fs, dir: REPO_DIR, ref: branchName });
+
+ // Write YAML file to the knowledge directory
+ const yamlFilePath = path.join(REPO_DIR, newYamlFilePath);
+ fs.mkdirSync(path.dirname(yamlFilePath), { recursive: true });
+ fs.writeFileSync(yamlFilePath, yamlString);
+
+ // Write attribution file to the knowledge directory
+ const attributionFilePath = path.join(REPO_DIR, newAttributionFilePath);
+ fs.writeFileSync(attributionFilePath, attributionContent);
+
+ // Stage the files
+ await git.add({ fs, dir: REPO_DIR, filepath: newYamlFilePath });
+ await git.add({ fs, dir: REPO_DIR, filepath: newAttributionFilePath });
+
+ // Commit the changes
+ await git.commit({
+ fs,
+ dir: REPO_DIR,
+ message: `${submissionSummary}\n\nSigned-off-by: ${name} <${email}>`,
+ author: {
+ name: name,
+ email: email
+ }
+ });
+
+ // Respond with success message and branch name
+ return NextResponse.json({ message: 'Branch and commit created locally', branch: branchName }, { status: 201 });
+ } catch (error) {
+ console.error('Failed to create local branch and commit:', error);
+ return NextResponse.json({ error: 'Failed to create local branch and commit' }, { status: 500 });
+ }
+}
diff --git a/src/app/api/local/pr/skill/route.ts b/src/app/api/local/pr/skill/route.ts
new file mode 100644
index 00000000..873da074
--- /dev/null
+++ b/src/app/api/local/pr/skill/route.ts
@@ -0,0 +1,70 @@
+// src/app/api/local/pr/skill/route.ts
+import { NextResponse } from 'next/server';
+import { NextRequest } from 'next/server';
+import * as git from 'isomorphic-git';
+import fs from 'fs';
+import path from 'path';
+import yaml from 'js-yaml';
+
+// Define paths and configuration
+const REPO_DIR = process.env.NEXT_PUBLIC_LOCAL_REPO_PATH || '/path/to/local/repo'; // Update with actual local path
+const SKILLS_DIR = 'compositional_skills';
+
+export async function POST(req: NextRequest) {
+ try {
+ // Extract the QnA data from the request body TODO: what is documentOutline?
+ const { content, attribution, name, email, submissionSummary, documentOutline, filePath } = await req.json(); // eslint-disable-line @typescript-eslint/no-unused-vars
+
+ // Define file paths
+ const branchName = `skill-contribution-${Date.now()}`;
+ const newYamlFilePath = path.join(SKILLS_DIR, filePath, 'qna.yaml');
+ const newAttributionFilePath = path.join(SKILLS_DIR, filePath, 'attribution.txt');
+
+ // Prepare file content
+ const yamlString = yaml.dump(content);
+ const attributionString = `
+Title of work: ${attribution.title_of_work}
+License of the work: ${attribution.license_of_the_work}
+Creator names: ${attribution.creator_names}
+`;
+
+ // Initialize the repository if it doesn’t exist
+ await git.init({ fs, dir: REPO_DIR });
+
+ // Create a new branch
+ await git.branch({ fs, dir: REPO_DIR, ref: branchName });
+
+ // Checkout the new branch
+ await git.checkout({ fs, dir: REPO_DIR, ref: branchName });
+
+ // Write the QnA YAML file
+ const yamlFilePath = path.join(REPO_DIR, newYamlFilePath);
+ fs.mkdirSync(path.dirname(yamlFilePath), { recursive: true });
+ fs.writeFileSync(yamlFilePath, yamlString);
+
+ // Write the attribution text file
+ const attributionFilePath = path.join(REPO_DIR, newAttributionFilePath);
+ fs.writeFileSync(attributionFilePath, attributionString);
+
+ // Stage files
+ await git.add({ fs, dir: REPO_DIR, filepath: newYamlFilePath });
+ await git.add({ fs, dir: REPO_DIR, filepath: newAttributionFilePath });
+
+ // Commit files
+ await git.commit({
+ fs,
+ dir: REPO_DIR,
+ message: `${submissionSummary}\n\nSigned-off-by: ${name} <${email}>`,
+ author: {
+ name: name,
+ email: email
+ }
+ });
+
+ // Respond with success
+ return NextResponse.json({ message: 'Branch and commit created locally', branch: branchName }, { status: 201 });
+ } catch (error) {
+ console.error('Failed to create local branch and commit:', error);
+ return NextResponse.json({ error: 'Failed to create local branch and commit' }, { status: 500 });
+ }
+}
diff --git a/src/app/experimental/contribute-local/configuration-local/page.tsx b/src/app/experimental/contribute-local/configuration-local/page.tsx
new file mode 100644
index 00000000..c7550df8
--- /dev/null
+++ b/src/app/experimental/contribute-local/configuration-local/page.tsx
@@ -0,0 +1,14 @@
+// src/app/experimental/contribute-local/clone-repo/page.tsx
+import * as React from 'react';
+import { AppLayout } from '@/components/AppLayout';
+import CloneRepoLocal from '@/components/Experimental/CloneRepoLocal/CloneRepoLocal';
+
+const CloneRepoPage: React.FC = () => {
+ return (
+
+
+
+ );
+};
+
+export default CloneRepoPage;
diff --git a/src/app/experimental/contribute-local/knowledge/page.tsx b/src/app/experimental/contribute-local/knowledge/page.tsx
new file mode 100644
index 00000000..dc1a4cf2
--- /dev/null
+++ b/src/app/experimental/contribute-local/knowledge/page.tsx
@@ -0,0 +1,14 @@
+// src/app/experimental/contribute-local/knowledge/page.tsx
+import * as React from 'react';
+import { AppLayout } from '@/components/AppLayout';
+import { KnowledgeFormLocal } from '@/components/Experimental/ContributeLocal/Knowledge';
+
+const KnowledgeFormLocalPage: React.FC = () => {
+ return (
+
+
+
+ );
+};
+
+export default KnowledgeFormLocalPage;
diff --git a/src/app/experimental/contribute-local/skill/page.tsx b/src/app/experimental/contribute-local/skill/page.tsx
new file mode 100644
index 00000000..9bcdc347
--- /dev/null
+++ b/src/app/experimental/contribute-local/skill/page.tsx
@@ -0,0 +1,14 @@
+// src/app/experimental/contribute-local/skill/page.tsx
+import * as React from 'react';
+import { AppLayout } from '@/components/AppLayout';
+import SkillFormLocal from '@/components/Experimental/ContributeLocal/Skill';
+
+const SkillFormPageLocal: React.FC = () => {
+ return (
+
+
+
+ );
+};
+
+export default SkillFormPageLocal;
diff --git a/src/app/experimental/dashboard-local/page.tsx b/src/app/experimental/dashboard-local/page.tsx
new file mode 100644
index 00000000..e1da680f
--- /dev/null
+++ b/src/app/experimental/dashboard-local/page.tsx
@@ -0,0 +1,17 @@
+// src/app/experimental/dashboard-local/page.tsx
+'use client';
+
+import * as React from 'react';
+import '@patternfly/react-core/dist/styles/base.css';
+import { AppLayout } from '@/components/AppLayout';
+import { DashboardLocal } from '@/components/Experimental/DashboardLocal';
+
+const Home: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
+
+export default Home;
diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx
index 0999861c..417189cd 100644
--- a/src/components/AppLayout.tsx
+++ b/src/components/AppLayout.tsx
@@ -87,7 +87,13 @@ const AppLayout: React.FunctionComponent = ({ children }) => {
},
isExperimentalEnabled && {
path: '/experimental',
- label: 'Experimental Features'
+ label: 'Experimental Features',
+ children: [
+ { path: '/experimental/dashboard-local/', label: 'Local Dashboard' },
+ { path: '/experimental/contribute-local/skill/', label: 'Local Skill' },
+ { path: '/experimental/contribute-local/knowledge/', label: 'Local Knowledge' },
+ { path: '/experimental/contribute-local/configuration-local/', label: 'Local Configuration' }
+ ]
}
].filter(Boolean) as Route[];
diff --git a/src/components/Experimental/CloneRepoLocal/CloneRepoLocal.module.css b/src/components/Experimental/CloneRepoLocal/CloneRepoLocal.module.css
new file mode 100644
index 00000000..f975b732
--- /dev/null
+++ b/src/components/Experimental/CloneRepoLocal/CloneRepoLocal.module.css
@@ -0,0 +1,9 @@
+/* CloneRepoLocal.module.css */
+
+.formContainer {
+ padding: 3rem;
+}
+
+.formGroup {
+ max-width: 500px;
+}
diff --git a/src/components/Experimental/CloneRepoLocal/CloneRepoLocal.tsx b/src/components/Experimental/CloneRepoLocal/CloneRepoLocal.tsx
new file mode 100644
index 00000000..a7e2cd1e
--- /dev/null
+++ b/src/components/Experimental/CloneRepoLocal/CloneRepoLocal.tsx
@@ -0,0 +1,115 @@
+// src/components/Experimental/CloneRepoLocal/CloneRepoLocal.tsx
+'use client';
+
+import React, { useState } from 'react';
+import { Form } from '@patternfly/react-core/dist/dynamic/components/Form';
+import { FormGroup } from '@patternfly/react-core/dist/dynamic/components/Form';
+import { TextInput } from '@patternfly/react-core/dist/dynamic/components/TextInput';
+import { ActionGroup } from '@patternfly/react-core/dist/dynamic/components/Form';
+import { Button } from '@patternfly/react-core/dist/dynamic/components/Button';
+import { FormHelperText } from '@patternfly/react-core/dist/dynamic/components/Form';
+import { HelperText } from '@patternfly/react-core/dist/dynamic/components/HelperText';
+import { HelperTextItem } from '@patternfly/react-core/dist/dynamic/components/HelperText';
+import styles from './CloneRepoLocal.module.css';
+
+// Retrieve the public base directory from environment variables
+const BASE_DIRECTORY = process.env.NEXT_PUBLIC_BASE_CLONE_DIRECTORY;
+
+const CloneRepoLocal: React.FC = () => {
+ const [repoUrl, setRepoUrl] = useState('');
+ const [directory, setDirectory] = useState('');
+ const [message, setMessage] = useState('');
+ const [fullPath, setFullPath] = useState('');
+
+ const handleRepoUrlChange = (_event: React.FormEvent, value: string) => {
+ setRepoUrl(value);
+ };
+
+ const handleDirectoryChange = (_event: React.FormEvent, value: string) => {
+ setDirectory(value);
+ };
+
+ const handleCloneRepo = async () => {
+ if (!repoUrl || !directory) {
+ setMessage('Please provide both repository URL and directory path.');
+ return;
+ }
+
+ try {
+ const response = await fetch('/api/local/clone-repo', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ repoUrl, directory })
+ });
+
+ const result = await response.json();
+ if (response.ok) {
+ setMessage(result.message);
+ setFullPath(result.fullPath); // Store the full path to display to the user
+ } else {
+ setMessage(`Error: ${result.message}`);
+ setFullPath(''); // Clear full path if there's an error
+ }
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+ setMessage(`An unexpected error occurred: ${errorMessage}`);
+ setFullPath('');
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default CloneRepoLocal;
diff --git a/src/components/Experimental/ContributeLocal/Knowledge/SubmitLocal/Submit.tsx b/src/components/Experimental/ContributeLocal/Knowledge/SubmitLocal/Submit.tsx
new file mode 100644
index 00000000..93ca97df
--- /dev/null
+++ b/src/components/Experimental/ContributeLocal/Knowledge/SubmitLocal/Submit.tsx
@@ -0,0 +1,112 @@
+// src/components/Experimental/ContributeLocal/Knowledge/SubmitLocal/Submit.tsx
+import React from 'react';
+import { Button } from '@patternfly/react-core/dist/dynamic/components/Button';
+import { ActionGroupAlertContent, KnowledgeFormData } from '..';
+import { AttributionData, KnowledgeYamlData } from '@/types';
+import { KnowledgeSchemaVersion } from '@/types/const';
+import { dumpYaml } from '@/utils/yamlConfig';
+import { validateFields } from '@/components/Contribute/Knowledge/validation';
+
+interface Props {
+ disableAction: boolean;
+ knowledgeFormData: KnowledgeFormData;
+ setActionGroupAlertContent: React.Dispatch>;
+ githubUsername: string | undefined;
+ resetForm: () => void;
+}
+
+const Submit: React.FC = ({ disableAction, knowledgeFormData, setActionGroupAlertContent, githubUsername, resetForm }) => {
+ const handleSubmit = async (event: React.FormEvent) => {
+ event.preventDefault();
+ if (!validateFields(knowledgeFormData, setActionGroupAlertContent)) return;
+
+ // Strip leading slash and ensure trailing slash in the file path
+ let sanitizedFilePath = knowledgeFormData.filePath!.startsWith('/') ? knowledgeFormData.filePath!.slice(1) : knowledgeFormData.filePath;
+ sanitizedFilePath = sanitizedFilePath!.endsWith('/') ? sanitizedFilePath : `${sanitizedFilePath}/`;
+
+ const knowledgeYamlData: KnowledgeYamlData = {
+ created_by: githubUsername!,
+ version: KnowledgeSchemaVersion,
+ domain: knowledgeFormData.domain!,
+ document_outline: knowledgeFormData.documentOutline!,
+ seed_examples: knowledgeFormData.seedExamples.map((example) => ({
+ context: example.context,
+ questions_and_answers: example.questionAndAnswers.map((questionAndAnswer) => ({
+ question: questionAndAnswer.question,
+ answer: questionAndAnswer.answer
+ }))
+ })),
+ document: {
+ repo: knowledgeFormData.knowledgeDocumentRepositoryUrl!,
+ commit: knowledgeFormData.knowledgeDocumentCommit!,
+ patterns: knowledgeFormData.documentName!.split(',').map((pattern) => pattern.trim())
+ }
+ };
+
+ const yamlString = dumpYaml(knowledgeYamlData);
+
+ const attributionData: AttributionData = {
+ title_of_work: knowledgeFormData.titleWork!,
+ link_to_work: knowledgeFormData.linkWork!,
+ revision: knowledgeFormData.revision!,
+ license_of_the_work: knowledgeFormData.licenseWork!,
+ creator_names: knowledgeFormData.creators!
+ };
+
+ const waitForSubmissionAlert: ActionGroupAlertContent = {
+ title: 'Knowledge contribution submission in progress.!',
+ message: `Once the submission is successful, it will provide the link to the newly created Pull Request.`,
+ success: true,
+ waitAlert: true,
+ timeout: false
+ };
+ setActionGroupAlertContent(waitForSubmissionAlert);
+
+ const name = knowledgeFormData.name;
+ const email = knowledgeFormData.email;
+ const submissionSummary = knowledgeFormData.submissionSummary;
+ const documentOutline = knowledgeFormData.documentOutline;
+ const response = await fetch('/api/local/pr/knowledge', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ content: yamlString,
+ attribution: attributionData,
+ name,
+ email,
+ submissionSummary,
+ documentOutline,
+ filePath: sanitizedFilePath
+ })
+ });
+
+ if (!response.ok) {
+ const actionGroupAlertContent: ActionGroupAlertContent = {
+ title: `Failed data submission`,
+ message: response.statusText,
+ success: false
+ };
+ setActionGroupAlertContent(actionGroupAlertContent);
+ return;
+ }
+
+ const result = await response.json();
+ const actionGroupAlertContent: ActionGroupAlertContent = {
+ title: 'Knowledge contribution submitted successfully!',
+ message: `Thank you for your contribution!`,
+ url: `${result.html_url}`,
+ success: true
+ };
+ setActionGroupAlertContent(actionGroupAlertContent);
+ resetForm();
+ };
+ return (
+
+ Submit
+
+ );
+};
+
+export default Submit;
diff --git a/src/components/Experimental/ContributeLocal/Knowledge/index.tsx b/src/components/Experimental/ContributeLocal/Knowledge/index.tsx
new file mode 100644
index 00000000..fcf32c80
--- /dev/null
+++ b/src/components/Experimental/ContributeLocal/Knowledge/index.tsx
@@ -0,0 +1,611 @@
+// src/components/Experimental/ContributeLocal/Knowledge/index.tsx
+'use client';
+import React, { useEffect, useMemo, useState } from 'react';
+import './knowledge.css';
+import { Alert, AlertActionCloseButton } from '@patternfly/react-core/dist/dynamic/components/Alert';
+import { ActionGroup } from '@patternfly/react-core/dist/dynamic/components/Form';
+import { Form } from '@patternfly/react-core/dist/dynamic/components/Form';
+import { getGitHubUsername } from '@/utils/github';
+import { useSession } from 'next-auth/react';
+import AuthorInformation from '@/components/Contribute/AuthorInformation';
+import { FormType } from '@/components/Contribute/AuthorInformation';
+import KnowledgeInformation from '@/components/Contribute/Knowledge/KnowledgeInformation/KnowledgeInformation';
+import FilePathInformation from '@/components/Contribute/Knowledge/FilePathInformation/FilePathInformation';
+import DocumentInformation from '@/components/Contribute/Knowledge/DocumentInformation/DocumentInformation';
+import AttributionInformation from '@/components/Contribute/Knowledge/AttributionInformation/AttributionInformation';
+import Submit from './SubmitLocal/Submit';
+import { Breadcrumb } from '@patternfly/react-core/dist/dynamic/components/Breadcrumb';
+import { BreadcrumbItem } from '@patternfly/react-core/dist/dynamic/components/Breadcrumb';
+import { PageBreadcrumb } from '@patternfly/react-core/dist/dynamic/components/Page';
+import { PageGroup } from '@patternfly/react-core/dist/dynamic/components/Page';
+import { PageSection } from '@patternfly/react-core/dist/dynamic/components/Page';
+import { TextContent } from '@patternfly/react-core/dist/dynamic/components/Text';
+import { Title } from '@patternfly/react-core/dist/dynamic/components/Title';
+import KnowledgeDescriptionContent from '@/components/Contribute/Knowledge/KnowledgeDescription/KnowledgeDescriptionContent';
+import KnowledgeSeedExample from '@/components/Contribute/Knowledge/KnowledgeSeedExample/KnowledgeSeedExample';
+import { checkKnowledgeFormCompletion } from '@/components/Contribute/Knowledge/validation';
+import { ValidatedOptions } from '@patternfly/react-core/dist/esm/helpers/constants';
+import { DownloadDropdown } from '@/components/Contribute/Knowledge/DownloadDropdown/DownloadDropdown';
+import { ViewDropdown } from '@/components/Contribute/Knowledge/ViewDropdown/ViewDropdown';
+import Update from '@/components/Contribute/Knowledge/Update/Update';
+import { PullRequestFile } from '@/types';
+import { Button } from '@patternfly/react-core/dist/esm/components/Button/Button';
+import { useRouter } from 'next/navigation';
+import { autoFillKnowledgeFields } from '@/components/Contribute/Knowledge/AutoFill';
+import { Spinner } from '@patternfly/react-core/dist/esm/components/Spinner';
+
+export interface QuestionAndAnswerPair {
+ immutable: boolean;
+ question: string;
+ isQuestionValid: ValidatedOptions;
+ questionValidationError?: string;
+ answer: string;
+ isAnswerValid: ValidatedOptions;
+ answerValidationError?: string;
+}
+
+export interface SeedExample {
+ immutable: boolean;
+ isExpanded: boolean;
+ context: string;
+ isContextValid: ValidatedOptions;
+ validationError?: string;
+ questionAndAnswers: QuestionAndAnswerPair[];
+}
+
+export interface KnowledgeFormData {
+ email: string;
+ name: string;
+ submissionSummary: string;
+ domain: string;
+ documentOutline: string;
+ filePath: string;
+ seedExamples: SeedExample[];
+ knowledgeDocumentRepositoryUrl: string;
+ knowledgeDocumentCommit: string;
+ documentName: string;
+ titleWork: string;
+ linkWork: string;
+ revision: string;
+ licenseWork: string;
+ creators: string;
+}
+
+export interface KnowledgeEditFormData {
+ isEditForm: boolean;
+ knowledgeVersion: number;
+ pullRequestNumber: number;
+ branchName: string;
+ yamlFile: PullRequestFile;
+ attributionFile: PullRequestFile;
+ knowledgeFormData: KnowledgeFormData;
+}
+
+export interface ActionGroupAlertContent {
+ title: string;
+ message: string;
+ waitAlert?: boolean;
+ url?: string;
+ success: boolean;
+ timeout?: number | boolean;
+}
+
+export interface KnowledgeFormProps {
+ knowledgeEditFormData?: KnowledgeEditFormData;
+}
+
+export const KnowledgeFormLocal: React.FunctionComponent = ({ knowledgeEditFormData }) => {
+ const [deploymentType, setDeploymentType] = useState();
+
+ const { data: session } = useSession();
+ const [githubUsername, setGithubUsername] = useState('');
+ // Author Information
+ const [email, setEmail] = useState('');
+ const [name, setName] = useState('');
+
+ // Knowledge Information
+ const [submissionSummary, setSubmissionSummary] = useState('');
+ const [domain, setDomain] = useState('');
+ const [documentOutline, setDocumentOutline] = useState('');
+
+ // File Path Information
+ const [filePath, setFilePath] = useState('');
+
+ const [knowledgeDocumentRepositoryUrl, setKnowledgeDocumentRepositoryUrl] = useState('');
+ const [knowledgeDocumentCommit, setKnowledgeDocumentCommit] = useState('');
+ // This used to be 'patterns' but I am not totally sure what this variable actually is...
+ const [documentName, setDocumentName] = useState('');
+
+ // Attribution Information
+ // State
+ const [titleWork, setTitleWork] = useState('');
+ const [linkWork, setLinkWork] = useState('');
+ const [revision, setRevision] = useState('');
+ const [licenseWork, setLicenseWork] = useState('');
+ const [creators, setCreators] = useState('');
+
+ const [actionGroupAlertContent, setActionGroupAlertContent] = useState();
+
+ const [disableAction, setDisableAction] = useState(true);
+ const [reset, setReset] = useState(false);
+
+ const router = useRouter();
+
+ const emptySeedExample: SeedExample = {
+ immutable: true,
+ isExpanded: false,
+ context: '',
+ isContextValid: ValidatedOptions.default,
+ questionAndAnswers: [
+ {
+ immutable: true,
+ question: '',
+ isQuestionValid: ValidatedOptions.default,
+ answer: '',
+ isAnswerValid: ValidatedOptions.default
+ },
+ {
+ immutable: true,
+ question: '',
+ isQuestionValid: ValidatedOptions.default,
+ answer: '',
+ isAnswerValid: ValidatedOptions.default
+ },
+ {
+ immutable: true,
+ question: '',
+ isQuestionValid: ValidatedOptions.default,
+ answer: '',
+ isAnswerValid: ValidatedOptions.default
+ }
+ ]
+ };
+
+ const [seedExamples, setSeedExamples] = useState([
+ emptySeedExample,
+ emptySeedExample,
+ emptySeedExample,
+ emptySeedExample,
+ emptySeedExample
+ ]);
+
+ useEffect(() => {
+ const getEnvVariables = async () => {
+ const res = await fetch('/api/envConfig');
+ const envConfig = await res.json();
+ setDeploymentType(envConfig.DEPLOYMENT_TYPE);
+ };
+ getEnvVariables();
+ }, []);
+
+ useEffect(() => {
+ if (session?.user?.name && session?.user?.email) {
+ setName(session?.user?.name);
+ setEmail(session?.user?.email);
+ }
+ }, [session?.user]);
+
+ useMemo(() => {
+ const fetchUsername = async () => {
+ if (session?.accessToken) {
+ try {
+ const headers = {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${session.accessToken}`,
+ Accept: 'application/vnd.github+json',
+ 'X-GitHub-Api-Version': '2022-11-28'
+ };
+
+ const fetchedUsername = await getGitHubUsername(headers);
+ setGithubUsername(fetchedUsername);
+ } catch (error) {
+ console.error('Failed to fetch GitHub username:', error);
+ }
+ }
+ };
+
+ fetchUsername();
+ }, [session?.accessToken]);
+
+ useEffect(() => {
+ // Set all elements from the knowledgeFormData to the state
+ if (knowledgeEditFormData) {
+ setEmail(knowledgeEditFormData.knowledgeFormData.email);
+ setName(knowledgeEditFormData.knowledgeFormData.name);
+ setSubmissionSummary(knowledgeEditFormData.knowledgeFormData.submissionSummary);
+ setDomain(knowledgeEditFormData.knowledgeFormData.domain);
+ setDocumentOutline(knowledgeEditFormData.knowledgeFormData.documentOutline);
+ setFilePath(knowledgeEditFormData.knowledgeFormData.filePath);
+ setKnowledgeDocumentRepositoryUrl(knowledgeEditFormData.knowledgeFormData.knowledgeDocumentRepositoryUrl);
+ setKnowledgeDocumentCommit(knowledgeEditFormData.knowledgeFormData.knowledgeDocumentCommit);
+ setDocumentName(knowledgeEditFormData.knowledgeFormData.documentName);
+ setTitleWork(knowledgeEditFormData.knowledgeFormData.titleWork);
+ setLinkWork(knowledgeEditFormData.knowledgeFormData.linkWork);
+ setRevision(knowledgeEditFormData.knowledgeFormData.revision);
+ setLicenseWork(knowledgeEditFormData.knowledgeFormData.licenseWork);
+ setCreators(knowledgeEditFormData.knowledgeFormData.creators);
+ setSeedExamples(knowledgeEditFormData.knowledgeFormData.seedExamples);
+ }
+ }, [knowledgeEditFormData]);
+
+ const validateContext = (context: string) => {
+ // Split the context into words based on spaces
+ const contextStr = context.trim();
+ if (contextStr.length == 0) {
+ setDisableAction(true);
+ return { msg: 'Context is required', status: ValidatedOptions.error };
+ }
+ const tokens = contextStr.split(/\s+/);
+ if (tokens.length > 0 && tokens.length <= 500) {
+ setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData));
+ return { msg: 'Valid Input', status: ValidatedOptions.success };
+ }
+ setDisableAction(true);
+ const errorMsg = 'Context must be less than 500 words. Current word count: ' + tokens.length;
+ return { msg: errorMsg, status: ValidatedOptions.error };
+ };
+
+ const validateQuestion = (question: string) => {
+ const questionStr = question.trim();
+ if (questionStr.length == 0) {
+ setDisableAction(true);
+ return { msg: 'Question is required', status: ValidatedOptions.error };
+ }
+ const tokens = questionStr.split(/\s+/);
+ if (tokens.length > 0 && tokens.length < 250) {
+ setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData));
+ return { msg: 'Valid input', status: ValidatedOptions.success };
+ }
+ setDisableAction(true);
+ return { msg: 'Question must be less than 250 words. Current word count: ' + tokens.length, status: ValidatedOptions.error };
+ };
+
+ const validateAnswer = (answer: string) => {
+ const answerStr = answer.trim();
+ if (answerStr.length == 0) {
+ setDisableAction(true);
+ return { msg: 'Answer is required', status: ValidatedOptions.error };
+ }
+ const tokens = answerStr.split(/\s+/);
+ if (tokens.length > 0 && tokens.length < 250) {
+ setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData));
+ return { msg: 'Valid input', status: ValidatedOptions.success };
+ }
+ setDisableAction(true);
+ return { msg: 'Answer must be less than 250 words. Current word count: ' + tokens.length, status: ValidatedOptions.error };
+ };
+
+ const handleContextInputChange = (seedExampleIndex: number, contextValue: string): void => {
+ setSeedExamples(
+ seedExamples.map((seedExample: SeedExample, index: number) =>
+ index === seedExampleIndex
+ ? {
+ ...seedExample,
+ context: contextValue
+ }
+ : seedExample
+ )
+ );
+ };
+
+ const handleContextBlur = (seedExampleIndex: number): void => {
+ const updatedSeedExamples = seedExamples.map((seedExample: SeedExample, index: number): SeedExample => {
+ if (index === seedExampleIndex) {
+ const { msg, status } = validateContext(seedExample.context);
+ return {
+ ...seedExample,
+ isContextValid: status,
+ validationError: msg
+ };
+ }
+ return seedExample;
+ });
+ setSeedExamples(updatedSeedExamples);
+ };
+
+ const handleQuestionInputChange = (seedExampleIndex: number, questionAndAnswerIndex: number, questionValue: string): void => {
+ setSeedExamples(
+ seedExamples.map((seedExample: SeedExample, index: number) =>
+ index === seedExampleIndex
+ ? {
+ ...seedExample,
+ questionAndAnswers: seedExample.questionAndAnswers.map((questionAndAnswerPair: QuestionAndAnswerPair, index: number) =>
+ index === questionAndAnswerIndex
+ ? {
+ ...questionAndAnswerPair,
+ question: questionValue
+ }
+ : questionAndAnswerPair
+ )
+ }
+ : seedExample
+ )
+ );
+ };
+
+ const handleQuestionBlur = (seedExampleIndex: number, questionAndAnswerIndex: number): void => {
+ setSeedExamples(
+ seedExamples.map((seedExample: SeedExample, index: number) =>
+ index === seedExampleIndex
+ ? {
+ ...seedExample,
+ questionAndAnswers: seedExample.questionAndAnswers.map((questionAndAnswerPair: QuestionAndAnswerPair, index: number) => {
+ if (index === questionAndAnswerIndex) {
+ const { msg, status } = validateQuestion(questionAndAnswerPair.question);
+ return {
+ ...questionAndAnswerPair,
+ isQuestionValid: status,
+ questionValidationError: msg
+ };
+ }
+ return questionAndAnswerPair;
+ })
+ }
+ : seedExample
+ )
+ );
+ };
+
+ const handleAnswerInputChange = (seedExampleIndex: number, questionAndAnswerIndex: number, answerValue: string): void => {
+ setSeedExamples(
+ seedExamples.map((seedExample: SeedExample, index: number) =>
+ index === seedExampleIndex
+ ? {
+ ...seedExample,
+ questionAndAnswers: seedExample.questionAndAnswers.map((questionAndAnswerPair: QuestionAndAnswerPair, index: number) =>
+ index === questionAndAnswerIndex
+ ? {
+ ...questionAndAnswerPair,
+ answer: answerValue
+ }
+ : questionAndAnswerPair
+ )
+ }
+ : seedExample
+ )
+ );
+ };
+
+ const handleAnswerBlur = (seedExampleIndex: number, questionAndAnswerIndex: number): void => {
+ setSeedExamples(
+ seedExamples.map((seedExample: SeedExample, index: number) =>
+ index === seedExampleIndex
+ ? {
+ ...seedExample,
+ questionAndAnswers: seedExample.questionAndAnswers.map((questionAndAnswerPair: QuestionAndAnswerPair, index: number) => {
+ if (index === questionAndAnswerIndex) {
+ const { msg, status } = validateAnswer(questionAndAnswerPair.answer);
+ return {
+ ...questionAndAnswerPair,
+ isAnswerValid: status,
+ answerValidationError: msg
+ };
+ }
+ return questionAndAnswerPair;
+ })
+ }
+ : seedExample
+ )
+ );
+ };
+
+ const onCloseActionGroupAlert = () => {
+ setActionGroupAlertContent(undefined);
+ };
+
+ const resetForm = (): void => {
+ setEmail('');
+ setName('');
+ setDocumentOutline('');
+ setSubmissionSummary('');
+ setDomain('');
+ setKnowledgeDocumentRepositoryUrl('');
+ setKnowledgeDocumentCommit('');
+ setDocumentName('');
+ setTitleWork('');
+ setLinkWork('');
+ setLicenseWork('');
+ setCreators('');
+ setRevision('');
+ setFilePath('');
+ setSeedExamples([emptySeedExample, emptySeedExample, emptySeedExample, emptySeedExample, emptySeedExample]);
+ setDisableAction(true);
+
+ // setReset is just reset button, value has no impact.
+ setReset(reset ? false : true);
+ };
+
+ const autoFillForm = (): void => {
+ setEmail(autoFillKnowledgeFields.email);
+ setName(autoFillKnowledgeFields.name);
+ setDocumentOutline(autoFillKnowledgeFields.documentOutline);
+ setSubmissionSummary(autoFillKnowledgeFields.submissionSummary);
+ setDomain(autoFillKnowledgeFields.domain);
+ setKnowledgeDocumentRepositoryUrl(autoFillKnowledgeFields.knowledgeDocumentRepositoryUrl);
+ setKnowledgeDocumentCommit(autoFillKnowledgeFields.knowledgeDocumentCommit);
+ setDocumentName(autoFillKnowledgeFields.documentName);
+ setTitleWork(autoFillKnowledgeFields.titleWork);
+ setLinkWork(autoFillKnowledgeFields.linkWork);
+ setLicenseWork(autoFillKnowledgeFields.licenseWork);
+ setCreators(autoFillKnowledgeFields.creators);
+ setRevision(autoFillKnowledgeFields.revision);
+ setSeedExamples(autoFillKnowledgeFields.seedExamples);
+ };
+
+ const knowledgeFormData: KnowledgeFormData = {
+ email: email,
+ name: name,
+ submissionSummary: submissionSummary,
+ domain: domain,
+ documentOutline: documentOutline,
+ filePath: filePath,
+ seedExamples: seedExamples,
+ knowledgeDocumentRepositoryUrl: knowledgeDocumentRepositoryUrl,
+ knowledgeDocumentCommit: knowledgeDocumentCommit,
+ documentName: documentName,
+ titleWork: titleWork,
+ linkWork: linkWork,
+ revision: revision,
+ licenseWork: licenseWork,
+ creators: creators
+ };
+
+ useEffect(() => {
+ setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData));
+ }, [knowledgeFormData]);
+
+ const handleCancel = () => {
+ router.push('/dashboard');
+ };
+
+ return (
+
+
+
+ Dashboard
+ Knowledge Contribution
+
+
+
+
+
+ Knowledge Contribution
+
+
+
+
+ {deploymentType === 'dev' && (
+
+ Auto-Fill
+
+ )}
+
+
+
+
+ );
+};
+
+export default KnowledgeFormLocal;
diff --git a/src/components/Experimental/ContributeLocal/Knowledge/knowledge.css b/src/components/Experimental/ContributeLocal/Knowledge/knowledge.css
new file mode 100644
index 00000000..9f65e32c
--- /dev/null
+++ b/src/components/Experimental/ContributeLocal/Knowledge/knowledge.css
@@ -0,0 +1,31 @@
+/* Knowledge CSS */
+
+.form-k {
+ width: 80%;
+ margin-bottom: 50px;
+ background-color: white;
+}
+
+.submit-k:hover,
+.download-k-yaml:hover,
+.download-k-attribution:hover,
+.button-active,
+.button-active:hover {
+ background-color: #45a049;
+}
+
+.heading-k {
+ text-align: left;
+ font-size: medium;
+}
+
+.button-secondary:hover {
+ border-color: #45a049;
+}
+
+.spinner-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 1rem;
+}
diff --git a/src/components/Experimental/ContributeLocal/Skill/SubmitLocal/SubmitLocal.tsx b/src/components/Experimental/ContributeLocal/Skill/SubmitLocal/SubmitLocal.tsx
new file mode 100644
index 00000000..f90bcca7
--- /dev/null
+++ b/src/components/Experimental/ContributeLocal/Skill/SubmitLocal/SubmitLocal.tsx
@@ -0,0 +1,106 @@
+import React from 'react';
+import { Button } from '@patternfly/react-core/dist/dynamic/components/Button';
+import { ActionGroupAlertContent, SkillFormData } from '..';
+import { AttributionData, SkillYamlData } from '@/types';
+import { SkillSchemaVersion } from '@/types/const';
+import { dumpYaml } from '@/utils/yamlConfig';
+import { validateFields } from '@/components/Contribute/Skill/validation';
+
+interface Props {
+ disableAction: boolean;
+ skillFormData: SkillFormData;
+ setActionGroupAlertContent: React.Dispatch>;
+ githubUsername: string | undefined;
+ resetForm: () => void;
+}
+
+// temporary location of these validation functions. Once the Skills form has been refactored then these can be moved out to the utils file.
+
+const Submit: React.FC = ({ disableAction, skillFormData, setActionGroupAlertContent, githubUsername, resetForm }) => {
+ const handleSubmit = async (event: React.FormEvent) => {
+ event.preventDefault();
+ if (!validateFields(skillFormData, setActionGroupAlertContent)) return;
+
+ console.log('skillFormData :' + skillFormData);
+ // Strip leading slash and ensure trailing slash in the file path
+ let sanitizedFilePath = skillFormData.filePath!.startsWith('/') ? skillFormData.filePath!.slice(1) : skillFormData.filePath;
+ sanitizedFilePath = sanitizedFilePath!.endsWith('/') ? sanitizedFilePath : `${sanitizedFilePath}/`;
+
+ const skillYamlData: SkillYamlData = {
+ created_by: githubUsername!,
+ version: SkillSchemaVersion,
+ task_description: skillFormData.documentOutline!,
+ seed_examples: skillFormData.seedExamples.map((example) => ({
+ context: example.context,
+ question: example.question,
+ answer: example.answer
+ }))
+ };
+
+ const yamlString = dumpYaml(skillYamlData);
+
+ const attributionData: AttributionData = {
+ title_of_work: skillFormData.titleWork!,
+ license_of_the_work: skillFormData.licenseWork!,
+ creator_names: skillFormData.creators!,
+ link_to_work: '',
+ revision: ''
+ };
+
+ const waitForSubmissionAlert: ActionGroupAlertContent = {
+ title: 'Skill contribution submission in progress.!',
+ message: `Once the submission is successful, it will provide the link to the newly created Pull Request.`,
+ success: true,
+ waitAlert: true,
+ timeout: false
+ };
+ setActionGroupAlertContent(waitForSubmissionAlert);
+
+ const name = skillFormData.name;
+ const email = skillFormData.email;
+ const submissionSummary = skillFormData.submissionSummary;
+ const documentOutline = skillFormData.documentOutline;
+ const response = await fetch('/api/local/pr/skill/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ content: yamlString,
+ attribution: attributionData,
+ name,
+ email,
+ submissionSummary,
+ documentOutline,
+ filePath: sanitizedFilePath
+ })
+ });
+
+ if (!response.ok) {
+ const actionGroupAlertContent: ActionGroupAlertContent = {
+ title: `Failed data submission`,
+ message: response.statusText,
+ success: false
+ };
+ setActionGroupAlertContent(actionGroupAlertContent);
+ return;
+ }
+
+ const result = await response.json();
+ const actionGroupAlertContent: ActionGroupAlertContent = {
+ title: 'Skill contribution submitted successfully!',
+ message: `Thank you for your contribution!`,
+ url: `${result.html_url}`,
+ success: true
+ };
+ setActionGroupAlertContent(actionGroupAlertContent);
+ resetForm();
+ };
+ return (
+
+ Submit
+
+ );
+};
+
+export default Submit;
diff --git a/src/components/Experimental/ContributeLocal/Skill/index.tsx b/src/components/Experimental/ContributeLocal/Skill/index.tsx
new file mode 100644
index 00000000..476ccfd2
--- /dev/null
+++ b/src/components/Experimental/ContributeLocal/Skill/index.tsx
@@ -0,0 +1,484 @@
+// src/components/Experimental/ContributeLocal/Skill/index.tsx
+'use client';
+import React, { useEffect, useMemo, useState } from 'react';
+import './skills.css';
+import { Alert, AlertActionCloseButton } from '@patternfly/react-core/dist/dynamic/components/Alert';
+import { ActionGroup } from '@patternfly/react-core/dist/dynamic/components/Form';
+import { Form } from '@patternfly/react-core/dist/dynamic/components/Form';
+import { getGitHubUsername } from '@/utils/github';
+import { useSession } from 'next-auth/react';
+import AuthorInformation from '@/components/Contribute/AuthorInformation';
+import { FormType } from '@/components/Contribute/AuthorInformation';
+import FilePathInformation from '@/components/Contribute/Skill/FilePathInformation/FilePathInformation';
+import AttributionInformation from '@/components/Contribute/Skill/AttributionInformation/AttributionInformation';
+import Submit from '@/components/Experimental/ContributeLocal/Skill/SubmitLocal/SubmitLocal';
+import { Breadcrumb } from '@patternfly/react-core/dist/dynamic/components/Breadcrumb';
+import { BreadcrumbItem } from '@patternfly/react-core/dist/dynamic/components/Breadcrumb';
+import { PageBreadcrumb } from '@patternfly/react-core/dist/dynamic/components/Page';
+import { PageGroup } from '@patternfly/react-core/dist/dynamic/components/Page';
+import { PageSection } from '@patternfly/react-core/dist/dynamic/components/Page';
+import { TextContent } from '@patternfly/react-core/dist/dynamic/components/Text';
+import { Title } from '@patternfly/react-core/dist/dynamic/components/Title';
+import { checkSkillFormCompletion } from '@/components/Contribute/Skill/validation';
+import { ValidatedOptions } from '@patternfly/react-core/dist/esm/helpers/constants';
+import { DownloadDropdown } from '@/components/Contribute/Skill/DownloadDropdown/DownloadDropdown';
+import { ViewDropdown } from '@/components/Contribute/Skill/ViewDropdown/ViewDropdown';
+import Update from '@/components/Contribute/Skill/Update/Update';
+import { PullRequestFile } from '@/types';
+import { Button } from '@patternfly/react-core/dist/esm/components/Button/Button';
+import { useRouter } from 'next/navigation';
+import SkillsSeedExample from '@/components/Contribute/Skill/SkillsSeedExample/SkillsSeedExample';
+import SkillsInformation from '@/components/Contribute/Skill/SkillsInformation/SkillsInformation';
+import SkillsDescriptionContent from '@/components/Contribute/Skill/SkillsDescription/SkillsDescriptionContent';
+import { autoFillSkillsFields } from '@/components/Contribute/Skill/AutoFill';
+import { Spinner } from '@patternfly/react-core/dist/dynamic/components/Spinner';
+
+export interface SeedExample {
+ immutable: boolean;
+ isExpanded: boolean;
+ context?: string;
+ isContextValid?: ValidatedOptions;
+ validationError?: string;
+ question: string;
+ isQuestionValid: ValidatedOptions;
+ questionValidationError?: string;
+ answer: string;
+ isAnswerValid: ValidatedOptions;
+ answerValidationError?: string;
+}
+
+export interface SkillFormData {
+ email: string;
+ name: string;
+ submissionSummary: string;
+ documentOutline: string;
+ filePath: string;
+ seedExamples: SeedExample[];
+ titleWork: string;
+ licenseWork: string;
+ creators: string;
+}
+
+export interface SkillEditFormData {
+ isEditForm: boolean;
+ skillVersion: number;
+ pullRequestNumber: number;
+ branchName: string;
+ yamlFile: PullRequestFile;
+ attributionFile: PullRequestFile;
+ skillFormData: SkillFormData;
+}
+
+export interface ActionGroupAlertContent {
+ title: string;
+ message: string;
+ waitAlert?: boolean;
+ url?: string;
+ success: boolean;
+ timeout?: number | boolean;
+}
+
+export interface SkillFormProps {
+ skillEditFormData?: SkillEditFormData;
+}
+
+export const SkillFormLocal: React.FunctionComponent = ({ skillEditFormData }) => {
+ const [deploymentType, setDeploymentType] = useState();
+
+ const { data: session } = useSession();
+ const [githubUsername, setGithubUsername] = useState('');
+ // Author Information
+ const [email, setEmail] = useState('');
+ const [name, setName] = useState('');
+
+ // Skills Information
+ const [submissionSummary, setSubmissionSummary] = useState('');
+ const [documentOutline, setDocumentOutline] = useState('');
+
+ // File Path Information
+ const [filePath, setFilePath] = useState('');
+
+ // Attribution Information
+ // State
+ const [titleWork, setTitleWork] = useState('');
+ const [licenseWork, setLicenseWork] = useState('');
+ const [creators, setCreators] = useState('');
+
+ const [actionGroupAlertContent, setActionGroupAlertContent] = useState();
+
+ const [disableAction, setDisableAction] = useState(true);
+ const [reset, setReset] = useState(false);
+
+ const router = useRouter();
+
+ const emptySeedExample: SeedExample = {
+ immutable: true,
+ isExpanded: false,
+ context: '',
+ isContextValid: ValidatedOptions.default,
+ question: '',
+ isQuestionValid: ValidatedOptions.default,
+ answer: '',
+ isAnswerValid: ValidatedOptions.default
+ };
+
+ const [seedExamples, setSeedExamples] = useState([
+ emptySeedExample,
+ emptySeedExample,
+ emptySeedExample,
+ emptySeedExample,
+ emptySeedExample
+ ]);
+
+ useEffect(() => {
+ const getEnvVariables = async () => {
+ const res = await fetch('/api/envConfig');
+ const envConfig = await res.json();
+ setDeploymentType(envConfig.DEPLOYMENT_TYPE);
+ };
+ getEnvVariables();
+ }, []);
+
+ useEffect(() => {
+ if (session?.user?.name && session?.user?.email) {
+ setName(session?.user?.name);
+ setEmail(session?.user?.email);
+ }
+ }, [session?.user]);
+
+ useMemo(() => {
+ const fetchUsername = async () => {
+ if (session?.accessToken) {
+ try {
+ const header = {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${session.accessToken}`,
+ Accept: 'application/vnd.github+json',
+ 'X-GitHub-Api-Version': '2022-11-28'
+ };
+ const fetchedUsername = await getGitHubUsername(header);
+ setGithubUsername(fetchedUsername);
+ } catch (error) {
+ console.error('Failed to fetch GitHub username:', error);
+ }
+ }
+ };
+
+ fetchUsername();
+ }, [session?.accessToken]);
+
+ useEffect(() => {
+ // Set all elements from the skillFormData to the state
+ if (skillEditFormData) {
+ setEmail(skillEditFormData.skillFormData.email);
+ setName(skillEditFormData.skillFormData.name);
+ setSubmissionSummary(skillEditFormData.skillFormData.submissionSummary);
+ setDocumentOutline(skillEditFormData.skillFormData.documentOutline);
+ setFilePath(skillEditFormData.skillFormData.filePath);
+ setTitleWork(skillEditFormData.skillFormData.titleWork);
+ setLicenseWork(skillEditFormData.skillFormData.licenseWork);
+ setCreators(skillEditFormData.skillFormData.creators);
+ setSeedExamples(skillEditFormData.skillFormData.seedExamples);
+ }
+ }, [skillEditFormData]);
+
+ const validateContext = (context: string): ValidatedOptions => {
+ // Context is optional
+ console.log('context', context);
+ return ValidatedOptions.success;
+ };
+
+ const validateQuestion = (question: string): ValidatedOptions => {
+ if (question.length > 0 && question.length < 250) {
+ setDisableAction(!checkSkillFormCompletion(skillFormData));
+ return ValidatedOptions.success;
+ }
+ setDisableAction(true);
+ return ValidatedOptions.error;
+ };
+
+ const validateAnswer = (answer: string): ValidatedOptions => {
+ if (answer.length > 0 && answer.length < 250) {
+ setDisableAction(!checkSkillFormCompletion(skillFormData));
+ return ValidatedOptions.success;
+ }
+ setDisableAction(true);
+ return ValidatedOptions.error;
+ };
+
+ const handleContextInputChange = (seedExampleIndex: number, contextValue: string): void => {
+ setSeedExamples(
+ seedExamples.map((seedExample: SeedExample, index: number) =>
+ index === seedExampleIndex
+ ? {
+ ...seedExample,
+ context: contextValue
+ }
+ : seedExample
+ )
+ );
+ };
+
+ const handleContextBlur = (seedExampleIndex: number): void => {
+ setSeedExamples(
+ seedExamples.map((seedExample: SeedExample, index: number) =>
+ index === seedExampleIndex
+ ? {
+ ...seedExample,
+ isContextValid: validateContext(seedExample.context ? seedExample.context : '')
+ }
+ : seedExample
+ )
+ );
+ };
+
+ const handleAnswerInputChange = (seedExampleIndex: number, answerValue: string): void => {
+ setSeedExamples(
+ seedExamples.map((seedExample: SeedExample, index: number) =>
+ index === seedExampleIndex
+ ? {
+ ...seedExample,
+ answer: answerValue
+ }
+ : seedExample
+ )
+ );
+ };
+
+ const handleAnswerBlur = (seedExampleIndex: number): void => {
+ setSeedExamples(
+ seedExamples.map((seedExample: SeedExample, index: number) =>
+ index === seedExampleIndex
+ ? {
+ ...seedExample,
+ isAnswerValid: validateAnswer(seedExample.answer)
+ }
+ : seedExample
+ )
+ );
+ };
+
+ const handleQuestionInputChange = (seedExampleIndex: number, questionValue: string): void => {
+ setSeedExamples(
+ seedExamples.map((seedExample: SeedExample, index: number) =>
+ index === seedExampleIndex
+ ? {
+ ...seedExample,
+ question: questionValue
+ }
+ : seedExample
+ )
+ );
+ };
+
+ const handleQuestionBlur = (seedExampleIndex: number): void => {
+ setSeedExamples(
+ seedExamples.map((seedExample: SeedExample, index: number) =>
+ index === seedExampleIndex
+ ? {
+ ...seedExample,
+ isQuestionValid: validateQuestion(seedExample.question)
+ }
+ : seedExample
+ )
+ );
+ };
+
+ const addSeedExample = (): void => {
+ const seedExample = emptySeedExample;
+ seedExample.immutable = false;
+ seedExample.isExpanded = true;
+ setSeedExamples([...seedExamples, seedExample]);
+ setDisableAction(true);
+ };
+
+ const deleteSeedExample = (seedExampleIndex: number): void => {
+ setSeedExamples(seedExamples.filter((_, index: number) => index !== seedExampleIndex));
+ setDisableAction(!checkSkillFormCompletion(skillFormData));
+ };
+
+ const onCloseActionGroupAlert = () => {
+ setActionGroupAlertContent(undefined);
+ };
+
+ const resetForm = (): void => {
+ setEmail('');
+ setName('');
+ setDocumentOutline('');
+ setSubmissionSummary('');
+ setTitleWork('');
+ setLicenseWork('');
+ setCreators('');
+ setFilePath('');
+ setSeedExamples([emptySeedExample, emptySeedExample, emptySeedExample, emptySeedExample, emptySeedExample]);
+ setDisableAction(true);
+
+ // setReset is just reset button, value has no impact.
+ setReset(reset ? false : true);
+ };
+
+ const autoFillForm = (): void => {
+ setEmail(autoFillSkillsFields.email);
+ setName(autoFillSkillsFields.name);
+ setDocumentOutline(autoFillSkillsFields.documentOutline);
+ setSubmissionSummary(autoFillSkillsFields.submissionSummary);
+ setTitleWork(autoFillSkillsFields.titleWork);
+ setLicenseWork(autoFillSkillsFields.licenseWork);
+ setCreators(autoFillSkillsFields.creators);
+ setFilePath(autoFillSkillsFields.filePath);
+ setSeedExamples(autoFillSkillsFields.seedExamples);
+ };
+
+ const skillFormData: SkillFormData = {
+ email: email,
+ name: name,
+ submissionSummary: submissionSummary,
+ documentOutline: documentOutline,
+ filePath: filePath,
+ seedExamples: seedExamples,
+ titleWork: titleWork,
+ licenseWork: licenseWork,
+ creators: creators
+ };
+
+ useEffect(() => {
+ setDisableAction(!checkSkillFormCompletion(skillFormData));
+ }, [skillFormData]);
+
+ const handleCancel = () => {
+ router.push('/dashboard');
+ };
+
+ return (
+
+
+
+ Dashboard
+ Skill Contribution
+
+
+
+
+
+ Skill Contribution
+
+
+
+
+ {deploymentType === 'dev' && (
+
+ Auto-Fill
+
+ )}
+
+
+
+ );
+};
+
+export default SkillFormLocal;
diff --git a/src/components/Experimental/ContributeLocal/Skill/skills.css b/src/components/Experimental/ContributeLocal/Skill/skills.css
new file mode 100644
index 00000000..877e5d7a
--- /dev/null
+++ b/src/components/Experimental/ContributeLocal/Skill/skills.css
@@ -0,0 +1,18 @@
+/* Skill CSS */
+
+.form-s {
+ width: 80%;
+ margin-bottom: 50px;
+ background-color: white;
+}
+
+.submit:hover,
+.download-yaml:hover,
+.download-attribution:hover {
+ background-color: #45a049;
+}
+
+.heading {
+ text-align: left;
+ font-size: medium;
+}
diff --git a/src/components/Experimental/DashboardLocal/index.tsx b/src/components/Experimental/DashboardLocal/index.tsx
new file mode 100644
index 00000000..18808c51
--- /dev/null
+++ b/src/components/Experimental/DashboardLocal/index.tsx
@@ -0,0 +1,161 @@
+// src/components/Experimental/DashboardLocal/index.tsx
+import * as React from 'react';
+import { Card, CardTitle, CardBody } from '@patternfly/react-core/dist/dynamic/components/Card';
+import { Stack, StackItem } from '@patternfly/react-core/dist/dynamic/layouts/Stack';
+import { PageBreadcrumb } from '@patternfly/react-core/dist/dynamic/components/Page';
+import { PageSection } from '@patternfly/react-core/dist/dynamic/components/Page';
+import { Title } from '@patternfly/react-core/dist/dynamic/components/Title';
+import { Breadcrumb, BreadcrumbItem } from '@patternfly/react-core/dist/esm/components/Breadcrumb';
+import { Spinner } from '@patternfly/react-core/dist/esm/components/Spinner';
+import { Button } from '@patternfly/react-core/dist/dynamic/components/Button';
+import { Flex, FlexItem } from '@patternfly/react-core/dist/dynamic/layouts/Flex';
+import { Modal, ModalVariant } from '@patternfly/react-core/dist/esm/components/Modal';
+
+const DashboardLocal: React.FunctionComponent = () => {
+ const [branches, setBranches] = React.useState([]);
+ const [isLoading, setIsLoading] = React.useState(true);
+ const [mergeStatus, setMergeStatus] = React.useState<{ branch: string; message: string; success: boolean } | null>(null);
+ const [diffData, setDiffData] = React.useState<{ branch: string; changes: { file: string; status: string }[] } | null>(null);
+ const [isModalOpen, setIsModalOpen] = React.useState(false);
+
+ // Fetch branches from the API route
+ React.useEffect(() => {
+ const fetchBranches = async () => {
+ try {
+ const response = await fetch('/api/local/git/branches');
+ const result = await response.json();
+ if (response.ok) {
+ setBranches(result.branches);
+ } else {
+ console.error('Failed to fetch branches:', result.error);
+ }
+ } catch (error) {
+ console.error('Error fetching branches:', error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ fetchBranches();
+ }, []);
+
+ const handleMerge = async (branchName: string) => {
+ setMergeStatus(null); // Clear previous status
+ try {
+ const response = await fetch('/api/local/git/branches', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ branchName, action: 'merge' })
+ });
+
+ const result = await response.json();
+ if (response.ok) {
+ setMergeStatus({ branch: branchName, message: result.message, success: true });
+ } else {
+ setMergeStatus({ branch: branchName, message: result.error, success: false });
+ }
+ } catch (error) {
+ setMergeStatus({ branch: branchName, message: 'Merge failed due to an unexpected error.', success: false });
+ console.error('Error merging branch:', error);
+ }
+ };
+
+ const handleShowChanges = async (branchName: string) => {
+ try {
+ const response = await fetch('/api/local/git/branches', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ branchName, action: 'diff' })
+ });
+
+ const result = await response.json();
+ if (response.ok) {
+ setDiffData({ branch: branchName, changes: result.changes });
+ setIsModalOpen(true);
+ } else {
+ console.error('Failed to get branch changes:', result.error);
+ }
+ } catch (error) {
+ console.error('Error fetching branch changes:', error);
+ }
+ };
+
+ return (
+
+
+
+ Dashboard
+
+
+
+
+ Git Repository Branches
+
+ Listing all branches from the local repository:
+
+
+
+ {isLoading ? (
+
+ ) : branches.length === 0 ? (
+ No branches found in the repository.
+ ) : (
+
+ {branches.map((branch) => (
+
+
+
+
+
+ Branch Name: {branch}
+
+
+ {branch !== 'main' && (
+ <>
+ handleMerge(branch)} style={{ marginRight: '8px' }}>
+ Merge into Main
+
+ handleShowChanges(branch)}>
+ Show Changes
+
+ >
+ )}
+
+
+
+
+
+ ))}
+
+ )}
+
+ {mergeStatus && (
+
+ {mergeStatus.message}
+
+ )}
+
+ setIsModalOpen(false)}
+ >
+ {diffData?.changes.length ? (
+
+ {diffData.changes.map((change) => (
+
+ {change.file} - {change.status}
+
+ ))}
+
+ ) : (
+ No differences found.
+ )}
+
+
+
+ );
+};
+
+export { DashboardLocal };
From de48901ae3568a0b0d4d8eccd3916f4e56305d1b Mon Sep 17 00:00:00 2001
From: Brent Salisbury
Date: Sun, 3 Nov 2024 23:48:13 -0500
Subject: [PATCH 2/3] Add creation date and an empty state to the local repo
dashboard
Signed-off-by: Brent Salisbury
---
package-lock.json | 112 ++++++++++++++++--
package.json | 1 -
src/app/api/local/git/branches/route.ts | 16 ++-
.../Experimental/DashboardLocal/index.tsx | 98 ++++++++++++---
4 files changed, 199 insertions(+), 28 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 3373f501..61002013 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -33,7 +33,6 @@
"@next/eslint-plugin-next": "^14.2.3",
"@playwright/test": "^1.47.2",
"@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.4",
- "@types/js-cookie": "^3.0.6",
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.5.0",
"@types/react": "18.3.1",
@@ -729,6 +728,111 @@
"node": ">= 10"
}
},
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "15.0.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.2.tgz",
+ "integrity": "sha512-KUpBVxIbjzFiUZhiLIpJiBoelqzQtVZbdNNsehhUn36e2YzKHphnK8eTUW1s/4aPy5kH/UTid8IuVbaOpedhpw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "15.0.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.2.tgz",
+ "integrity": "sha512-9J7TPEcHNAZvwxXRzOtiUvwtTD+fmuY0l7RErf8Yyc7kMpE47MIQakl+3jecmkhOoIyi/Rp+ddq7j4wG6JDskQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "15.0.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.2.tgz",
+ "integrity": "sha512-BjH4ZSzJIoTTZRh6rG+a/Ry4SW0HlizcPorqNBixBWc3wtQtj4Sn9FnRZe22QqrPnzoaW0ctvSz4FaH4eGKMww==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "15.0.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.2.tgz",
+ "integrity": "sha512-i3U2TcHgo26sIhcwX/Rshz6avM6nizrZPvrDVDY1bXcLH1ndjbO8zuC7RoHp0NSK7wjJMPYzm7NYL1ksSKFreA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "15.0.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.2.tgz",
+ "integrity": "sha512-AMfZfSVOIR8fa+TXlAooByEF4OB00wqnms1sJ1v+iu8ivwvtPvnkwdzzFMpsK5jA2S9oNeeQ04egIWVb4QWmtQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "15.0.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.2.tgz",
+ "integrity": "sha512-JkXysDT0/hEY47O+Hvs8PbZAeiCQVxKfGtr4GUpNAhlG2E0Mkjibuo8ryGD29Qb5a3IOnKYNoZlh/MyKd2Nbww==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "15.0.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.2.tgz",
+ "integrity": "sha512-foaUL0NqJY/dX0Pi/UcZm5zsmSk5MtP/gxx3xOPyREkMFN+CTjctPfu3QaqrQHinaKdPnMWPJDKt4VjDfTBe/Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@@ -1002,12 +1106,6 @@
"hoist-non-react-statics": "^3.3.0"
}
},
- "node_modules/@types/js-cookie": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
- "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
- "dev": true
- },
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
diff --git a/package.json b/package.json
index c73857dc..b17ba967 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,6 @@
"@next/eslint-plugin-next": "^14.2.3",
"@playwright/test": "^1.47.2",
"@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.4",
- "@types/js-cookie": "^3.0.6",
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.5.0",
"@types/react": "18.3.1",
diff --git a/src/app/api/local/git/branches/route.ts b/src/app/api/local/git/branches/route.ts
index 9d880342..5175eec3 100644
--- a/src/app/api/local/git/branches/route.ts
+++ b/src/app/api/local/git/branches/route.ts
@@ -16,9 +16,21 @@ export async function GET() {
// List all branches in the repository
const branches = await git.listBranches({ fs, dir: REPO_DIR });
+ const branchDetails = [];
- // Return the list of branches as JSON
- return NextResponse.json({ branches }, { status: 200 });
+ for (const branch of branches) {
+ const branchCommit = await git.resolveRef({ fs, dir: REPO_DIR, ref: branch });
+ const commitDetails = await git.readCommit({ fs, dir: REPO_DIR, oid: branchCommit });
+
+ branchDetails.push({
+ name: branch,
+ creationDate: commitDetails.commit.committer.timestamp * 1000 // Convert to milliseconds
+ });
+ }
+
+ branchDetails.sort((a, b) => b.creationDate - a.creationDate); // Sort by creation date, newest first
+
+ return NextResponse.json({ branches: branchDetails }, { status: 200 });
} catch (error) {
console.error('Failed to list branches:', error);
return NextResponse.json({ error: 'Failed to list branches' }, { status: 500 });
diff --git a/src/components/Experimental/DashboardLocal/index.tsx b/src/components/Experimental/DashboardLocal/index.tsx
index 18808c51..1d71c404 100644
--- a/src/components/Experimental/DashboardLocal/index.tsx
+++ b/src/components/Experimental/DashboardLocal/index.tsx
@@ -1,6 +1,6 @@
// src/components/Experimental/DashboardLocal/index.tsx
import * as React from 'react';
-import { Card, CardTitle, CardBody } from '@patternfly/react-core/dist/dynamic/components/Card';
+import { Card, CardBody } from '@patternfly/react-core/dist/dynamic/components/Card';
import { Stack, StackItem } from '@patternfly/react-core/dist/dynamic/layouts/Stack';
import { PageBreadcrumb } from '@patternfly/react-core/dist/dynamic/components/Page';
import { PageSection } from '@patternfly/react-core/dist/dynamic/components/Page';
@@ -10,13 +10,24 @@ import { Spinner } from '@patternfly/react-core/dist/esm/components/Spinner';
import { Button } from '@patternfly/react-core/dist/dynamic/components/Button';
import { Flex, FlexItem } from '@patternfly/react-core/dist/dynamic/layouts/Flex';
import { Modal, ModalVariant } from '@patternfly/react-core/dist/esm/components/Modal';
+import {
+ EmptyState,
+ EmptyStateBody,
+ EmptyStateHeader,
+ EmptyStateFooter,
+ EmptyStateActions
+} from '@patternfly/react-core/dist/dynamic/components/EmptyState';
+import GithubIcon from '@patternfly/react-icons/dist/esm/icons/github-icon';
+import Image from 'next/image';
+import { useRouter } from 'next/navigation';
const DashboardLocal: React.FunctionComponent = () => {
- const [branches, setBranches] = React.useState([]);
+ const [branches, setBranches] = React.useState<{ name: string; creationDate: number }[]>([]);
const [isLoading, setIsLoading] = React.useState(true);
const [mergeStatus, setMergeStatus] = React.useState<{ branch: string; message: string; success: boolean } | null>(null);
const [diffData, setDiffData] = React.useState<{ branch: string; changes: { file: string; status: string }[] } | null>(null);
const [isModalOpen, setIsModalOpen] = React.useState(false);
+ const router = useRouter();
// Fetch branches from the API route
React.useEffect(() => {
@@ -25,7 +36,9 @@ const DashboardLocal: React.FunctionComponent = () => {
const response = await fetch('/api/local/git/branches');
const result = await response.json();
if (response.ok) {
- setBranches(result.branches);
+ // Filter out 'main' branch
+ const filteredBranches = result.branches.filter((branch: { name: string }) => branch.name !== 'main');
+ setBranches(filteredBranches);
} else {
console.error('Failed to fetch branches:', result.error);
}
@@ -39,6 +52,11 @@ const DashboardLocal: React.FunctionComponent = () => {
fetchBranches();
}, []);
+ const formatDateTime = (timestamp: number) => {
+ const date = new Date(timestamp);
+ return `${date.toLocaleDateString()} ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
+ };
+
const handleMerge = async (branchName: string) => {
setMergeStatus(null); // Clear previous status
try {
@@ -89,37 +107,81 @@ const DashboardLocal: React.FunctionComponent = () => {
- Git Repository Branches
+ Local Git Repository Branches
- Listing all branches from the local repository:
{isLoading ? (
) : branches.length === 0 ? (
- No branches found in the repository.
+
+
+
+
+ }
+ />
+
+
+ InstructLab is a powerful and accessible tool for advancing generative AI through community collaboration and open-source principles.
+ By contributing your own data, you can help train and refine the language model.
+
+ To get started, contribute a skill or contribute knowledge.
+
+
+
+
+ router.push('/experimental/contribute-local/configuration-local/')}>
+ Clone a taxonomy
+
+ router.push('/experimental/contribute-local/skill/')}>
+ Contribute Skill
+
+ router.push('/experimental/contribute-local/knowledge/')}>
+ Contribute Knowledge
+
+ router.push('/playground/chat')}>
+ Chat with the Models
+
+
+
+ }
+ iconPosition="right"
+ component="a"
+ href="https://github.com/instructlab"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ View the Project on Github
+
+
+
+
) : (
{branches.map((branch) => (
-
+
- Branch Name: {branch}
+ Branch Name: {branch.name}
+
+ Created on: {formatDateTime(branch.creationDate)}
- {branch !== 'main' && (
- <>
- handleMerge(branch)} style={{ marginRight: '8px' }}>
- Merge into Main
-
- handleShowChanges(branch)}>
- Show Changes
-
- >
- )}
+ handleMerge(branch.name)} style={{ marginRight: '8px' }}>
+ Merge into Main
+
+ handleShowChanges(branch.name)}>
+ Show Changes
+
From 81f44fd0fbc57c2982fdc1e6cb425d3301ed2574 Mon Sep 17 00:00:00 2001
From: Brent Salisbury
Date: Wed, 6 Nov 2024 20:33:51 -0500
Subject: [PATCH 3/3] Use the user's email for the created_by field for local
submissions
- Also redirect the submission success popup to the local dashboard
Signed-off-by: Brent Salisbury
---
package-lock.json | 324 +++++++++++++++++-
package.json | 1 +
.../Knowledge/SubmitLocal/Submit.tsx | 13 +-
.../ContributeLocal/Knowledge/index.tsx | 4 +-
.../Skill/SubmitLocal/SubmitLocal.tsx | 15 +-
.../ContributeLocal/Skill/index.tsx | 4 +-
6 files changed, 334 insertions(+), 27 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 61002013..ea69db6e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,6 +24,7 @@
"node-fetch": "^3.3.2",
"react": "18.3.1",
"react-dom": "18.3.1",
+ "sharp": "^0.33.5",
"uuid": "^11.0.2",
"winston": "^3.16.0"
},
@@ -343,6 +344,15 @@
"kuler": "^2.0.0"
}
},
+ "node_modules/@emnapi/runtime": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
+ "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -583,6 +593,27 @@
"@img/sharp-libvips-darwin-arm64": "1.0.4"
}
},
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
+ "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.0.4"
+ }
+ },
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
@@ -598,6 +629,291 @@
"url": "https://opencollective.com/libvips"
}
},
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
+ "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
+ "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
+ "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
+ "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
+ "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
+ "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
+ "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
+ "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.0.5"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
+ "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
+ "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
+ "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
+ "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
+ "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
+ "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
+ "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
+ "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -2166,7 +2482,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
- "optional": true,
"engines": {
"node": ">=8"
}
@@ -5885,7 +6200,6 @@
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
"hasInstallScript": true,
- "optional": true,
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.3",
@@ -5923,7 +6237,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
- "optional": true,
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
@@ -5936,7 +6249,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "optional": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -5947,14 +6259,12 @@
"node_modules/sharp/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "optional": true
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/sharp/node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
- "optional": true,
"bin": {
"semver": "bin/semver.js"
},
diff --git a/package.json b/package.json
index b17ba967..b9dfe2f6 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"node-fetch": "^3.3.2",
"react": "18.3.1",
"react-dom": "18.3.1",
+ "sharp": "^0.33.5",
"uuid": "^11.0.2",
"winston": "^3.16.0"
},
diff --git a/src/components/Experimental/ContributeLocal/Knowledge/SubmitLocal/Submit.tsx b/src/components/Experimental/ContributeLocal/Knowledge/SubmitLocal/Submit.tsx
index 93ca97df..442e9ad4 100644
--- a/src/components/Experimental/ContributeLocal/Knowledge/SubmitLocal/Submit.tsx
+++ b/src/components/Experimental/ContributeLocal/Knowledge/SubmitLocal/Submit.tsx
@@ -11,11 +11,11 @@ interface Props {
disableAction: boolean;
knowledgeFormData: KnowledgeFormData;
setActionGroupAlertContent: React.Dispatch>;
- githubUsername: string | undefined;
+ email: string;
resetForm: () => void;
}
-const Submit: React.FC = ({ disableAction, knowledgeFormData, setActionGroupAlertContent, githubUsername, resetForm }) => {
+const Submit: React.FC = ({ disableAction, knowledgeFormData, setActionGroupAlertContent, email, resetForm }) => {
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!validateFields(knowledgeFormData, setActionGroupAlertContent)) return;
@@ -25,7 +25,7 @@ const Submit: React.FC = ({ disableAction, knowledgeFormData, setActionGr
sanitizedFilePath = sanitizedFilePath!.endsWith('/') ? sanitizedFilePath : `${sanitizedFilePath}/`;
const knowledgeYamlData: KnowledgeYamlData = {
- created_by: githubUsername!,
+ created_by: email,
version: KnowledgeSchemaVersion,
domain: knowledgeFormData.domain!,
document_outline: knowledgeFormData.documentOutline!,
@@ -54,7 +54,7 @@ const Submit: React.FC = ({ disableAction, knowledgeFormData, setActionGr
};
const waitForSubmissionAlert: ActionGroupAlertContent = {
- title: 'Knowledge contribution submission in progress.!',
+ title: 'Knowledge contribution submission in progress!',
message: `Once the submission is successful, it will provide the link to the newly created Pull Request.`,
success: true,
waitAlert: true,
@@ -63,7 +63,6 @@ const Submit: React.FC = ({ disableAction, knowledgeFormData, setActionGr
setActionGroupAlertContent(waitForSubmissionAlert);
const name = knowledgeFormData.name;
- const email = knowledgeFormData.email;
const submissionSummary = knowledgeFormData.submissionSummary;
const documentOutline = knowledgeFormData.documentOutline;
const response = await fetch('/api/local/pr/knowledge', {
@@ -92,11 +91,11 @@ const Submit: React.FC = ({ disableAction, knowledgeFormData, setActionGr
return;
}
- const result = await response.json();
+ await response.json();
const actionGroupAlertContent: ActionGroupAlertContent = {
title: 'Knowledge contribution submitted successfully!',
message: `Thank you for your contribution!`,
- url: `${result.html_url}`,
+ url: '/experimental/dashboard-local/',
success: true
};
setActionGroupAlertContent(actionGroupAlertContent);
diff --git a/src/components/Experimental/ContributeLocal/Knowledge/index.tsx b/src/components/Experimental/ContributeLocal/Knowledge/index.tsx
index fcf32c80..fac134f1 100644
--- a/src/components/Experimental/ContributeLocal/Knowledge/index.tsx
+++ b/src/components/Experimental/ContributeLocal/Knowledge/index.tsx
@@ -568,7 +568,7 @@ export const KnowledgeFormLocal: React.FunctionComponent = (
actionGroupAlertContent.url &&
actionGroupAlertContent.url.trim().length > 0 && (
- View your pull request
+ View your new branch
)}
@@ -592,7 +592,7 @@ export const KnowledgeFormLocal: React.FunctionComponent = (
disableAction={disableAction}
knowledgeFormData={knowledgeFormData}
setActionGroupAlertContent={setActionGroupAlertContent}
- githubUsername={githubUsername}
+ email={email}
resetForm={resetForm}
/>
)}
diff --git a/src/components/Experimental/ContributeLocal/Skill/SubmitLocal/SubmitLocal.tsx b/src/components/Experimental/ContributeLocal/Skill/SubmitLocal/SubmitLocal.tsx
index f90bcca7..7cf9967a 100644
--- a/src/components/Experimental/ContributeLocal/Skill/SubmitLocal/SubmitLocal.tsx
+++ b/src/components/Experimental/ContributeLocal/Skill/SubmitLocal/SubmitLocal.tsx
@@ -10,13 +10,11 @@ interface Props {
disableAction: boolean;
skillFormData: SkillFormData;
setActionGroupAlertContent: React.Dispatch>;
- githubUsername: string | undefined;
+ email: string;
resetForm: () => void;
}
-// temporary location of these validation functions. Once the Skills form has been refactored then these can be moved out to the utils file.
-
-const Submit: React.FC = ({ disableAction, skillFormData, setActionGroupAlertContent, githubUsername, resetForm }) => {
+const Submit: React.FC = ({ disableAction, skillFormData, setActionGroupAlertContent, email, resetForm }) => {
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!validateFields(skillFormData, setActionGroupAlertContent)) return;
@@ -27,7 +25,7 @@ const Submit: React.FC = ({ disableAction, skillFormData, setActionGroupA
sanitizedFilePath = sanitizedFilePath!.endsWith('/') ? sanitizedFilePath : `${sanitizedFilePath}/`;
const skillYamlData: SkillYamlData = {
- created_by: githubUsername!,
+ created_by: email,
version: SkillSchemaVersion,
task_description: skillFormData.documentOutline!,
seed_examples: skillFormData.seedExamples.map((example) => ({
@@ -48,7 +46,7 @@ const Submit: React.FC = ({ disableAction, skillFormData, setActionGroupA
};
const waitForSubmissionAlert: ActionGroupAlertContent = {
- title: 'Skill contribution submission in progress.!',
+ title: 'Skill contribution submission in progress!',
message: `Once the submission is successful, it will provide the link to the newly created Pull Request.`,
success: true,
waitAlert: true,
@@ -57,7 +55,6 @@ const Submit: React.FC = ({ disableAction, skillFormData, setActionGroupA
setActionGroupAlertContent(waitForSubmissionAlert);
const name = skillFormData.name;
- const email = skillFormData.email;
const submissionSummary = skillFormData.submissionSummary;
const documentOutline = skillFormData.documentOutline;
const response = await fetch('/api/local/pr/skill/', {
@@ -86,11 +83,11 @@ const Submit: React.FC = ({ disableAction, skillFormData, setActionGroupA
return;
}
- const result = await response.json();
+ await response.json();
const actionGroupAlertContent: ActionGroupAlertContent = {
title: 'Skill contribution submitted successfully!',
message: `Thank you for your contribution!`,
- url: `${result.html_url}`,
+ url: '/experimental/dashboard-local/',
success: true
};
setActionGroupAlertContent(actionGroupAlertContent);
diff --git a/src/components/Experimental/ContributeLocal/Skill/index.tsx b/src/components/Experimental/ContributeLocal/Skill/index.tsx
index 476ccfd2..32838497 100644
--- a/src/components/Experimental/ContributeLocal/Skill/index.tsx
+++ b/src/components/Experimental/ContributeLocal/Skill/index.tsx
@@ -441,7 +441,7 @@ export const SkillFormLocal: React.FunctionComponent = ({ skillE
actionGroupAlertContent.url &&
actionGroupAlertContent.url.trim().length > 0 && (
- View your pull request
+ View your new branch
)}
@@ -465,7 +465,7 @@ export const SkillFormLocal: React.FunctionComponent = ({ skillE
disableAction={disableAction}
skillFormData={skillFormData}
setActionGroupAlertContent={setActionGroupAlertContent}
- githubUsername={githubUsername}
+ email={email}
resetForm={resetForm}
/>
)}