diff --git a/.env b/.env
index f256c63d..a8331286 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,6 @@
+VUE_APP_JIRA_CLIENT_ID=3tPI6y3UgOxjUUVd2ELL3mhZr6cGAatt
+VUE_APP_JIRA_CLIENT_SECRET=ATOAHCxhe5I4NKjvZo_hnzLSS6N038CmfyUsdnoXHCI0e8el_dY_xrFmMFJVHMAfa14d8502F3BF
VUE_APP_I18N_LOCALE=en
VUE_APP_I18N_FALLBACK_LOCALE=en
+
+VUE_APP_SERVER_PORT=64064
diff --git a/README.md b/README.md
index b3bab0c9..a38445a0 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,7 @@ If you have an idea about how we can better meet that goal, please let us know b
If you looking to contribute - please checkout the [contributing guidelines](docs/CONTRIBUTING.md).
-If you're just looking to take it for a spin, check out the pre-built [packages](https://github.com/dacoaster/yattie/releases) and find the one built for your platform. Can't find the platform you're looking for? Let us know you'd like a new one supported at the [feature requests](https://features.yattie.ai) page!
+If you're just looking to take it for a spin, check out the pre-built [packages](https://yatt.ai/downloads) and find the one built for your platform. Can't find the platform you're looking for? Let us know you'd like a new one supported at the [feature requests](https://features.yattie.ai) page!
### Installation
@@ -88,7 +88,7 @@ TODO - This section could use some love (and screenshots!)
## Usage
-TODO - This section could use some love (and screenshots!)
+Check out our [docs](https://docs.yattie.ai).
## Roadmap
diff --git a/package.json b/package.json
index 310009d0..83dc7296 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,7 @@
{
"name": "yattie",
- "version": "0.4.3",
+ "version": "0.5.0",
"private": true,
- "main": "background.js",
"engines": {
"npm": ">=8.0.0 <9.0.0",
"node": ">=16.0.0 <17.0.0"
@@ -15,32 +14,41 @@
"license": "GPLv3",
"scripts": {
"serve": "vue-cli-service serve",
+ "build": "vue-cli-service electron:build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint",
"dev": "vue-cli-service electron:serve",
- "build": "vue-cli-service electron:build",
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"publish": "vue-cli-service electron:build -p always"
},
+ "main": "background.js",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.2.1",
"@johmun/vue-tags-input": "^2.1.0",
"@peepi/vuetify-tiptap": "^1.2.3",
- "tui-image-editor": "^3.15.3",
"adm-zip": "^0.5.9",
+ "axios": "^1.2.1",
+ "body-parser": "^1.20.1",
+ "child_process": "^1.0.2",
+ "client-oauth2": "^4.3.3",
"core-js": "^3.8.3",
+ "cors": "^2.8.5",
"d3": "^5.14.2",
"dayjs": "^1.11.5",
"detect-file-type": "^0.2.8",
+ "express": "^4.18.2",
"extract-zip": "^2.0.1",
"ffmpeg-static": "^5.1.0",
"ffprobe-static": "^3.1.0",
"fluent-ffmpeg": "^2.1.2",
+ "form-data": "^4.0.0",
"lodash": "^4.17.21",
+ "open": "^8.4.0",
"simple-json-db": "^2.0.0",
"sinon": "^15.0.0",
+ "tui-image-editor": "^3.15.3",
"uuid": "3.3.3",
"v-mask": "^2.3.0",
"vue": "^2.6.14",
diff --git a/public/server/Server.js b/public/server/Server.js
new file mode 100644
index 00000000..30689d13
--- /dev/null
+++ b/public/server/Server.js
@@ -0,0 +1,36 @@
+const express = require("express");
+const bodyParser = require("body-parser");
+const cors = require("cors");
+const path = require("path");
+
+const app = express();
+const port = process.env.VUE_APP_SERVER_PORT || 64064;
+
+const corsOptions = {
+ origin: `http://localhost:${port}`,
+};
+
+app.use(cors());
+app.use(cors(corsOptions));
+
+// parse requests of content-type - application/json
+app.use(bodyParser.json());
+
+// parse requests of content-type - application/x-www-form-urlencoded
+app.use(bodyParser.urlencoded({ extended: true }));
+
+// path
+app.use(express.static(path.join(__dirname, "images")));
+
+app.disable("x-powered-by");
+
+// modules
+require("./modules/JiraUtility")(app);
+
+try {
+ app.listen(port, () => {
+ console.log(`OAuth redirect server running at http://localhost:${port}`);
+ });
+} catch (err) {
+ console.log(err);
+}
diff --git a/public/server/images/favicon.ico b/public/server/images/favicon.ico
new file mode 100644
index 00000000..a914da5e
Binary files /dev/null and b/public/server/images/favicon.ico differ
diff --git a/public/server/images/logo.png b/public/server/images/logo.png
new file mode 100644
index 00000000..bc26babd
Binary files /dev/null and b/public/server/images/logo.png differ
diff --git a/public/server/jira.html b/public/server/jira.html
new file mode 100644
index 00000000..656c7918
--- /dev/null
+++ b/public/server/jira.html
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+ YATTIE
+
+
+
+
+
+
+
+
+
+
+
+
+
+ You are now connected to JIRA! You can close this window and go back to the YATTIE app to get started
+ testing.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/server/modules/JiraUtility.js b/public/server/modules/JiraUtility.js
new file mode 100644
index 00000000..f0176fe1
--- /dev/null
+++ b/public/server/modules/JiraUtility.js
@@ -0,0 +1,41 @@
+const open = require("open");
+const path = require("path");
+const ClientOAuth2 = require("client-oauth2");
+const port = process.env.VUE_APP_SERVER_PORT;
+const auth = new ClientOAuth2({
+ clientId: process.env.VUE_APP_JIRA_CLIENT_ID,
+ clientSecret: process.env.VUE_APP_JIRA_CLIENT_SECRET,
+ accessTokenUri: "https://auth.atlassian.com/oauth/token",
+ authorizationUri:
+ "https://auth.atlassian.com/authorize?audience=api.atlassian.com&prompt=consent",
+ redirectUri: `http://localhost:${port}/oauth2/atlassian/callback`,
+ scopes: ["read:jira-work", "write:jira-work", "read:me", "offline_access"],
+});
+module.exports = (app) => {
+ app.use((req, res, next) => {
+ res.header(
+ "Access-Control-Allow-Headers",
+ "x-access-token, Origin, Content-Type, Accept"
+ );
+ next();
+ });
+
+ app.get("/oauth2/atlassian", (req, res) => {
+ var uri = auth.code.getUri();
+ open(uri, (err) => {
+ console.log(err);
+ });
+ return res.send(uri);
+ });
+ app.get("/oauth2/atlassian/callback", (req, res) => {
+ auth.code.getToken(req.originalUrl).then((user) => {
+ const data = {
+ type: "jira",
+ accessToken: user.data,
+ };
+ process.send(data);
+
+ return res.sendFile(path.join(__dirname, "../jira.html"));
+ });
+ });
+};
diff --git a/src/assets/icon/bug.svg b/src/assets/icon/bug.svg
new file mode 100644
index 00000000..b937a8b2
--- /dev/null
+++ b/src/assets/icon/bug.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icon/list.svg b/src/assets/icon/list.svg
new file mode 100644
index 00000000..361c224f
--- /dev/null
+++ b/src/assets/icon/list.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/background.js b/src/background.js
index 4c6ae817..32ef2a37 100644
--- a/src/background.js
+++ b/src/background.js
@@ -32,8 +32,8 @@ async function createWindow() {
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
- if (isDevelopment){
- win.webContents.openDevTools();
+ if (isDevelopment) {
+ win.webContents.openDevTools();
}
} else {
createProtocol("app");
diff --git a/src/components/ControlPanel.vue b/src/components/ControlPanel.vue
index 3f6b4c0d..840e305b 100644
--- a/src/components/ControlPanel.vue
+++ b/src/components/ControlPanel.vue
@@ -58,7 +58,7 @@
color="primary"
@click="deleteConfirmDialog = true"
>
- mdi-delete Delete
+ mdi-delete {{ $tc("caption.delete", 1) }}
@@ -70,9 +70,17 @@
color="white"
@click="exportItems"
>
- mdi-download Export
+ mdi-download {{ $tc("caption.export", 1) }}
+
+
+
@@ -482,12 +490,16 @@ import DurationConfirmDialog from "./dialogs/DurationConfirmDialog.vue";
import AudioErrorDialog from "./dialogs/AudioErrorDialog.vue";
import EndSessionDialog from "./dialogs/EndSessionDialog.vue";
import MinimizeControlWrapper from "../components/MinimizeControlWrapper.vue";
+
+import JiraExportSession from "./jira/JiraExportSession";
+
import {
IPC_HANDLERS,
IPC_FUNCTIONS,
IPC_BIND_KEYS,
SESSION_STATUSES,
VIDEO_RESOLUTION,
+ STATUSES,
} from "../modules/constants";
import {
DEFAULT_MAP_NODES,
@@ -515,6 +527,7 @@ export default {
AudioErrorDialog,
EndSessionDialog,
MinimizeControlWrapper,
+ JiraExportSession,
},
props: {
items: {
@@ -529,6 +542,14 @@ export default {
type: Object,
default: () => {},
},
+ credentialItem: {
+ type: Object,
+ default: () => {},
+ },
+ isAuthenticated: {
+ type: Boolean,
+ default: () => false,
+ },
checkedStatusOfPreSessionTask: {
type: Boolean,
default: () => false,
@@ -560,6 +581,12 @@ export default {
configItem: function (newValue) {
this.config = newValue;
},
+ credentialItem: function (newValue) {
+ this.credential = newValue;
+ },
+ isAuthenticated: function (newValue) {
+ this.checkAuth = newValue;
+ },
"$store.state.status": {
deep: true,
handler(newValue) {
@@ -626,6 +653,8 @@ export default {
sourceId: this.srcId,
itemLists: this.items,
config: this.configItem,
+ credential: this.credentialItem,
+ checkAuth: this.isAuthenticated,
audioDevices: [],
loaded: false,
status: this.$store.state.status,
@@ -997,7 +1026,6 @@ export default {
this.durationConfirmDialog = false;
this.status = SESSION_STATUSES.PROCEED;
this.changeSessionStatus(SESSION_STATUSES.PROCEED);
- console.log("start interval-1");
this.startInterval();
},
updateStatus(value) {
@@ -1384,7 +1412,6 @@ export default {
duration: this.duration,
sourceId: this.sourceId,
};
- // console.log(data);
localStorage.setItem("state-data", JSON.stringify(data));
if (!window.ipc) return;
await window.ipc.invoke(IPC_HANDLERS.WINDOW, {
@@ -1431,16 +1458,13 @@ export default {
path: this.$route.path,
};
if (!window.ipc) return;
- await window.ipc
- .invoke(IPC_HANDLERS.FILE_SYSTEM, {
- func: IPC_FUNCTIONS.SAVE_SESSION,
- data: data,
- })
- .then(() => {
- if (callback) {
- callback();
- }
- });
+ const { status } = await window.ipc.invoke(IPC_HANDLERS.FILE_SYSTEM, {
+ func: IPC_FUNCTIONS.SAVE_SESSION,
+ data: data,
+ });
+ if (status === STATUSES.SUCCESS && callback) {
+ callback();
+ }
},
discardSession(callback = null) {
this.newSessionDialog = false;
@@ -1449,7 +1473,6 @@ export default {
}
},
async clearSession() {
- console.log("clear session");
this.$root.$emit("new-session");
this.status = SESSION_STATUSES.PENDING;
@@ -1475,10 +1498,10 @@ export default {
},
});
this.stopInterval();
- // const currentPath = this.$router.history.current.path;
- // if (currentPath !== "/main") {
- // this.$router.push({ path: "/main" });
- // }
+ const currentPath = this.$router.history.current.path;
+ if (currentPath !== "/main") {
+ this.$router.push({ path: "/main" });
+ }
});
},
async resetSession() {
diff --git a/src/components/ExportPanel.vue b/src/components/ExportPanel.vue
index 7dfd92b2..4b3245c5 100644
--- a/src/components/ExportPanel.vue
+++ b/src/components/ExportPanel.vue
@@ -2,30 +2,84 @@
-
+
{{ $tc("caption.export_session_report", 1) }}
+
+
+
diff --git a/src/components/MinimizeControlWrapper.vue b/src/components/MinimizeControlWrapper.vue
index e9e22a36..a41d31fc 100644
--- a/src/components/MinimizeControlWrapper.vue
+++ b/src/components/MinimizeControlWrapper.vue
@@ -251,7 +251,6 @@ export default {
y: event.screenY,
};
- console.log(deltaPos);
if (!window.ipc) return;
window.ipc.invoke(IPC_HANDLERS.WINDOW, {
diff --git a/src/components/__tests__/NotesWrapper.spec.js b/src/components/__tests__/NotesWrapper.spec.js
index 6dd44244..cb6c4ba8 100644
--- a/src/components/__tests__/NotesWrapper.spec.js
+++ b/src/components/__tests__/NotesWrapper.spec.js
@@ -19,6 +19,6 @@ describe("NotesWrapper.vue", () => {
},
vuetify,
});
-
+ expect(wrapper.find(".content").exists()).toBe(true);
});
});
diff --git a/src/components/__tests__/TestWrapper.spec.js b/src/components/__tests__/TestWrapper.spec.js
index d9e35828..b217fb52 100644
--- a/src/components/__tests__/TestWrapper.spec.js
+++ b/src/components/__tests__/TestWrapper.spec.js
@@ -55,8 +55,7 @@ describe("TestWrapper.vue", () => {
const charterTabWrapper = wrapper.find(".charter-tab");
expect(charterTabWrapper.findComponent(MindmapEditor).exists()).toBe(false);
-
- const precondWrapper = wrapper.find(".pre-cond");
+ expect(wrapper.find(".pre-cond").exists()).toBe(true);
});
test('change the value of "Time limit" input box', async () => {
diff --git a/src/components/authentication/SigninWrapper.vue b/src/components/authentication/SigninWrapper.vue
index bf3675a8..48eaf10b 100644
--- a/src/components/authentication/SigninWrapper.vue
+++ b/src/components/authentication/SigninWrapper.vue
@@ -10,13 +10,27 @@
+
+
+
-
+
+
{{ $tc("caption.signin_jira", 1) }}
@@ -50,29 +64,181 @@
class="text-capitalize pa-0 signup-btn"
color="primary"
plain
- to="/authentication/signup2"
+ to="/authentication/signupMain"
>
{{ $tc("caption.sign_up", 1) }}
-->
+
+ {{ snackBar.message }}
+
+
+ Close
+
+
+
@@ -112,6 +278,7 @@ export default {
margin-left: -50px;
}
.content {
+ position: relative;
background-color: #fff;
border-radius: 8px;
padding: 32px 40px;
@@ -168,4 +335,13 @@ export default {
font-weight: 500;
color: #6d28d9;
}
+
+.loading-wrapper {
+ position: absolute;
+ overflow: hidden;
+ z-index: 9999999;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
diff --git a/src/components/authentication/SignupHomeWrapper.vue b/src/components/authentication/SignupHomeWrapper.vue
index 0ec47daf..920bf7e5 100644
--- a/src/components/authentication/SignupHomeWrapper.vue
+++ b/src/components/authentication/SignupHomeWrapper.vue
@@ -5,7 +5,7 @@
-
+
{{ $t("message.signup_policy") }}
- {{ $tc("caption.term_data_policy", 1) }}
+ {{ $tc("caption.signup_term_data_policy", 1) }}
{{ $tc("caption.and", 1) }}
- {{ $tc("caption.cookie_policy", 1) }}
+ {{ $tc("caption.signup_cookie_policy", 1) }}
.
diff --git a/src/components/authentication/SignupMainWrapper.vue b/src/components/authentication/SignupMainWrapper.vue
index 432cea41..65aa41b8 100644
--- a/src/components/authentication/SignupMainWrapper.vue
+++ b/src/components/authentication/SignupMainWrapper.vue
@@ -10,13 +10,27 @@
+
+
+
{{ $tc("caption.signup_yattie", 1) }}
-
+
{{ $tc("caption.signup_jira", 1) }}
@@ -26,12 +40,12 @@
- {{ $tc("caption.singup_qtest", 1) }}
+ {{ $tc("caption.signup_qtest", 1) }}
- {{ $tc("caption.signup_pratictest", 1) }}
+ {{ $tc("caption.signup_practitest", 1) }}
@@ -49,7 +63,7 @@
fill
small
block
- to="/authentication/signup3"
+ to="/authentication/signupYattie"
>
{{ $tc("caption.sign_up", 1) }}
@@ -80,29 +94,165 @@
{{ $t("message.signup_policy") }}
- {{ $tc("caption.term_data_policy", 1) }}
+ {{ $tc("caption.signup_term_data_policy", 1) }}
{{ $tc("caption.and", 1) }}
- {{ $tc("caption.cookie_policy", 1) }}
+ {{ $tc("caption.signup_cookie_policy", 1) }}
.
+
+ {{ snackBar.message }}
+
+
+ Close
+
+
+
diff --git a/src/components/authentication/SignupYattieWrapper.vue b/src/components/authentication/SignupYattieWrapper.vue
index 32acdc58..d632a18f 100644
--- a/src/components/authentication/SignupYattieWrapper.vue
+++ b/src/components/authentication/SignupYattieWrapper.vue
@@ -4,7 +4,7 @@
mdi-chevron-left
{{ $tc("caption.back", 1) }}
@@ -79,12 +79,12 @@
{{ $t("message.signup_policy") }}
- {{ $tc("caption.term_data_policy", 1) }}
+ {{ $tc("caption.signup_term_data_policy", 1) }}
{{ $tc("caption.and", 1) }}
- {{ $tc("caption.cookie_policy", 1) }}
+ {{ $tc("caption.signup_cookie_policy", 1) }}
.
diff --git a/src/components/dialogs/NoteDialog.vue b/src/components/dialogs/NoteDialog.vue
index 04857f8f..aad1e83f 100644
--- a/src/components/dialogs/NoteDialog.vue
+++ b/src/components/dialogs/NoteDialog.vue
@@ -1,5 +1,12 @@
-
+
diff --git a/src/components/dialogs/SummaryDialog.vue b/src/components/dialogs/SummaryDialog.vue
index 18e8db1f..884e4f61 100644
--- a/src/components/dialogs/SummaryDialog.vue
+++ b/src/components/dialogs/SummaryDialog.vue
@@ -1,5 +1,12 @@
-
+
diff --git a/src/components/jira/JiraExportSession.vue b/src/components/jira/JiraExportSession.vue
new file mode 100644
index 00000000..125ee701
--- /dev/null
+++ b/src/components/jira/JiraExportSession.vue
@@ -0,0 +1,359 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+ {{ $tc("caption.export_item_to_jira", 1) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $tc("caption.discard", 1) }}
+
+
+
+
+ {{ $tc("caption.export", 1) }}
+
+
+
+
+
+
+
+
+ {{ snackBar.message }}
+
+
+ Close
+
+
+
+
+
+
+
+
+
diff --git a/src/components/settings/GeneralTab.vue b/src/components/settings/GeneralTab.vue
index 21af9d96..d3e566b3 100644
--- a/src/components/settings/GeneralTab.vue
+++ b/src/components/settings/GeneralTab.vue
@@ -183,8 +183,6 @@ export default {
color: function (newValue, oldValue) {
if (newValue === oldValue) return;
- console.log(newValue, oldValue);
-
this.setting.defaultColor = newValue.hexa;
this.handleConfig();
},
diff --git a/src/integrations/IntegrationHelpers.js b/src/integrations/IntegrationHelpers.js
new file mode 100644
index 00000000..d1f20b43
--- /dev/null
+++ b/src/integrations/IntegrationHelpers.js
@@ -0,0 +1,91 @@
+import dayjs from "dayjs";
+import { IPC_HANDLERS, IPC_FUNCTIONS } from "../modules/constants";
+import ClientOAuth2 from "client-oauth2";
+
+export default {
+ checkAuth(credential) {
+ if (Object.keys(credential).length) {
+ if (this.isTokenExpired(credential)) {
+ const access_token = this.refreshAccessToken(credential);
+ if (access_token) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ },
+ getAccessToken(credential) {
+ let access_token = null;
+ if (Object.keys(credential).length) {
+ if (this.isTokenExpired(credential)) {
+ access_token = this.refreshAccessToken(credential);
+ } else {
+ const type = credential.type;
+ access_token = credential[type].access_token;
+ }
+ }
+ return access_token;
+ },
+ isTokenExpired(data) {
+ const type = data.type;
+ if (type) {
+ const auth = data[type];
+ const now = new Date();
+ const loggedAt = new Date(auth.loggedAt);
+ const expireIn = auth.expires_in;
+ const duration = (now.getTime() - loggedAt.getTime()) / 1000;
+ if (duration > expireIn) {
+ return true;
+ }
+ return false;
+ }
+ return true;
+ },
+ refreshAccessToken(credential) {
+ return new Promise(function (resolve, reject) {
+ // TODO - declare this once and import it
+ // TOOD - Move all JIRA-specific items to JIRA integration module
+ const auth = new ClientOAuth2({
+ clientId: process.env.VUE_APP_JIRA_CLIENT_ID,
+ clientSecret: process.env.VUE_APP_JIRA_CLIENT_SECRET,
+ accessTokenUri: "https://auth.atlassian.com/oauth/token",
+ authorizationUri:
+ "https://auth.atlassian.com/authorize?audience=api.atlassian.com&prompt=consent",
+ redirectUri: `http://localhost:${process.env.VUE_APP_SERVER_PORT}/oauth2/atlassian/callback`,
+ scopes: [
+ "read:jira-work",
+ "write:jira-work",
+ "read:me",
+ "offline_access",
+ ],
+ });
+ if (Object.keys(credential).length && credential.type) {
+ let token = auth.createToken(
+ credential[credential.type].access_token,
+ credential[credential.type].refresh_token
+ );
+ token.refresh().then((data) => {
+ credential[credential.type].access_token = data.access_token;
+ credential[credential.type].expires_in = data.expires_in;
+ credential[credential.type].token_type = data.token_type;
+ credential[credential.type].refresh_token = data.refresh_token;
+ credential[credential.type].scope = data.scope;
+ credential[credential.type].loggedAt = dayjs().format(
+ "MM/DD/YYYY HH:mm:ss"
+ );
+
+ window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ func: IPC_FUNCTIONS.UPDATE_CREDENTIAL,
+ data: credential,
+ });
+ return resolve(data.access_token);
+ });
+ } else {
+ return reject();
+ }
+ });
+ },
+};
diff --git a/src/layouts/Default.vue b/src/layouts/Default.vue
index 1ac807dd..dbdd3004 100644
--- a/src/layouts/Default.vue
+++ b/src/layouts/Default.vue
@@ -2,7 +2,7 @@
-
+
@@ -14,8 +14,19 @@ export default {
data: () => ({
overlay: false,
+ config: {},
+ credential: {},
+ checkAuth: false,
}),
+ created() {
+ this.getConfig();
+ this.getCredential();
+ },
mounted() {
+ this.$root.$on("overlay", (value) => {
+ this.overlay = value;
+ });
+
if (!window.ipc) return;
window.ipc.on("OPEN_CHILD_WINDOW", () => {
@@ -81,6 +92,33 @@ export default {
this.$vuetify.theme.dark = isDarkMode;
localStorage.setItem("isDarkMode", isDarkMode);
});
+
+ window.ipc.on("CONFIG_CHANGE", () => {
+ this.getConfig();
+ });
+
+ window.ipc.on("CREDENTIAL_CHANGE", () => {
+ this.getCredential();
+ });
+ },
+ methods: {
+ getConfig() {
+ if (!window.ipc) return;
+ window.ipc
+ .invoke(IPC_HANDLERS.DATABASE, { func: IPC_FUNCTIONS.GET_CONFIG })
+ .then((result) => {
+ this.config = result;
+ });
+ },
+ getCredential() {
+ if (!window.ipc) return;
+ window.ipc
+ .invoke(IPC_HANDLERS.DATABASE, { func: IPC_FUNCTIONS.GET_CREDENTIAL })
+ .then((result) => {
+ this.credential = result;
+ this.checkAuth = this.$integrationHelpers.checkAuth(this.credential);
+ });
+ },
},
};
diff --git a/src/locales/en.json b/src/locales/en.json
index f4afa9b1..c6a32d31 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -29,6 +29,7 @@
"checklist": "Checklists",
"report": "Reports",
"support": "Support",
+ "signup_test_app": "Sign up to test apps",
"back": "Back",
"sign_in": "Sign in",
"signin_yattie": "Sign in with YATT",
@@ -42,6 +43,7 @@
"and": "And",
"signup_yattie": "Sign up with YATT",
"signup_jira": "Sign up with JIRA",
+ "signup_testrail": "Sign up with TestRail",
"signup_qtest": "Sign up with qTest",
"signup_practitest": "Sign up with PractiTest",
"skip_sign_up": "Skip sign up",
@@ -98,6 +100,7 @@
"start_session": "Start session",
"start_quick_test": "Start Quick Test",
"export": "Export",
+ "export_to_jira": "Export to JIRA",
"resume_session": "Resume session",
"save_session": "Save session",
"clear_session": "Clear session",
@@ -116,6 +119,7 @@
"save_current_progress": "Save current progress",
"error_recording_audio": "Error recording audio",
"export_session_report": "Export session report",
+ "export_session_report_to_jira": "Export session report to JIRA",
"optimizing": "Optimizing",
"start": "Start",
"end": "End",
@@ -130,6 +134,8 @@
"minute": "Minutes",
"workspace": "Workspace",
"change_recording_target": "Change recording target",
+ "export_item_to_jira": "Export Item to JIRA",
+ "issues": "ISSUES",
"quick_test": "@.capitalize:quick test",
"map_node": "Map Node",
"new_exploratory_session": "New Exploratory Session",
diff --git a/src/main.js b/src/main.js
index 03ce2b5c..cf2aaa3d 100644
--- a/src/main.js
+++ b/src/main.js
@@ -4,6 +4,7 @@ import vuetify from "./plugins/vuetify";
import VTiptap from "@peepi/vuetify-tiptap";
import router from "./router";
import store from "./store";
+import integrationHelpers from "./integrations/IntegrationHelpers";
import DefaultLayout from "./layouts/Default.vue";
import MinimizeLayout from "./layouts/Minimize.vue";
@@ -19,6 +20,14 @@ Vue.component("minimize-layout", MinimizeLayout);
Vue.config.productionTip = false;
+const plugins = {
+ install() {
+ Vue.integrationHelpers = integrationHelpers;
+ Vue.prototype.$integrationHelpers = integrationHelpers;
+ },
+};
+Vue.use(plugins);
+
new Vue({
vuetify,
router,
diff --git a/src/modules/CaptureUtility.js b/src/modules/CaptureUtility.js
index 52b63fab..51504929 100644
--- a/src/modules/CaptureUtility.js
+++ b/src/modules/CaptureUtility.js
@@ -245,7 +245,7 @@ module.exports.uploadEvidence = async () => {
});
};
-module.exports.dropFile = async (data) => {
+module.exports.dropFile = async (data) => {
const fileName = data.name;
const filePath = path.join(configDir, "sessions", "userMedia", fileName);
diff --git a/src/modules/DatabaseUtility.js b/src/modules/DatabaseUtility.js
index aaf64a5e..c00aa51d 100644
--- a/src/modules/DatabaseUtility.js
+++ b/src/modules/DatabaseUtility.js
@@ -10,7 +10,7 @@ const jsonDbConfig = {
jsonSpaces: 2,
};
-let metaDb, configDb, dataDb;
+let metaDb, configDb, credentialDb, dataDb;
let browserWindow;
module.exports.initializeSession = () => {
@@ -28,6 +28,7 @@ module.exports.initializeSession = () => {
const meta = {
configPath: path.join(configDir, "config.json"),
+ credentialPath: path.join(configDir, "credential.json"),
dataPath: path.join(configDir, "data.json"),
};
@@ -111,8 +112,36 @@ module.exports.initializeSession = () => {
};
metaDb = new JSONdb(path.join(configDir, "meta.json"), jsonDbConfig);
- configDb = new JSONdb(path.join(configDir, "config.json"), jsonDbConfig);
- dataDb = new JSONdb(path.join(configDir, "data.json"), jsonDbConfig);
+ if (metaDb.has("meta")) {
+ const metaPath = metaDb.get("meta");
+ if (metaPath.configPath) {
+ configDb = new JSONdb(metaPath.configPath, jsonDbConfig);
+ } else {
+ configDb = new JSONdb(path.join(configDir, "config.json"), jsonDbConfig);
+ }
+
+ if (metaPath.credentialPath) {
+ credentialDb = new JSONdb(metaPath.credentialPath, jsonDbConfig);
+ } else {
+ credentialDb = new JSONdb(
+ path.join(configDir, "credential.json"),
+ jsonDbConfig
+ );
+ }
+
+ if (metaPath.configPath) {
+ dataDb = new JSONdb(metaPath.dataPath, jsonDbConfig);
+ } else {
+ dataDb = new JSONdb(path.join(configDir, "data.json"), jsonDbConfig);
+ }
+ } else {
+ configDb = new JSONdb(path.join(configDir, "config.json"), jsonDbConfig);
+ credentialDb = new JSONdb(
+ path.join(configDir, "credential.json"),
+ jsonDbConfig
+ );
+ dataDb = new JSONdb(path.join(configDir, "data.json"), jsonDbConfig);
+ }
try {
if (!metaDb.has("meta")) {
@@ -122,6 +151,10 @@ module.exports.initializeSession = () => {
configDb.set("config", config);
}
+ if (!credentialDb.has("credential")) {
+ credentialDb.set("credential", {});
+ }
+
dataDb.set("items", []);
dataDb.set("notes", {
content: "",
@@ -229,6 +262,24 @@ module.exports.updateConfig = (config) => {
}
};
+module.exports.getCredential = () => {
+ try {
+ return credentialDb.get("credential");
+ } catch (error) {
+ return {};
+ }
+};
+
+module.exports.updateCredential = (credential) => {
+ try {
+ credentialDb.set("credential", credential);
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("CREDENTIAL_CHANGE");
+ } catch (error) {
+ console.log(error);
+ }
+};
+
module.exports.getMetaData = () => {
try {
return metaDb.get("meta");
diff --git a/src/modules/FileSystemUtility.js b/src/modules/FileSystemUtility.js
index 8e21e1ac..2dfef7f4 100644
--- a/src/modules/FileSystemUtility.js
+++ b/src/modules/FileSystemUtility.js
@@ -1,4 +1,4 @@
-const { app, remote, dialog, BrowserWindow } = require("electron");
+const { app, remote, dialog, shell, BrowserWindow } = require("electron");
const path = require("path");
const fs = require("fs");
const AdmZip = require("adm-zip");
@@ -116,7 +116,10 @@ module.exports.saveSession = async (data) => {
});
if (!filePath) {
- return Promise.resolve({ status: "canceled", message: "No path selected" });
+ return Promise.resolve({
+ status: STATUSES.ERROR,
+ message: "No path selected",
+ });
}
return new Promise(function (resolve) {
@@ -365,3 +368,8 @@ module.exports.dragItem = (event, data) => {
icon: iconPath,
});
};
+
+module.exports.openExternalLink = async (url = "") => {
+ if (url === "") return;
+ return shell.openExternal(url);
+};
diff --git a/src/modules/IpcHandlers.js b/src/modules/IpcHandlers.js
index 5b141e03..2e273f40 100644
--- a/src/modules/IpcHandlers.js
+++ b/src/modules/IpcHandlers.js
@@ -5,9 +5,9 @@ const databaseUtility = require("./DatabaseUtility");
const fileSystemUtility = require("./FileSystemUtility");
const menuUtility = require("./MenuUtility");
const windowUtility = require("./WindowUtility");
+const serverUtility = require("./ServerUtility");
ipcMain.handle(IPC_HANDLERS.CAPTURE, async (event, args) => {
- console.log(args);
switch (args.func) {
case IPC_FUNCTIONS.GET_MEDIA_SOURCE:
return captureUtility.getMediaSource();
@@ -73,7 +73,6 @@ ipcMain.handle(IPC_HANDLERS.WINDOW, async (event, args) => {
case IPC_FUNCTIONS.CLOSE_MODAL_WINDOW:
return windowUtility.closeModalWindow(args.data);
case IPC_FUNCTIONS.OPEN_NOTES_WINDOW:
- console.log("handler", args.data);
return windowUtility.openNotesWindow(args.data);
case IPC_FUNCTIONS.CLOSE_NOTES_WINDOW:
return windowUtility.closeNotesWindow();
@@ -102,6 +101,10 @@ ipcMain.handle(IPC_HANDLERS.DATABASE, async (event, args) => {
return databaseUtility.getConfig(args.data);
case IPC_FUNCTIONS.UPDATE_CONFIG:
return databaseUtility.updateConfig(args.data);
+ case IPC_FUNCTIONS.GET_CREDENTIAL:
+ return databaseUtility.getCredential(args.data);
+ case IPC_FUNCTIONS.UPDATE_CREDENTIAL:
+ return databaseUtility.updateCredential(args.data);
case IPC_FUNCTIONS.GET_METADATA:
return databaseUtility.getMetaData(args.data);
case IPC_FUNCTIONS.UPDATE_METADATA:
@@ -133,6 +136,8 @@ ipcMain.handle(IPC_HANDLERS.FILE_SYSTEM, async (event, args) => {
return fileSystemUtility.openConfigFile(args.data);
case IPC_FUNCTIONS.DRAG_ITEM:
return fileSystemUtility.dragItem(event, args.data);
+ case IPC_FUNCTIONS.OPEN_EXTERNAL_LINK:
+ return fileSystemUtility.openExternalLink(args.data);
default:
return null;
}
@@ -144,3 +149,12 @@ ipcMain.handle(IPC_HANDLERS.MENU, async (event, args) => {
return menuUtility.changeMenuItemStatus(args.data);
}
});
+
+ipcMain.handle(IPC_HANDLERS.SERVER, async (event, args) => {
+ switch (args.func) {
+ case IPC_FUNCTIONS.START_SERVER:
+ return serverUtility.startServer(args.data);
+ case IPC_FUNCTIONS.STOP_SERVER:
+ return serverUtility.stopServer(args.data);
+ }
+});
diff --git a/src/modules/ServerUtility.js b/src/modules/ServerUtility.js
new file mode 100644
index 00000000..eaae14d4
--- /dev/null
+++ b/src/modules/ServerUtility.js
@@ -0,0 +1,35 @@
+const { fork } = require("child_process");
+const path = require("path");
+const browserUtility = require("./BrowserWindowUtility");
+
+let serverProcess = null;
+
+module.exports.startServer = async () => {
+ const isDevelopment = process.env.NODE_ENV !== "production";
+ serverProcess = fork(
+ isDevelopment
+ ? path.resolve(__dirname, "../public/server/Server.js")
+ : path.resolve(__dirname, "./server/Server.js")
+ );
+ const browserWindow = browserUtility.getBrowserWindow();
+
+ serverProcess.on("message", (data) => {
+ switch (data.type) {
+ case "jira":
+ browserWindow.webContents.send("JIRA_LOGIN", data.accessToken);
+ break;
+ default:
+ break;
+ }
+ });
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ // TODO: The above line is not idea, but the express server doesn't seem to
+ // send a 'spawn' event, so the below line doesn't work.
+ //await once(serverProcess, 'spawn');
+};
+
+module.exports.stopServer = () => {
+ if (serverProcess) {
+ serverProcess.kill();
+ }
+};
diff --git a/src/modules/constants.js b/src/modules/constants.js
index 4e6fd8ff..d9ca13f4 100644
--- a/src/modules/constants.js
+++ b/src/modules/constants.js
@@ -5,6 +5,7 @@ export const IPC_HANDLERS = {
STORE: "store",
MENU: "menu",
WINDOW: "window",
+ SERVER: "server",
};
export const IPC_FUNCTIONS = {
@@ -49,6 +50,8 @@ export const IPC_FUNCTIONS = {
GET_ITEM_BY_ID: "getItemById",
GET_CONFIG: "getConfig",
UPDATE_CONFIG: "updateConfig",
+ GET_CREDENTIAL: "getCredential",
+ UPDATE_CREDENTIAL: "updateCredential",
GET_METADATA: "getMetaData",
UPDATE_METADATA: "updateMetaData",
@@ -63,8 +66,12 @@ export const IPC_FUNCTIONS = {
EXPORT_SESSION: "exportSession",
OPEN_CONFIG_FILE: "openConfigFile",
DRAG_ITEM: "dragItem",
+ OPEN_EXTERNAL_LINK: "openExternalLink",
CHANGE_MENUITEM_STATUS: "changeMenuItemStatus",
+
+ START_SERVER: "startServer",
+ STOP_SERVER: "stopServer",
};
export const IPC_BIND_KEYS = {
diff --git a/src/views/AddSession.vue b/src/views/AddSession.vue
index 27264160..38aaa6ea 100644
--- a/src/views/AddSession.vue
+++ b/src/views/AddSession.vue
@@ -151,7 +151,6 @@ export default {
// set comment type by config
if (this.config.commentType && this.config.commentType !== "") {
- console.log("comment type:", this.config.commentType);
this.comment.type = this.config.commentType;
}
// set templates by config
@@ -213,7 +212,6 @@ export default {
this.item = value;
},
updateProcessing(value) {
- console.log("new processing:", value);
this.processing = value;
},
async handleDiscard() {
diff --git a/src/views/AuthenticationView.vue b/src/views/AuthenticationView.vue
index 7279cc46..7e04b427 100644
--- a/src/views/AuthenticationView.vue
+++ b/src/views/AuthenticationView.vue
@@ -1,17 +1,59 @@
-
+