@@ -50,7 +159,7 @@
class="subtitle-1 mb-4"
:style="{ color: currentTheme.secondary }"
>
- {{ $tc("caption.yatt", 1) }}
+ {{ $tc("caption.testfiesta", 1) }}
{{ $tc("caption.add_color", 1) }}
@@ -129,23 +238,15 @@
diff --git a/src/components/settings/GeneralTab.vue b/src/components/settings/GeneralTab.vue
index fc81d70b..c1ef5369 100644
--- a/src/components/settings/GeneralTab.vue
+++ b/src/components/settings/GeneralTab.vue
@@ -1,7 +1,7 @@
-
+
{{ $tc("caption.app_settings", 1) }}
-
+
+
+
+
+
+ {{ $tc("caption.clear_cache", 1) }}
+
+
+
+
+
+
- {{ $tc("caption.appearance", 1) }}
+ {{ $tc("caption.theme", 1) }}
import { TEXT_TYPES, STATUSES } from "@/modules/constants";
+import DeleteConfirmDialog from "../dialogs/DeleteConfirmDialog.vue";
import ShareOAuthDialog from "@/components/dialogs/ShareOAuthDialog.vue";
import { mapGetters } from "vuex";
export default {
name: "GeneralTab",
- components: { ShareOAuthDialog },
+ components: { ShareOAuthDialog, DeleteConfirmDialog },
props: {
metadata: {
type: Object,
default: () => {},
},
- configItem: {
- type: Object,
- default: () => {},
- },
},
watch: {
metadata: function (newValue) {
this.meta = newValue;
},
- configItem: function (newValue) {
- this.config = newValue;
- },
color: function (newValue, oldValue) {
if (newValue === oldValue) return;
@@ -237,6 +262,7 @@ export default {
},
computed: {
...mapGetters({
+ config: "config/fullConfig",
credentials: "auth/credentials",
}),
serverOAuthCredentials() {
@@ -271,16 +297,16 @@ export default {
},
data() {
return {
+ deleteConfirmDialog: false,
shareOauthDialog: false,
meta: this.metadata,
- config: this.configItem,
comment: {
type: "Comment",
content: "",
text: "",
},
menu: false,
- color: this.configItem.defaultColor,
+ color: this.config?.defaultColor,
commentTypes: Object.keys(TEXT_TYPES).filter(
(item) => item !== "Summary"
),
@@ -290,6 +316,11 @@ export default {
handleConfig() {
this.$emit("submit-config", this.config);
},
+ updateRetentionPeriod(value) {
+ let configToChange = structuredClone(this.config);
+ configToChange.cache.retentionPeriod = value ? parseInt(value) : value;
+ this.$emit("submit-config", configToChange);
+ },
async openConfigFile() {
if (this.$isElectron) {
const { status } = await this.$electronService.openConfigFile();
@@ -306,6 +337,19 @@ export default {
}
}
},
+ async deleteSessions() {
+ if (this.$isElectron) {
+ const { message } = await this.$electronService.deleteSession("all");
+ this.$root.$emit("set-snackbar", message);
+ }
+ this.deleteConfirmDialog = false;
+ },
+ handleDeleteConfirmDialog() {
+ this.deleteConfirmDialog = true;
+ setTimeout(() => {
+ this.$refs.deleteConfirmDialog.$refs.confirmBtn.$el.focus();
+ }, 100);
+ },
async showOAuthDialog() {
this.shareOauthDialog = true;
},
@@ -314,7 +358,6 @@ export default {
diff --git a/src/components/settings/HotkeysTab.vue b/src/components/settings/HotkeysTab.vue
index 0d0bc810..16760003 100755
--- a/src/components/settings/HotkeysTab.vue
+++ b/src/components/settings/HotkeysTab.vue
@@ -121,7 +121,6 @@ export default {
diff --git a/src/components/settings/SupportTab.vue b/src/components/settings/SupportTab.vue
index ff44312c..39ea5c8d 100644
--- a/src/components/settings/SupportTab.vue
+++ b/src/components/settings/SupportTab.vue
@@ -9,24 +9,15 @@
+
diff --git a/src/components/settings/TemplateTab.vue b/src/components/settings/TemplateTab.vue
index 6ca9d554..5d43fae6 100644
--- a/src/components/settings/TemplateTab.vue
+++ b/src/components/settings/TemplateTab.vue
@@ -12,7 +12,7 @@
{{ $tc("caption.apply_to", 1) }}
@@ -106,7 +106,6 @@
diff --git a/src/components/zephyr/ZephyrScaleExportSession.vue b/src/components/zephyr/ZephyrScaleExportSession.vue
new file mode 100644
index 00000000..bf9c759f
--- /dev/null
+++ b/src/components/zephyr/ZephyrScaleExportSession.vue
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
+ {{ $tc("caption.export_item_to_zephyr_scale", 1) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $tc("caption.discard", 1) }}
+
+
+
+
+ {{ $tc("caption.export", 1) }}
+
+
+
+
+
+
+
+
+ {{ snackBar.message }}
+
+
+ Close
+
+
+
+
+
+
+
+
+
diff --git a/src/components/zephyr/ZephyrSquadExportSession.vue b/src/components/zephyr/ZephyrSquadExportSession.vue
new file mode 100644
index 00000000..a380c813
--- /dev/null
+++ b/src/components/zephyr/ZephyrSquadExportSession.vue
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
+ {{ $tc("caption.export_item_to_zephyr_squad", 1) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $tc("caption.discard", 1) }}
+
+
+
+
+ {{ $tc("caption.export", 1) }}
+
+
+
+
+
+
+
+
+ {{ snackBar.message }}
+
+
+ Close
+
+
+
+
+
+
+
+
+
diff --git a/src/helpers/WebHelpers.js b/src/helpers/WebHelpers.js
new file mode 100644
index 00000000..1196c798
--- /dev/null
+++ b/src/helpers/WebHelpers.js
@@ -0,0 +1,180 @@
+const { DEFAULT_FILE_TYPES } = require("@/modules/constants");
+const uuidv4 = require("uuid");
+
+module.exports.createImageForWeb = (url) => {
+ const fileType = DEFAULT_FILE_TYPES["image"].type;
+ const { stepID, attachmentID, fileName } = generateIDAndName("image");
+
+ const base64Response = atob(url.split(",")[1]);
+ const binaryData = new Uint8Array(base64Response.length);
+ for (let i = 0; i < base64Response.length; i++) {
+ binaryData[i] = base64Response.charCodeAt(i);
+ }
+ let blob = new Blob([binaryData], { type: fileType });
+
+ const filePath = URL.createObjectURL(blob);
+ const fileSize = blob.size;
+
+ return {
+ item: {
+ stepID,
+ attachmentID,
+ fileName,
+ filePath,
+ fileSize,
+ fileType,
+ },
+ };
+};
+
+module.exports.updateImageForWeb = ({ item, url }) => {
+ const { fileName } = item.fileName
+ ? { fileName: item.fileName }
+ : generateIDAndName("image");
+ const base64Response = atob(url.split(",")[1]);
+ const binaryData = new Uint8Array(base64Response.length);
+ for (let i = 0; i < base64Response.length; i++) {
+ binaryData[i] = base64Response.charCodeAt(i);
+ }
+ let blob = new Blob([binaryData], { type: item.fileType });
+
+ const filePath = URL.createObjectURL(blob);
+ const fileSize = blob.size;
+
+ return {
+ item: {
+ fileName,
+ filePath,
+ fileSize,
+ },
+ };
+};
+
+module.exports.createMindmapImageForWeb = ({ item, url }) => {
+ const { fileName } = item.fileName
+ ? { fileName: item.fileName }
+ : generateIDAndName("mindmap");
+ const base64Response = atob(url.split(",")[1]);
+ const binaryData = new Uint8Array(base64Response.length);
+ for (let i = 0; i < base64Response.length; i++) {
+ binaryData[i] = base64Response.charCodeAt(i);
+ }
+ let blob = new Blob([binaryData], { type: "image/png" });
+ const filePath = URL.createObjectURL(blob);
+ const fileSize = blob.size;
+
+ return {
+ item: {
+ fileName,
+ filePath,
+ fileSize,
+ },
+ };
+};
+
+module.exports.updateMindmapImageForWeb = ({ item, url }) => {
+ const { fileName } = item.fileName
+ ? { fileName: item.fileName }
+ : generateIDAndName("mindmap");
+ const base64Response = atob(url.split(",")[1]);
+ const binaryData = new Uint8Array(base64Response.length);
+ for (let i = 0; i < base64Response.length; i++) {
+ binaryData[i] = base64Response.charCodeAt(i);
+ }
+ let blob = new Blob([binaryData], { type: "image/png" });
+
+ const filePath = URL.createObjectURL(blob);
+ const fileSize = blob.size;
+
+ return {
+ item: {
+ fileName,
+ filePath,
+ fileSize,
+ },
+ };
+};
+
+module.exports.createVideoForWeb = (blob) => {
+ const fileType = DEFAULT_FILE_TYPES["video"].type;
+ const { stepID, attachmentID, fileName } = generateIDAndName("video");
+
+ // const base64Response = atob(url.split(",")[1]);
+ // const binaryData = new Uint8Array(base64Response.length);
+ // for (let i = 0; i < base64Response.length; i++) {
+ // binaryData[i] = base64Response.charCodeAt(i);
+ // }
+ // let blob = new Blob([binaryData], { type: fileType });
+
+ const filePath = URL.createObjectURL(blob);
+ const fileSize = blob.size;
+
+ return {
+ item: {
+ stepID,
+ attachmentID,
+ fileName,
+ filePath,
+ fileSize,
+ fileType,
+ },
+ };
+};
+
+module.exports.createAudioForWeb = (blob) => {
+ const fileType = DEFAULT_FILE_TYPES["audio"].type;
+ const { stepID, attachmentID, fileName } = generateIDAndName("audio");
+
+ const filePath = URL.createObjectURL(blob);
+ const fileSize = blob.size;
+
+ return {
+ item: {
+ stepID,
+ attachmentID,
+ fileName,
+ filePath,
+ fileSize,
+ fileType,
+ },
+ };
+};
+
+module.exports.saveNoteForWeb = (comment) => {
+ const fileType = DEFAULT_FILE_TYPES["text"].type;
+ const { stepID, attachmentID, fileName } = generateIDAndName("text");
+ let blob = new Blob([comment.text], { type: fileType });
+ const filePath = URL.createObjectURL(blob);
+ const fileSize = blob.size;
+
+ return {
+ item: {
+ stepID,
+ attachmentID,
+ fileName,
+ filePath,
+ fileSize,
+ fileType,
+ },
+ };
+};
+
+const generateIDAndName = (type) => {
+ const stepID = uuidv4();
+ const attachmentID = uuidv4();
+ const idStr = attachmentID.replaceAll("-", "");
+ let suffix;
+
+ // if (type === "poster") {
+ // type = "image";
+ // }
+
+ if (DEFAULT_FILE_TYPES[type]) {
+ suffix = DEFAULT_FILE_TYPES[type].suffix;
+ } else {
+ suffix = "file";
+ }
+ const fileName = `${type}-${idStr}.${suffix}`;
+
+ return { stepID, attachmentID, fileName };
+};
diff --git a/src/integrations/IntegrationHelpers.js b/src/integrations/IntegrationHelpers.js
index 9c0ab2a2..fa8199d6 100644
--- a/src/integrations/IntegrationHelpers.js
+++ b/src/integrations/IntegrationHelpers.js
@@ -51,23 +51,24 @@ export default {
updatedCredential.status > 399 &&
updatedCredential.status < 500
) {
- if (credential.yattOauthTokenId) {
+ if (credential.testfiestaOauthTokenId) {
updatedCredential = null;
failedAuth.push({
credentialProvider,
credential,
});
- // Remove the id from yatt.oauthTokenIds
- let yattCreds = [];
- const currentCreds = newCredentials.yatt || credentials.yatt;
+ // Remove the id from testfiesta.oauthTokenIds
+ let testfiestaCreds = [];
+ const currentCreds =
+ newCredentials.testfiesta || credentials.testfiesta;
for (const cred of currentCreds) {
cred.oauthTokenIds = cred.oauthTokenIds.filter(
- (id) => id !== credential.yattOauthTokenId
+ (id) => id !== credential.testfiestaOauthTokenId
);
- yattCreds.push(cred);
+ testfiestaCreds.push(cred);
}
- newCredentials.yatt = yattCreds;
+ newCredentials.testfiesta = testfiestaCreds;
}
}
updated = true;
@@ -93,7 +94,7 @@ export default {
}
if (updated) {
- await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.UPDATE_CREDENTIALS,
data: newCredentials,
});
@@ -110,8 +111,8 @@ export default {
}
return true;
},
- getYattCredentialForOauthToken(credentials, token) {
- for (const credential of credentials?.yatt) {
+ getTestFiestaCredentialForOauthToken(credentials, token) {
+ for (const credential of credentials?.testfiesta) {
if (credential.oauthTokenIds.includes(token)) {
return credential.accessToken;
}
@@ -122,15 +123,15 @@ export default {
let tokenURL;
if (provider === "jira") {
if (!credential.url) {
- tokenURL = `${process.env.VUE_APP_YATT_API_URL}/app/oauth/jira/token/${credential.yattOauthTokenId}`;
- let yattToken = this.getYattCredentialForOauthToken(
+ tokenURL = `${process.env.VUE_APP_TESTFIESTA_API_URL}/app/oauth/jira/token/${credential.testfiestaOauthTokenId}`;
+ let testfiestaToken = this.getTestFiestaCredentialForOauthToken(
credentials,
- credential.yattOauthTokenId
+ credential.testfiestaOauthTokenId
);
let header = {
headers: {
- Authorization: `Bearer ${yattToken}`,
+ Authorization: `Bearer ${testfiestaToken}`,
Accept: "application/json",
},
};
diff --git a/src/integrations/JiraIntegrationHelpers.js b/src/integrations/JiraIntegrationHelpers.js
index bfbc3736..e59972d8 100644
--- a/src/integrations/JiraIntegrationHelpers.js
+++ b/src/integrations/JiraIntegrationHelpers.js
@@ -449,7 +449,7 @@ export default {
credentials.jira = [formattedData];
}
- window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.UPDATE_CREDENTIALS,
data: credentials,
});
@@ -560,7 +560,7 @@ export default {
refreshToken: data.refreshToken,
clientId: data.clientId,
clientSecret: data.clientSecret,
- yattOauthTokenId: data.yattOauthTokenId,
+ testfiestaOauthTokenId: data.testfiestaOauthTokenId,
expiresAt: data.expiresAt,
type: data.type,
loggedInAt: data.loggedInAt,
diff --git a/src/integrations/OpenAIIntegrationHelpers.js b/src/integrations/OpenAIIntegrationHelpers.js
index b364105f..521511be 100644
--- a/src/integrations/OpenAIIntegrationHelpers.js
+++ b/src/integrations/OpenAIIntegrationHelpers.js
@@ -65,7 +65,7 @@ export default {
credentials.openai = formattedData;
- window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.UPDATE_CREDENTIALS,
data: credentials,
});
diff --git a/src/integrations/TestRailIntegrationHelpers.js b/src/integrations/TestRailIntegrationHelpers.js
index 1a9799fe..a7636c1f 100644
--- a/src/integrations/TestRailIntegrationHelpers.js
+++ b/src/integrations/TestRailIntegrationHelpers.js
@@ -26,7 +26,7 @@ export default {
credentials.testrail = [formattedData];
}
- window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.UPDATE_CREDENTIALS,
data: credentials,
});
diff --git a/src/integrations/TestfiestaIntegrationHelpers.js b/src/integrations/TestfiestaIntegrationHelpers.js
new file mode 100644
index 00000000..bd7c6364
--- /dev/null
+++ b/src/integrations/TestfiestaIntegrationHelpers.js
@@ -0,0 +1,134 @@
+import { IPC_HANDLERS, IPC_FUNCTIONS } from "../modules/constants";
+import axios from "axios";
+import dayjs from "dayjs";
+
+export default {
+ async getHeaders(credential) {
+ let authHeader = { headers: {} };
+ if (credential.type === "bearer")
+ authHeader = {
+ headers: {
+ Authorization: `Bearer ${credential.accessToken}`,
+ Accept: "application/json",
+ },
+ };
+ else if (credential.type === "cookie")
+ authHeader = {
+ headers: {
+ Accept: "application/json",
+ },
+ withCredentials: true,
+ };
+
+ return authHeader;
+ },
+ async saveSession(credentials) {
+ if (!credentials?.testfiesta || credentials?.testfiesta.length < 1) {
+ const url = `${process.env.VUE_APP_TESTFIESTA_API_URL}/app/signup/token`;
+ const newCredentialsResponse = await axios.get(url);
+ this.saveCredentials(credentials, newCredentialsResponse.data);
+ }
+
+ // Pull case and session data
+ const url = `${process.env.VUE_APP_TESTFIESTA_API_URL}/pinata/executions`;
+ const state = await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.GET_STATE,
+ });
+
+ const credential = credentials?.testfiesta[0];
+ const options = {
+ headers: this.getHeaders(credential),
+ };
+
+ // Post to TestFiesta
+ let returnResponse = {
+ link: "",
+ };
+ await axios
+ .patch(url, state, options)
+ .then((postedSession) => {
+ returnResponse = postedSession.data;
+ })
+ .catch((error) => {
+ returnResponse.error = error.response.data.errors;
+ });
+
+ // Take signed attachment URLs and upload them
+ returnResponse.steps.map(async (step) => {
+ if (step.uploadURL) {
+ const match = state.session.items.find(
+ (item) => item.stepID === step.external_id
+ );
+ if (match?.filePath) {
+ const sanitizedPath =
+ match.filePath.substring(match.filePath.length - 1) !== "?"
+ ? match.filePath
+ : match.filePath.substring(0, match.filePath.length - 1);
+ const fetchResponse = await fetch(`file://${sanitizedPath}`);
+ const fileBlob = await fetchResponse.blob();
+ const file = new File([fileBlob], step.uid, { type: match.fileType });
+ await axios
+ .put(step.uploadURL, file, {
+ headers: {
+ "Content-Type": match.fileType,
+ "X-Upload-Content-Length": match.fileSize,
+ },
+ })
+ .catch((error) => {
+ returnResponse.error.push(...error.response.data.errors);
+ });
+ }
+ }
+ });
+
+ // Return result (with generated "link" field)
+ return returnResponse;
+ },
+ saveCredentials(credentials, data) {
+ let formattedData = this.formatData(data);
+
+ if (!credentials) {
+ credentials = {};
+ }
+
+ if (credentials.testfiesta && credentials.testfiesta.length > 0) {
+ let matched = false;
+ for (const [index, credential] of credentials.testfiesta.entries()) {
+ if (credential.user.id === formattedData.user.id) {
+ credentials.testfiesta[index] = formattedData;
+ matched = true;
+ }
+ }
+ if (!matched) {
+ credentials.testfiesta.push(formattedData);
+ }
+ } else {
+ credentials.testfiesta = [formattedData];
+ }
+
+ window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.UPDATE_CREDENTIALS,
+ data: credentials,
+ });
+
+ return credentials;
+ },
+ formatData(data) {
+ return {
+ accessToken: data.accessToken,
+ expiresAt: data.expiresAt,
+ type: data.type || "bearer",
+ loggedInAt: data.loggedInAt || dayjs().format("YYYY-MM-DD HH:mm:ss"),
+ oauthTokenIds: data.oauthTokenIds,
+ user: {
+ id: data.user.uid,
+ email: data.user.email,
+ name: data.user.first_name + " " + data.user.last_name,
+ avatar: data.user.avatar_url,
+ locale: data.user.preferences?.locale,
+ verified: data.user.preferences?.verified,
+ },
+ orgs: data.orgs,
+ };
+ },
+};
diff --git a/src/integrations/XrayIntegrationHelpers.js b/src/integrations/XrayIntegrationHelpers.js
new file mode 100644
index 00000000..0d2390e9
--- /dev/null
+++ b/src/integrations/XrayIntegrationHelpers.js
@@ -0,0 +1,192 @@
+import { IPC_HANDLERS, IPC_FUNCTIONS } from "../modules/constants";
+import axios from "axios";
+
+const XRAY_URL = "https://xray.cloud.getxray.app/api/v2/graphql";
+
+export default {
+ saveCredentials(credentials, data) {
+ let formattedData = this.formatData(data);
+
+ if (!credentials) {
+ credentials = {};
+ }
+
+ if (credentials.xray && credentials.xray.length > 0) {
+ let matched = false;
+ for (const [xrayIndex, credential] of Object.entries(credentials.xray)) {
+ if (credential.user.client_id === formattedData.user.client_id) {
+ credentials.xray[xrayIndex] = formattedData;
+ matched = true;
+ }
+ }
+ if (!matched) {
+ credentials.xray.push(formattedData);
+ }
+ } else {
+ credentials.xray = [formattedData];
+ }
+
+ window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.UPDATE_CREDENTIALS,
+ data: credentials,
+ });
+
+ return credentials;
+ },
+
+ formatData(data) {
+ return {
+ accessToken: data.auth_token,
+ type: "xray",
+ url: "www.getxray.app",
+ loggedInAt: data.loggedInAt,
+ lastRefreshed: data.lastRefreshed,
+ clientId: data.client_id,
+ clientSecret: data.client_secret,
+ user: {},
+ };
+ },
+
+ async fetchTestExecutions(accessToken) {
+ const graphqlQuery = {
+ query: `{
+ getTestExecutions(limit: 10) {
+ total
+ start
+ limit
+ results {
+ issueId
+ jira(fields: ["key", "summary", "assignee", "reporter"])
+ }
+ }
+ }`,
+ };
+ const authHeader = `Bearer ${accessToken}`;
+ const graphqlHeaders = {
+ headers: {
+ Authorization: authHeader,
+ "Content-Type": "application/json",
+ },
+ };
+
+ return axios
+ .post(XRAY_URL, graphqlQuery, graphqlHeaders)
+ .then((response) => {
+ if (response.status === 200) {
+ console.log("Fetched Test Executions");
+
+ return response.data.data.getTestExecutions.results;
+ }
+ })
+ .catch((error) => {
+ console.error("Error fetching test executions: ", error);
+ throw new Error(error.message);
+ });
+ },
+
+ async fetchTestRuns(accessToken, issueId) {
+ const graphqlQuery = {
+ query: `{
+ getTestRuns(testExecIssueIds: ["${issueId}"], limit: 10 ) {
+ total
+ limit
+ start
+ results {
+ id
+ status {
+ name
+ color
+ description
+ }
+ gherkin
+ examples {
+ id
+ status {
+ name
+ color
+ description
+ }
+ }
+ test {
+ issueId
+ jira(fields: ["key", "summary"])
+ }
+ testExecution {
+ issueId
+ }
+ }
+ }
+ }`,
+ };
+ const authHeader = `Bearer ${accessToken}`;
+ const graphqlHeaders = {
+ headers: {
+ Authorization: authHeader,
+ "Content-Type": "application/json",
+ },
+ };
+
+ return axios
+ .post(XRAY_URL, graphqlQuery, graphqlHeaders)
+ .then((response) => {
+ if (response.status === 200) {
+ console.log("Fetched Test Runs");
+
+ return response.data.data.getTestRuns.results;
+ }
+ })
+ .catch((error) => {
+ console.error("Error fetching test runs: ", error);
+ throw new Error(error.message);
+ });
+ },
+
+ async addEvidenceToTestRun(testRunId, file, accessToken) {
+ const graphqlQuery = {
+ query: `mutation {
+ addEvidenceToTestRun(
+ id: "${testRunId}",
+ evidence: [
+ {
+ filename: "${file.filename}"
+ mimeType: "${file.mimeType}"
+ data: "${file.data}"
+ }
+ ]
+ ) {
+ addedEvidence
+ warnings
+ }
+ }`,
+ };
+ const authHeader = `Bearer ${accessToken}`;
+ const graphqlHeaders = {
+ headers: {
+ Authorization: authHeader,
+ "Content-Type": "application/json",
+ },
+ };
+
+ return axios
+ .post(XRAY_URL, graphqlQuery, graphqlHeaders)
+ .then((response) => {
+ if (response.status === 200) {
+ if (response.data.errors) {
+ console.error(
+ "Error attaching evidence to test run: ",
+ response.data.errors[0]
+ );
+ throw new Error(response.data.errors[0].message);
+ }
+
+ console.log("Attached evidence to test run");
+
+ return response.data.data.addEvidenceToTestRun.addedEvidence;
+ }
+ })
+ .catch((error) => {
+ console.error("Error attaching evidence to test run: ", error);
+ throw new Error(error.message);
+ });
+ },
+};
diff --git a/src/integrations/YattIntegrationHelpers.js b/src/integrations/YattIntegrationHelpers.js
deleted file mode 100644
index e2ac006d..00000000
--- a/src/integrations/YattIntegrationHelpers.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import { IPC_HANDLERS, IPC_FUNCTIONS } from "../modules/constants";
-
-export default {
- saveCredentials(credentials, data) {
- console.log(`Saving YATT: ${JSON.stringify(data)}`);
- let formattedData = this.formatData(data);
-
- if (!credentials) {
- credentials = {};
- }
-
- if (credentials.yatt && credentials.yatt.length > 0) {
- let matched = false;
- for (const [index, credential] of credentials.yatt.entries()) {
- if (credential.user.id === formattedData.user.id) {
- credentials.yatt[index] = formattedData;
- matched = true;
- }
- }
- if (!matched) {
- credentials.yatt.push(formattedData);
- }
- } else {
- credentials.yatt = [formattedData];
- }
-
- window.ipc.invoke(IPC_HANDLERS.DATABASE, {
- func: IPC_FUNCTIONS.UPDATE_CREDENTIALS,
- data: credentials,
- });
-
- return credentials;
- },
- formatData(data) {
- return {
- accessToken: data.accessToken,
- jiraOauthTokenId: data.jiraOauthTokenId,
- expiresAt: data.expiresAt,
- type: data.type,
- loggedInAt: data.loggedInAt,
- oauthTokenIds: data.oauthTokenIds,
- user: {
- id: data.user.uid,
- email: data.user.email,
- name: data.user.first_name + " " + data.user.last_name,
- avatar: data.user.avatar_url,
- locale: data.user.preferences?.locale,
- verified: data.user.preferences?.verified,
- },
- orgs: data.orgs,
- };
- },
-};
diff --git a/src/integrations/ZephyrScaleIntegrationHelpers.js b/src/integrations/ZephyrScaleIntegrationHelpers.js
new file mode 100644
index 00000000..02a645e0
--- /dev/null
+++ b/src/integrations/ZephyrScaleIntegrationHelpers.js
@@ -0,0 +1,120 @@
+import { IPC_HANDLERS, IPC_FUNCTIONS } from "../modules/constants";
+import axios from "axios";
+
+const ZEPHYR_SCALE_URL = "https://api.zephyrscale.smartbear.com/v2";
+
+export default {
+ saveCredentials(credentials, data) {
+ let formattedData = this.formatData(data);
+
+ if (!credentials) {
+ credentials = {};
+ }
+
+ if (credentials.zephyrScale && credentials.zephyrScale.length > 0) {
+ let matched = false;
+ for (const [zephyrScaleIndex, credential] of Object.entries(
+ credentials.zephyrScale
+ )) {
+ if (credential.user.client_id === formattedData.user.client_id) {
+ credentials.zephyrScale[zephyrScaleIndex] = formattedData;
+ matched = true;
+ }
+ }
+ if (!matched) {
+ credentials.zephyrScale.push(formattedData);
+ }
+ } else {
+ credentials.zephyrScale = [formattedData];
+ }
+
+ window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.UPDATE_CREDENTIALS,
+ data: credentials,
+ });
+
+ return credentials;
+ },
+
+ formatData(data) {
+ return {
+ accessToken: data.auth_token,
+ type: "zephyrScale",
+ url: "smartbear.com/test-management/zephyr-scale/",
+ loggedInAt: data.loggedInAt,
+ lastRefreshed: data.lastRefreshed,
+ user: {},
+ };
+ },
+
+ async fetchProjects(authToken) {
+ const url = `${ZEPHYR_SCALE_URL}/projects`;
+ const authHeader = `Bearer ${authToken}`;
+ let header = {
+ headers: {
+ Authorization: authHeader,
+ "Content-Type": "application/json",
+ },
+ };
+
+ return await axios
+ .get(url, header)
+ .then((response) => {
+ if (response.status === 200) {
+ console.log("Fetched Projects");
+
+ return response.data.values;
+ }
+ })
+ .catch((error) => {
+ console.error("Error fetching projects: ", error);
+ throw new Error(error.message);
+ });
+ },
+
+ async fetchTestCycles(authToken, projectKey) {
+ const url = `${ZEPHYR_SCALE_URL}/testcycles?projectKey=${projectKey}`;
+ const authHeader = `Bearer ${authToken}`;
+ let header = {
+ headers: {
+ Authorization: authHeader,
+ "Content-Type": "application/json",
+ },
+ };
+
+ return await axios
+ .get(url, header)
+ .then((response) => {
+ console.log("Fetched Test Cycles");
+
+ return response.data.values;
+ })
+ .catch((error) => {
+ console.error("Error fetching test cycles: ", error);
+ throw new Error(error.message);
+ });
+ },
+
+ async fetchTestExecutions(authToken, projectKey, testCycleKey) {
+ const url = `${ZEPHYR_SCALE_URL}/testexecutions?projectKey=${projectKey}&testCycle=${testCycleKey}`;
+ const authHeader = `Bearer ${authToken}`;
+ let header = {
+ headers: {
+ Authorization: authHeader,
+ "Content-Type": "application/json",
+ },
+ };
+
+ return await axios
+ .get(url, header)
+ .then((response) => {
+ console.log("Fetched Test Executions");
+
+ return response.data.values;
+ })
+ .catch((error) => {
+ console.error("Error fetching executions: ", error);
+ throw new Error(error.message);
+ });
+ },
+};
diff --git a/src/integrations/ZephyrSquadIntegrationHelpers.js b/src/integrations/ZephyrSquadIntegrationHelpers.js
new file mode 100644
index 00000000..cf98c8fa
--- /dev/null
+++ b/src/integrations/ZephyrSquadIntegrationHelpers.js
@@ -0,0 +1,120 @@
+import { IPC_HANDLERS, IPC_FUNCTIONS } from "../modules/constants";
+import axios from "axios";
+
+const ZEPHYR_SQUAD_URL = "https://prod-api.zephyr4jiracloud.com/v2";
+
+export default {
+ saveCredentials(credentials, data) {
+ let formattedData = this.formatData(data);
+
+ if (!credentials) {
+ credentials = {};
+ }
+
+ if (credentials.zephyrSquad && credentials.zephyrSquad.length > 0) {
+ let matched = false;
+ for (const [zephyrSquadIndex, credential] of Object.entries(
+ credentials.zephyrSquad
+ )) {
+ if (credential.user.client_id === formattedData.user.client_id) {
+ credentials.zephyrSquad[zephyrSquadIndex] = formattedData;
+ matched = true;
+ }
+ }
+ if (!matched) {
+ credentials.zephyrSquad.push(formattedData);
+ }
+ } else {
+ credentials.zephyrSquad = [formattedData];
+ }
+
+ window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.UPDATE_CREDENTIALS,
+ data: credentials,
+ });
+
+ return credentials;
+ },
+
+ formatData(data) {
+ return {
+ accessToken: data.auth_token,
+ type: "zephyrSquad",
+ url: "smartbear.com/test-management/zephyr-squad/",
+ loggedInAt: data.loggedInAt,
+ lastRefreshed: data.lastRefreshed,
+ user: {},
+ };
+ },
+
+ async fetchProjects(authToken) {
+ const url = `${ZEPHYR_SQUAD_URL}/projects`;
+ const authHeader = `Bearer ${authToken}`;
+ let header = {
+ headers: {
+ Authorization: authHeader,
+ "Content-Type": "application/json",
+ },
+ };
+
+ return await axios
+ .get(url, header)
+ .then((response) => {
+ if (response.status === 200) {
+ console.log("Fetched Projects");
+
+ return response.data.values;
+ }
+ })
+ .catch((error) => {
+ console.error("Error fetching projects: ", error);
+ throw new Error(error.message);
+ });
+ },
+
+ async fetchTestCycles(authToken, projectKey) {
+ const url = `${ZEPHYR_SQUAD_URL}/testcycles?projectKey=${projectKey}`;
+ const authHeader = `Bearer ${authToken}`;
+ let header = {
+ headers: {
+ Authorization: authHeader,
+ "Content-Type": "application/json",
+ },
+ };
+
+ return await axios
+ .get(url, header)
+ .then((response) => {
+ console.log("Fetched Test Cycles");
+
+ return response.data.values;
+ })
+ .catch((error) => {
+ console.error("Error fetching test cycles: ", error);
+ throw new Error(error.message);
+ });
+ },
+
+ async fetchTestExecutions(authToken, projectKey, testCycleKey) {
+ const url = `${ZEPHYR_SQUAD_URL}/testexecutions?projectKey=${projectKey}&testCycle=${testCycleKey}`;
+ const authHeader = `Bearer ${authToken}`;
+ let header = {
+ headers: {
+ Authorization: authHeader,
+ "Content-Type": "application/json",
+ },
+ };
+
+ return await axios
+ .get(url, header)
+ .then((response) => {
+ console.log("Fetched Test Executions");
+
+ return response.data.values;
+ })
+ .catch((error) => {
+ console.error("Error fetching executions: ", error);
+ throw new Error(error.message);
+ });
+ },
+};
diff --git a/src/layouts/Default.vue b/src/layouts/Default.vue
index 9604eab3..b90acb0a 100644
--- a/src/layouts/Default.vue
+++ b/src/layouts/Default.vue
@@ -73,8 +73,8 @@ export default {
this.$store.commit("restoreState", state);
const currentPath = this.$router.history.current.path;
- if (currentPath !== state.path) {
- await this.$router.push({ path: state.path });
+ if (currentPath !== state.session.path) {
+ await this.$router.push({ path: state.session.path });
}
}
});
@@ -86,8 +86,8 @@ export default {
}
},
methods: {
- setTheme({ appearance }) {
- const isDarkMode = appearance === "dark";
+ setTheme({ theme }) {
+ const isDarkMode = theme === "dark";
this.$vuetify.theme.dark = isDarkMode;
localStorage.setItem("isDarkMode", isDarkMode.toString());
},
diff --git a/src/locales/en.json b/src/locales/en.json
index c73c3cc4..1d500a78 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -1,10 +1,11 @@
{
"caption": {
"no_values_found": "No values found",
+ "share_session": "Share session",
"ai_assist": "AI assist",
"filename": "File name",
"custom_oauth_connections": "Custom OAuth Connections",
- "about_yattie": "About YATTIE",
+ "about_pinata": "About Piñata",
"close": "Close",
"login_with_jira_cloud": "Login with JIRA Cloud",
"login_with_jira_datacenter": "Login with JIRA Datacenter",
@@ -14,7 +15,18 @@
"share_config": "Got your configuration just how you like it? You can share your config file with your team to standardize your process or help them get started quicker!",
"key_capture": "Press your new hotkey to update",
"split_credentials": "Your credentials are split into a separate file than your configuration. If you would like to share a custom server OAuth configuration, you can click ",
+ "image_deleted": "Successfully delete the image.",
+ "theme": "Appearance",
"here": "here",
+ "marker": "Marker",
+ "shapes": "Shapes",
+ "connector": "Connector",
+ "text": "Text",
+ "upload_evidence": "Upload evidence",
+ "upload_attachment": "Upload attachment",
+ "zoom_in_to_flow": "Zoom in to flow",
+ "zoom_in": "Zoom in",
+ "zoom_out": "Zoom out",
"new_session": "New session",
"end_session": "End session",
"open_session": "Open saved session",
@@ -32,18 +44,41 @@
"audio_wave": "Audio wave",
"detail": "Details",
"title": "Title",
+ "exploratory_session": "Exploratory session",
"precondition": "Preconditions",
"configured_time": "Configured time",
"session_time": "Session time",
+ "session_name": "Session name",
+ "privacy": "Privacy",
"session_elapsed_time": "Elapsed time",
"notes": "Notes",
- "tags": "Tags",
+ "tags_tab": "Tags",
+ "default_tags": "Default Tags",
+ "add_another_tag": "Add another tag",
"reactions": "Reactions",
+ "color_picker": "Pick Color",
+ "fill": "Fill",
+ "transparent": "Transparent",
+ "no_fill": "No fill",
+ "color": "Color",
+ "rectangle": "Rectangle",
+ "ellipse": "Ellipse",
+ "triangle": "Triangle",
+ "diamond": "Diamond",
+ "downward-triangle": "Downward-triangle",
"environment": "Environment",
"os": "@.upper:os",
"window": "Window",
"full_screen": "Full screen",
"screen_size": "Screen size",
+ "current_date_time": "Current Data/Time",
+ "computer_name": "Computer Name",
+ "os_system": "Operating System",
+ "system_manufacturer": "System Manufacturer",
+ "system_model": "System Model",
+ "bios": "BIOS",
+ "processor": "Processor",
+ "memory": "Memory",
"general": "General",
"connections": "Connections",
"templates": "Templates",
@@ -51,26 +86,34 @@
"reports": "Reports",
"addons": "Addons",
"hotkeys": "Hotkeys",
+ "tags": "Tags",
"support": "Support",
"signup_test_app": "Sign up to test apps",
"back": "Back",
"sign_in": "Sign in",
- "signin_yatt": "Sign in with YATT",
+ "signin_testfiesta": "Sign in with TestFiesta",
"signin_jira": "Sign in with JIRA",
"signin_testrail": "Sign in with TestRail",
+ "signin_xray": "Sign in with XRAY",
+ "signin_zephyr_squad": "Sign in with Zephyr Squad",
+ "signin_zephyr_scale": "Sign in with Zephyr Scale",
"signin_qtest": "Sign in with qTest",
"signin_practitest": "Sign in with PractiTest",
"sign_up": "Sign up|Sign Up",
"signup_term_data_policy": "Terms, data policy",
"signup_cookie_policy": "Cookies policy",
"and": "And",
- "signup_yattie": "Sign up with YATT",
+ "signup_pinata": "Sign up with TESTFIESTA",
"signup_jira": "Sign up with JIRA",
"signup_testrail": "Sign up with TestRail",
+ "signup_xray": "Sign up with XRAY",
+ "signup_zephyr_squad": "Sign up with Zephyr Squad",
+ "signup_zephyr_scale": "Sign up with Zephyr Scale",
"signup_qtest": "Sign up with qTest",
"signup_practitest": "Sign up with PractiTest",
"skip_sign_up": "Skip sign up",
"or": "Or",
+ "open": "Open",
"start_test_as": "Start test as",
"your_email": "Your email",
"ok": "@.upper:ok",
@@ -79,6 +122,7 @@
"cancel": "Cancel",
"confirm": "Confirm",
"proceed": "Proceed",
+ "copy_link": "Copy link",
"session_saved": "Session Saved",
"note_title": "Note title",
"take_note": "Take a Note",
@@ -91,16 +135,18 @@
"post_session": "Post session",
"enable_checklist": "Enable checklist",
"add_another_task": "Add another task",
+ "add_org_logo_in_pdf": "Add an organization Logo in PDF",
"required": "Required",
"required_follow_up": "Follow up required",
"external_connection": "@.upper:external connections",
- "yatt": "@.upper:yatt",
+ "testfiesta": "@.upper:testfiesta",
"add_color": "Add color",
"search": "Search",
"organization_name": "Organization name",
"add_another_organization": "Add another organization",
"app_settings": "App Settings",
"select_file": "Select a file",
+ "clear_cache": "Clear Cache",
"appearance": "Appearance",
"screen_recording": "Screen recording",
"audio_on_screen_capture": "Audio on screen capture",
@@ -120,12 +166,18 @@
"save_hotkey": "Save hotkeys",
"checklist": "Checklist",
"required_field": "This field is required",
+ "continue": "Continue",
"start_session": "Start session",
"start_quick_test": "Start Quick Test",
"export": "Export",
"save_as": "Save As",
+ "save_as_zip": "Save As ZIP Archive",
+ "save_as_pdf": "Save As PDF",
"export_to_jira": "Export to JIRA",
"export_to_testrail": "Export to TestRail",
+ "export_to_xray": "Export to XRAY",
+ "export_to_zephyr_squad": "Export to Zephyr Squad",
+ "export_to_zephyr_scale": "Export to Zephyr Scale",
"resume_session": "Resume session",
"save_session": "Save session",
"clear_session": "Clear session",
@@ -138,7 +190,9 @@
"start_audio_record": "Start audio record",
"stop_audio_record": "Stop audio record",
"note": "Note|Notes",
- "mind_map": "Mind map",
+ "mind_map": "Mindmap",
+ "add_tag": "+ Add Tag",
+ "text_description": "Text description",
"minimize": "Minimize",
"confirm_delete": "Confirm delete",
"confirm_reset": "Confirm reset",
@@ -150,7 +204,6 @@
"end": "End",
"length": "Length",
"session_started": "Session started",
- "upload_evidence": "Upload evidence",
"elapsed_time": "Elapsed time",
"remaining_time": "Remaining time",
"charter": "Charter",
@@ -162,9 +215,15 @@
"change_recording_target": "Change recording target",
"export_item_to_jira": "Export item to JIRA",
"export_item_to_testrail": "Export item to TestRail",
+ "export_item_to_xray": "Export item to XRAY",
+ "export_item_to_zephyr_squad": "Export item to Zephyr Squad",
+ "export_item_to_zephyr_scale": "Export item to Zephyr Scale",
"issues": "ISSUE(S)",
"projects": "PROJECT(S)",
+ "test_executions": "TEST EXECUTION(S)",
+ "test_cycles": "TEST CYCLE(S)",
"runs": "RUN(S)",
+ "test_runs": "TEST RUN(S)",
"tests": "TEST(S)",
"add_result": "ADD RESULT",
"result_status": "RESULT STATUS",
@@ -174,6 +233,8 @@
"dark_mode": "Dark mode",
"quick_test": "@.capitalize:quick test",
"client_id": "Client ID",
+ "zephyr_squad_api_access_token": "Zephyr Squad API Access Token",
+ "zephyr_scale_api_access_token": "Zephyr Scale API Access Token",
"client_secret": "Client Secret",
"user_name": "User name",
"password": "Password",
@@ -181,17 +242,26 @@
"is_required": " is required",
"new_exploratory_session": "New Exploratory Session",
"open_exploratory_session": "Open Exploratory Session",
+ "scripted_test_session": "Scripted Test Session",
"sign_in_with": "Sign in with",
"log_in_successful": "Log in successful",
"log_in_failed": "Log in failed",
"logged_in_jira": "Logged in to Jira",
"logged_in_testrail": "Logged in to TestRail",
+ "logged_in_xray": "Logged in to XRAY",
+ "logged_in_zephyr_squad": "Logged in to Zephyr Squad",
+ "logged_in_zephyr_scale": "Logged in to Zephyr Scale",
"logged_in_qtest": "Logged in to qTest",
"logged_in_practitest": "Logged in to PractiTest",
"not_logged_in_jira": "Not Logged in to Jira",
"not_logged_in_testrail": "Not Logged in to TestRail",
+ "not_logged_in_xray": "Not Logged in to XRAY",
+ "not_logged_in_zephyr_squad": "Not Logged in to Zephyr Squad",
+ "not_logged_in_zephyr_scale": "Not Logged in to Zephyr Scale",
"not_logged_in_qtest": "Not Logged in to qTest",
"not_logged_in_practitest": "Not Logged in to PractiTest",
+ "log_out": "Log Out",
+ "integrations": "Integrations",
"required_checkbox": "This checkbox is required to continue",
"create_new_issue": "Create a New Issue",
"create_new_jira_issue": "Create a New Jira Issue",
@@ -204,8 +274,9 @@
"invalid_openai_key": "Invalid OpenAI key",
"openai_key_saved": "OpenAI key successfully saved",
"openai_key_validation_failed": "Failed to validate OpenAI key",
+ "successfully_saved": "Successfully saved!",
"no_server_oauth_creds": "No shareable OAuth credentials found.",
- "testrail_instance_url":"Instance URL"
+ "testrail_instance_url": "Instance URL"
},
"hotkeys": {
"general": "General",
@@ -250,7 +321,7 @@
"save_as_charter": "Save test charter",
"reset_session": "Reset session",
"app_setting": "App settings",
- "take_survey": "Take the YATTIE Survey",
+ "take_survey": "Take the Pinata Survey",
"exit": "Exit",
"edit": "Edit",
"undo": "Undo",
@@ -264,11 +335,14 @@
"check_update": "Check for updates"
},
"message": {
+ "add_default_tags": "Add your default tags that will be used in any session",
"browser_action_attempts": "Waiting for browser action. Attempts:",
"invalid_html": "Invalid HTML returned",
+ "empty_workspace": "Start taking notes and gathering evidence below to build a testing tree.",
"ai_assist_not_empty": "AI assist field cannot be empty",
"successfully_added_attachment": "Successfully added attachment to issue",
"api_error": "API error",
+ "zephyr_api_access_token_error": "Invalid Access Token",
"please_try_again": "Please try again",
"successfully_created_issue": "Successfully created issue",
"please_fill_required": "Please fill out all required fields",
@@ -279,11 +353,15 @@
"jira_server_sign_in_description": "In order to sign in to your JIRA Server instance securely, you will need a JIRA administrator to create an \"Incoming Link\" with \"WRITE\" permissions by following the directions here:",
"jira_server_sign_in_note": "Note: You must be logged into your JIRA Server instance in your browser before trying to authenticate.",
"jira_incoming_link": "JIRA Incoming Link Documentation",
+ "zephyr_api_keys_docs": "How Do I Generate API Access Token?",
+ "xray_api_keys_docs": "How do I create an API Client id and Client Secret?",
"signup_policy": "By signing up, you agree to our",
"enter_service_url": "Enter Service URL",
+ "confirm_clear_cache": "Are you sure you want to clear the local session cache?",
"confirm_delete": "Are you sure you want to delete?",
"confirm_reset": "Are you sure you want to reset?",
- "confirm_reset_session": "Do you want to continue your last session?",
+ "confirm_back": "Are you sure you want to go back? Your session data will be lost",
+ "continue_last_session": "Do you want to continue your last session?",
"confirm_save_progress": "Do you want save current progress?",
"confirm_proceed_session_time": "Do you want to proceed with testing or end the test session?",
"insert_note": "Insert your note here",
@@ -293,17 +371,22 @@
"insert_comment": "Insert your comment here",
"use_pre_post_test_checklist": "Use pre and post testing checklists to streamline your testing process",
"connect_to_testrail": "Connect to a TestRail instance",
+ "connect_to_xray": "Connect to an XRAY instance",
+ "connect_to_zephyr_squad": "Connect to a Zephyr Squad instance",
+ "connect_to_zephyr_scale": "Connect to a Zephyr Scale instance",
"connect_to_jira": "Connect to a JIRA instance",
"use_app_only_local": "Use the app locally only",
- "dont_pull_push_data": "Don't pull or push data to external sources",
+ "dont_pull_push_data": "Don't automatically pull or push data to external sources",
"capture_audio": "Catpure audio with screen recordings",
"select_default_color": "Select the default annotation color",
"default_color_description": "This will change the default color of the image annotation tools",
"select_default_comment_type": "Select default note/comment type",
"make_written_summary": "Make a written summary mandatory upon completing a session",
+ "add_organization_logo": "Add an organization logo to a session exporting pdf",
"improve_note": "Improve your note taking and evidence labeling with pre-populated templates",
"speed_hotkey": "Speed up your workflow with custom hotkeys",
"define_required_precondition": "Define required preconditions for this test.",
+ "default_template_text": "Define default template text.",
"enter_title_for_project": "Enter a title for the project",
"select_presession_task": "You can't begin your session without checking all required(*) pre-session checkboxes.",
"error_recording_audio": "An error occurred while recording the audio. Please make sure you have at least one audio input device connected",
@@ -312,8 +395,12 @@
"confirm_session_saved": "Session successfully saved on your computer.",
"confirm_jira_issue": "You have not selected any evidence to attach to this issue. If you would like to attach evidence, please go back and select it before creating an issue.",
"user_name": "User name",
- "is_required":" is required.",
- "api_key":"Api key",
- "instance_url": "Instance URL"
+ "is_required": " is required.",
+ "api_key": "Api key",
+ "instance_url": "Instance URL",
+ "client_id": "Client ID",
+ "zephyr_squad_api_access_token": "Zephyr Squad API Access Token",
+ "zephyr_scale_api_access_token": "Zephyr Scale API Access Token",
+ "client_secret": "Client Secret"
}
}
diff --git a/src/main.js b/src/main.js
index f21ccf3c..c98908d4 100644
--- a/src/main.js
+++ b/src/main.js
@@ -11,6 +11,7 @@ import DefaultLayout from "./layouts/Default.vue";
import MinimizeLayout from "./layouts/Minimize.vue";
import VueShortkey from "vue-shortkey";
+
import VueMask from "v-mask";
import i18n from "./i18n";
import StorageService from "./services/storageService";
diff --git a/src/menu.js b/src/menu.js
index d1515bd9..fd8e017e 100644
--- a/src/menu.js
+++ b/src/menu.js
@@ -58,7 +58,7 @@ const createMenu = (win) => {
{
label: i18n.tc("menu.take_survey", 1),
click() {
- open("https://yatt.ai/yattie-survey/", (err) => {
+ open("https://testfiesta.com/pinata-survey/", (err) => {
console.log(err);
});
},
@@ -109,7 +109,7 @@ const createMenu = (win) => {
{
label: i18n.t("menu.online_doc"),
click() {
- open("https://docs.yattie.ai/", (err) => {
+ open("https://docs.testfiesta.com/", (err) => {
console.log(err);
});
},
@@ -117,7 +117,7 @@ const createMenu = (win) => {
{
label: i18n.t("menu.get_support"),
click() {
- open("https://github.com/dacoaster/yattie/issues/", (err) => {
+ open("https://github.com/dacoaster/pinata/issues/", (err) => {
console.log(err);
});
},
diff --git a/src/modules/BrowserWindowUtility.js b/src/modules/BrowserWindowUtility.js
index 1c349d8b..67809f02 100644
--- a/src/modules/BrowserWindowUtility.js
+++ b/src/modules/BrowserWindowUtility.js
@@ -8,12 +8,12 @@ module.exports.getBrowserWindow = () => {
return this._browserWindow;
};
-module.exports.setMinimizedWindow = (minimizedWindow) => {
- this._minimizedWindow = minimizedWindow;
+module.exports.setLowProfiledWindow = (lowProfiledWindow) => {
+ this._lowProfiledWindow = lowProfiledWindow;
};
-module.exports.getMinimizedWindow = () => {
- return this._minimizedWindow;
+module.exports.getLowProfiledWindow = () => {
+ return this._lowProfiledWindow;
};
module.exports.setViewMode = (viewMode) => {
@@ -26,5 +26,5 @@ module.exports.getViewMode = () => {
module.exports.getParentWindow = () => {
if (this._viewMode === VIEW_MODE.NORMAL) return this._browserWindow;
- else return this._minimizedWindow;
+ else return this._lowProfiledWindow;
};
diff --git a/src/modules/CaptureUtility.js b/src/modules/CaptureUtility.js
index fdef0124..f80ae8e5 100644
--- a/src/modules/CaptureUtility.js
+++ b/src/modules/CaptureUtility.js
@@ -4,6 +4,7 @@ const fs = require("fs");
const dayjs = require("dayjs");
const detect = require("detect-file-type");
const uuidv4 = require("uuid");
+const crypto = require("crypto");
const ffmpeg = require("fluent-ffmpeg");
@@ -20,9 +21,9 @@ ffmpeg.setFfmpegPath(ffmpegPath);
ffmpeg.setFfprobePath(ffprobePath.path);
const browserUtility = require("./BrowserWindowUtility");
-const databaseUtility = require("./DatabaseUtility");
+const persistenceUtility = require("./PersistenceUtility");
-const { STATUSES } = require("./constants");
+const { STATUSES, DEFAULT_FILE_TYPES } = require("./constants");
const configDir = (app || remote.app).getPath("userData");
@@ -45,16 +46,17 @@ module.exports.getMediaSource = async () => {
};
module.exports.createImage = ({ url, isPoster }) => {
+ const fileType = DEFAULT_FILE_TYPES["image"].type;
const imageType = isPoster ? "poster" : "image";
- const { id, fileName } = generateIDAndName(imageType);
+ const { stepID, attachmentID, fileName } = generateIDAndName(imageType);
const filePath = path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
fileName
);
const base64Data = url.replace(/^data:image\/png;base64,/, "");
- fs.writeFile(filePath, base64Data, "base64", function (err) {
+ fs.writeFileSync(filePath, base64Data, "base64", function (err) {
if (err) {
console.log(err);
return {
@@ -64,12 +66,21 @@ module.exports.createImage = ({ url, isPoster }) => {
}
});
+ const fileSize = fs.statSync(filePath).size;
+ const fileChecksum = crypto
+ .createHash("md5")
+ .update(base64Data, "utf8")
+ .digest("hex");
return {
status: STATUSES.SUCCESS,
item: {
- id,
+ stepID,
+ attachmentID,
fileName,
filePath,
+ fileSize,
+ fileChecksum,
+ fileType,
},
};
};
@@ -80,39 +91,40 @@ module.exports.updateImage = ({ item, url }) => {
}
const { fileName } = item.fileName
? { fileName: item.fileName }
- : generateIDAndName("image", item.id);
+ : generateIDAndName("image", item.attachmentID);
const filePath = path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
fileName
);
const base64Data = url.replace(/^data:image\/png;base64,/, "");
- fs.writeFile(filePath, base64Data, "base64", function (err) {
- if (err) {
- console.log(err);
- return {
- status: STATUSES.ERROR,
- message: err,
- };
- }
- });
+ fs.writeFileSync(filePath, base64Data, "base64");
+
+ const fileSize = fs.statSync(filePath).size;
+ const fileChecksum = crypto
+ .createHash("md5")
+ .update(base64Data, "utf8")
+ .digest("hex");
return {
status: STATUSES.SUCCESS,
item: {
fileName,
filePath,
+ fileSize,
+ fileChecksum,
},
};
};
module.exports.createVideo = ({ buffer }) => {
- const { id, fileName } = generateIDAndName("video");
+ const fileType = DEFAULT_FILE_TYPES["video"].type;
+ const { stepID, attachmentID, fileName } = generateIDAndName("video");
const filePath = path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
fileName
);
fs.writeFileSync(filePath, Buffer.from(buffer), function (err) {
@@ -124,25 +136,38 @@ module.exports.createVideo = ({ buffer }) => {
};
}
});
+
+ const fileSize = fs.statSync(filePath).size;
+ const fileContents = fs.readFileSync(filePath);
+ const fileChecksum = crypto
+ .createHash("md5")
+ .update(fileContents, "utf8")
+ .digest("hex");
return {
status: STATUSES.SUCCESS,
item: {
- id,
+ stepID,
+ attachmentID,
fileName,
filePath,
+ fileSize,
+ fileChecksum,
+ fileType,
},
};
};
module.exports.optimizeVideo = ({ filePath }) => {
+ const fileFormat = DEFAULT_FILE_TYPES["video"].suffix;
const tempName =
"temp-optimizing-video-" +
dayjs().format("YYYY-MM-DD_HH-mm-ss-ms") +
- ".mp4";
+ "." +
+ fileFormat;
const tempPath = path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
tempName
);
@@ -150,7 +175,7 @@ module.exports.optimizeVideo = ({ filePath }) => {
ffmpeg(filePath)
.videoCodec("libx264")
.audioCodec("aac")
- .format("mp4")
+ .format(fileFormat)
.save(tempPath)
.on("start", function (commandLine) {
console.log("start : " + commandLine);
@@ -167,8 +192,17 @@ module.exports.optimizeVideo = ({ filePath }) => {
console.log(err);
return reject({ status: STATUSES.ERROR, message: err });
}
+
+ const fileSize = fs.statSync(filePath).size;
+ const fileContents = fs.readFileSync(filePath);
+ const fileChecksum = crypto
+ .createHash("md5")
+ .update(fileContents, "utf8")
+ .digest("hex");
return resolve({
status: STATUSES.SUCCESS,
+ fileSize,
+ fileChecksum,
});
});
})
@@ -180,14 +214,16 @@ module.exports.optimizeVideo = ({ filePath }) => {
};
module.exports.updateVideo = ({ item, start, end, previousDuration }) => {
+ const fileFormat = "mp4";
const tempName =
"temp-optimizing-video-" +
dayjs().format("YYYY-MM-DD_HH-mm-ss-ms") +
- ".mp4";
+ "." +
+ fileFormat;
const tempPath = path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
tempName
);
const duration = parseInt(end - start);
@@ -210,11 +246,11 @@ module.exports.updateVideo = ({ item, start, end, previousDuration }) => {
}
const { fileName } = item.fileName
? { fileName: item.fileName }
- : generateIDAndName("video", item.id);
+ : generateIDAndName("video", item.attachmentID);
const filePath = path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
fileName
);
fs.rename(tempPath, filePath, function (err) {
@@ -222,11 +258,20 @@ module.exports.updateVideo = ({ item, start, end, previousDuration }) => {
console.log(err);
return reject({ status: STATUSES.ERROR, message: err });
}
+
+ const fileSize = fs.statSync(filePath).size;
+ const fileContents = fs.readFileSync(filePath);
+ const fileChecksum = crypto
+ .createHash("md5")
+ .update(fileContents, "utf8")
+ .digest("hex");
return resolve({
status: STATUSES.SUCCESS,
item: {
fileName,
filePath,
+ fileSize,
+ fileChecksum,
},
});
});
@@ -238,26 +283,30 @@ module.exports.updateVideo = ({ item, start, end, previousDuration }) => {
} else {
const { fileName } = item.fileName
? { fileName: item.fileName }
- : generateIDAndName("video", item.id);
+ : generateIDAndName("video", item.attachmentID);
const filePath = path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
fileName
);
if (item.filePath && item.filePath !== filePath) {
- fs.rename(item.filePath, filePath, function (err) {
- if (err) {
- console.log(err);
- return reject({ status: STATUSES.ERROR, message: err });
- }
- });
+ fs.renameSync(item.filePath, filePath);
}
+
+ const fileSize = fs.statSync(filePath).size;
+ const fileContents = fs.readFileSync(filePath);
+ const fileChecksum = crypto
+ .createHash("md5")
+ .update(fileContents, "utf8")
+ .digest("hex");
return resolve({
status: STATUSES.SUCCESS,
item: {
fileName,
filePath,
+ fileSize,
+ fileChecksum,
},
});
}
@@ -265,11 +314,12 @@ module.exports.updateVideo = ({ item, start, end, previousDuration }) => {
};
module.exports.createAudio = ({ buffer }) => {
- const { id, fileName } = generateIDAndName("audio");
+ const fileType = DEFAULT_FILE_TYPES["audio"].type;
+ const { stepID, attachmentID, fileName } = generateIDAndName("audio");
const filePath = path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
fileName
);
fs.writeFileSync(filePath, Buffer.from(buffer), function (err) {
@@ -281,12 +331,22 @@ module.exports.createAudio = ({ buffer }) => {
};
}
});
+
+ const fileSize = fs.statSync(filePath).size;
+ const fileChecksum = crypto
+ .createHash("md5")
+ .update(Buffer.from(buffer, "utf-8"))
+ .digest("hex");
return {
status: STATUSES.SUCCESS,
item: {
- id,
+ stepID,
+ attachmentID,
fileName,
filePath,
+ fileSize,
+ fileChecksum,
+ fileType,
},
};
};
@@ -294,30 +354,31 @@ module.exports.createAudio = ({ buffer }) => {
module.exports.updateAudio = ({ item }) => {
const { fileName } = item.fileName
? { fileName: item.fileName }
- : generateIDAndName("audio", item.id);
+ : generateIDAndName("audio", item.attachmentID);
const filePath = path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
fileName
);
if (item.filePath && item.filePath !== filePath) {
- fs.rename(item.filePath, filePath, function (err) {
- if (err) {
- console.log(err);
- return {
- status: STATUSES.ERROR,
- message: err,
- };
- }
- });
+ fs.renameSync(item.filePath, filePath);
}
+
+ const fileSize = fs.statSync(filePath).size;
+ const fileContents = fs.readFileSync(filePath, "utf-8");
+ const fileChecksum = crypto
+ .createHash("md5")
+ .update(fileContents)
+ .digest("hex");
return {
status: STATUSES.SUCCESS,
item: {
fileName,
filePath,
+ fileSize,
+ fileChecksum,
},
};
};
@@ -328,34 +389,6 @@ module.exports.deleteFile = ({ filePath }) => {
}
};
-module.exports.saveNote = (comment, fileNameToSave = "") => {
- const { id, fileName } = generateIDAndName("text");
- const filePath = path.join(
- configDir,
- "sessions",
- databaseUtility.getSessionID(),
- fileNameToSave ? fileNameToSave : fileName
- );
-
- fs.writeFile(filePath, comment.text, function (err) {
- if (err) {
- console.log(err);
- return {
- status: STATUSES.ERROR,
- message: err,
- };
- }
- });
- return {
- status: STATUSES.SUCCESS,
- item: {
- id,
- fileName: fileNameToSave ? fileNameToSave : fileName,
- filePath,
- },
- };
-};
-
module.exports.uploadEvidence = async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ["openFile"],
@@ -369,13 +402,13 @@ module.exports.uploadEvidence = async () => {
}
// TODO - Handle multiple files uploaded
- const id = uuidv4();
- console.log({ id });
+ const stepID = uuidv4();
+ const attachmentID = uuidv4();
const fileName = path.basename(filePaths[0]);
const filePath = path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
fileName
);
@@ -391,28 +424,24 @@ module.exports.uploadEvidence = async () => {
});
}
- let fileType = "";
- if (result) {
- if (result.mime.substring(0, 6) === "image/") {
- fileType = "image";
- } else if (result.mime.substring(0, 6) === "video/") {
- fileType = "video";
- } else if (result.mime.substring(0, 6) === "audio/") {
- fileType = "audio";
- } else {
- fileType = "other";
- }
- } else {
- fileType = "other";
- }
+ let fileType = result.mime;
+ const fileSize = fs.statSync(filePath).size;
+ const fileContents = fs.readFileSync(filePath, "utf8");
+ const fileChecksum = crypto
+ .createHash("md5")
+ .update(fileContents)
+ .digest("hex");
return resolve({
status: STATUSES.SUCCESS,
item: {
- id,
+ stepID,
+ attachmentID,
fileType,
fileName,
filePath,
+ fileSize,
+ fileChecksum,
},
});
});
@@ -420,12 +449,13 @@ module.exports.uploadEvidence = async () => {
};
module.exports.dropFile = async (data) => {
- const id = uuidv4();
+ const stepID = uuidv4();
+ const attachmentID = uuidv4();
const fileName = data.name;
const filePath = path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
fileName
);
@@ -440,65 +470,54 @@ module.exports.dropFile = async (data) => {
});
}
- let fileType = "";
- if (result) {
- if (result.mime.substring(0, 6) === "image/") {
- fileType = "image";
- } else if (result.mime.substring(0, 6) === "video/") {
- fileType = "video";
- } else if (result.mime.substring(0, 6) === "audio/") {
- fileType = "audio";
- } else {
- fileType = "other";
- }
- } else {
- fileType = "other";
- }
+ let fileType = result.mime;
+ const fileSize = fs.statSync(filePath).size;
+ const fileContents = fs.readFileSync(filePath);
+ const fileChecksum = crypto
+ .createHash("md5")
+ .update(fileContents, "utf8")
+ .digest("hex");
return resolve({
status: STATUSES.SUCCESS,
+ // TODO - Move item building to a util
item: {
- id,
+ stepID,
+ attachmentID,
fileType,
fileName,
filePath,
+ fileSize,
+ fileChecksum,
},
});
});
});
};
-module.exports.setAppearance = ({ appearance }) => {
+module.exports.setAppearance = (theme) => {
const browserWindow = browserUtility.getBrowserWindow();
- browserWindow.webContents.send("SET_THEME", { appearance });
+ browserWindow.webContents.send("SET_THEME", theme);
};
const generateIDAndName = (type, uid = undefined) => {
- let id, idStr, fileName;
+ const stepID = uuidv4();
+ let attachmentID, idStr, fileName;
let success = false;
let suffix;
- switch (type) {
- case "video":
- suffix = "mp4";
- break;
- case "audio":
- suffix = "mp3";
- break;
- case "image":
- case "poster":
- suffix = "png";
- break;
- case "text":
- suffix = "txt";
- break;
- default:
- suffix = "file";
+ if (type === "poster") {
+ type = "image";
+ }
+ if (DEFAULT_FILE_TYPES[type]) {
+ suffix = DEFAULT_FILE_TYPES[type].suffix;
+ } else {
+ suffix = "file";
}
while (!success) {
- id = uuidv4();
- idStr = id.replaceAll("-", "");
+ attachmentID = uuidv4();
+ idStr = attachmentID.replaceAll("-", "");
for (let i = 0; i < idStr.length - 5; i++) {
fileName = `${type}-${idStr.substring(i, 5)}.${suffix}`;
if (
@@ -506,7 +525,7 @@ const generateIDAndName = (type, uid = undefined) => {
path.join(
configDir,
"sessions",
- databaseUtility.getSessionID(),
+ persistenceUtility.getSessionID(),
fileName
)
)
@@ -517,7 +536,7 @@ const generateIDAndName = (type, uid = undefined) => {
}
}
if (uid) {
- id = uid;
+ attachmentID = uid;
}
- return { id, fileName };
+ return { stepID, attachmentID, fileName };
};
diff --git a/src/modules/DatabaseUtility.js b/src/modules/DatabaseUtility.js
deleted file mode 100644
index dbbcd665..00000000
--- a/src/modules/DatabaseUtility.js
+++ /dev/null
@@ -1,493 +0,0 @@
-const JSONdb = require("simple-json-db");
-const { app, remote } = require("electron");
-const path = require("path");
-const fs = require("fs");
-const browserUtility = require("./BrowserWindowUtility");
-const { STATUSES } = require("./constants");
-
-const configDir = (app || remote.app).getPath("userData");
-const jsonDbConfig = {
- jsonSpaces: 2,
-};
-
-const currentVersion = app.getVersion();
-
-let metaDb, configDb, credentialDb, dataDb, configVersion;
-let browserWindow;
-
-const defaultMeta = {
- configPath: path.join(configDir, "config.json"),
- credentialsPath: path.join(configDir, "credentials.json"),
- sessionDataPath: "",
- version: currentVersion,
-};
-
-const defaultConfig = {
- useLocal: true,
- apperance: "light",
- showIssue: false,
- appLabel: false,
- defaultColor: "#1976D2FF",
- commentType: "Comment",
- audioCapture: false,
- videoQuality: "high",
- debugMode: false,
- summary: false,
- ai: {
- enabled: false,
- },
- templates: [
- {
- type: "Screenshot",
- precondition: {
- content: "",
- text: "",
- },
- issue: "",
- isBug: false,
- },
- {
- type: "Video",
- precondition: {
- content: "",
- text: "",
- },
- issue: "",
- isBug: false,
- },
- {
- type: "Audio",
- precondition: {
- content: "",
- text: "",
- },
- issue: "",
- isBug: false,
- },
- {
- type: "Note",
- precondition: {
- content: "",
- text: "",
- },
- issue: "",
- isBug: false,
- },
- {
- type: "File",
- precondition: {
- content: "",
- text: "",
- },
- issue: "",
- isBug: false,
- },
- {
- type: "Mindmap",
- precondition: {
- content: "",
- text: "",
- },
- issue: "",
- isBug: false,
- },
- ],
- checklist: {
- presession: {
- tasks: [],
- status: false,
- },
- postsession: {
- tasks: [],
- status: false,
- },
- },
- hotkeys: {
- general: {
- cancel: ["ctrl", "c"],
- save: ["ctrl", "s"],
- },
- home: {
- quickTest: ["ctrl", "q"],
- newExploratorySession: ["ctrl", "e"],
- openExploratorySession: ["ctrl", "o"],
- },
- sessionPlanning: {
- title: ["ctrl", "t"],
- charter: ["ctrl", "h"],
- timeLimit: ["ctrl", "l"],
- preconditions: ["ctrl", "p"],
- checklist: ["ctrl", "e"],
- start: "general.save",
- },
- workspace: {
- pause: ["ctrl", "p"],
- resume: "workspace.pause",
- stop: ["ctrl", "h"],
- videoStart: ["ctrl", "v"],
- videoStop: "workspace.videoStart",
- screenshot: ["ctrl", "r"],
- audioStart: ["ctrl", "a"],
- audioStop: "workspace.audioStart",
- note: ["ctrl", "n"],
- mindmap: ["ctrl", "m"],
- changeSource: ["ctrl", "o"],
- createIssue: ["ctrl", "i"],
- back: ["ctrl", "b"],
- }, // Dialogs on workspace use general.save and general.cancel
- evidence: {
- name: ["ctrl", "n"],
- followUp: ["ctrl", "f"],
- comment: ["ctrl", "d"],
- tags: ["ctrl", "t"],
- type: ["ctrl", "l"],
- save: "general.save",
- cancel: "general.cancel",
- },
- },
-};
-
-module.exports.initializeSession = () => {
- const sessionPath = path.join(configDir, "sessions");
- if (!fs.existsSync(sessionPath)) {
- createRootSessionDirectory();
- }
-
- metaDb = new JSONdb(path.join(configDir, "meta.json"), jsonDbConfig);
- let metadata = {
- version: currentVersion,
- };
- if (metaDb) {
- metadata = this.getMetadata();
- }
-
- if (!metadata.configPath) {
- metadata.configPath = defaultMeta.configPath;
- }
- configDb = new JSONdb(metadata.configPath, jsonDbConfig);
-
- if (!metadata.credentialsPath) {
- metadata.credentialsPath = defaultMeta.credentialsPath;
- }
- credentialDb = new JSONdb(metadata.credentialsPath, jsonDbConfig);
-
- if (metadata.sessionDataPath) {
- if (fs.existsSync(metadata.sessionDataPath)) {
- dataDb = new JSONdb(metadata.sessionDataPath, jsonDbConfig);
- } else {
- metaDb.set("sessionDataPath", "");
- }
- }
-
- if (metadata.version) {
- configVersion = metadata.version;
- } else {
- configVersion = "";
- }
-
- try {
- for (const [key, value] of Object.entries(metadata)) {
- metaDb.set(key, value);
- }
-
- if (!configDb.has("config")) {
- configDb.set("config", defaultConfig);
- } else {
- // Recursively ensure all keys that should exist, do.
- let currentConfig = configDb.get("config");
- const recursivelyMerge = (oldConfig, newConfig) => {
- if (!(oldConfig instanceof Object) || Array.isArray(oldConfig)) {
- if (!oldConfig || oldConfig.constructor !== newConfig.constructor) {
- // Overwriting if the type has changed in the default
- return newConfig;
- }
- return oldConfig;
- }
- if (!(newConfig instanceof Object) || Array.isArray(newConfig)) {
- return newConfig;
- }
-
- let builtConfig = {};
- for (const key of Object.keys(newConfig)) {
- builtConfig[key] = recursivelyMerge(
- oldConfig[key],
- newConfig[key],
- `path.${key}`
- );
- }
- for (const key of Object.keys(oldConfig)) {
- // Preserving keys in the config but not in default
- if (!Object.keys(newConfig).includes(key)) {
- builtConfig[key] = oldConfig[key];
- }
- }
- return builtConfig;
- };
- // TODO - Handle migrations after merge
- let fixedConfig = recursivelyMerge(currentConfig, defaultConfig);
- configDb.set("config", fixedConfig);
- }
-
- if (!credentialDb.has("credentials")) {
- credentialDb.set("credentials", {});
- }
- } catch (error) {
- console.log(error);
- }
-
- // TODO - Migrations for config files
- // Right now, it just overwrites.
- if (configVersion !== currentVersion) {
- let newMeta = this.getMetadata() || defaultMeta;
- newMeta.version = currentVersion;
- configVersion = currentVersion;
- for (const [key, value] of Object.entries(newMeta)) {
- metaDb.set(key, value);
- }
- }
-};
-
-const createRootSessionDirectory = () => {
- let sessionPaths = [path.join(configDir, "sessions")];
- sessionPaths.forEach((path) => {
- fs.mkdirSync(path, { recursive: true });
- });
-};
-
-const removeItemById = (id) => {
- const data = dataDb.get("items");
- const updatedData = data.filter((item) => item.id !== id);
- dataDb.set("items", updatedData);
-};
-
-const getItemById = (id) => {
- const data = dataDb.get("items");
- const item = data.find((item) => item.id === id);
-
- return item;
-};
-
-module.exports.createNewSession = (state) => {
- if (dataDb) {
- // TODO - ditching current session, should we save it or do something?
- }
- const sessionDataPath = path.join(
- configDir,
- "sessions",
- state.id,
- "sessionData.json"
- );
-
- metaDb.set("sessionDataPath", sessionDataPath);
- dataDb = new JSONdb(sessionDataPath, jsonDbConfig);
- dataDb.set("id", state.id);
- delete state.id;
- dataDb.set("state", state);
- dataDb.set("items", []);
- dataDb.set("notes", {
- content: "",
- text: "",
- });
-};
-
-module.exports.getSessionID = () => {
- try {
- if (dataDb) {
- return dataDb.get("id");
- }
- return "";
- } catch (error) {
- console.log(error);
- return "";
- }
-};
-
-module.exports.getState = () => {
- try {
- if (dataDb) {
- return dataDb.get("state");
- }
- return {};
- } catch (error) {
- console.log(error);
- return {};
- }
-};
-
-module.exports.updateState = (state) => {
- if (dataDb) {
- let currentState;
- try {
- currentState = dataDb.get("state");
- } catch (error) {
- console.log(error);
- currentState = {};
- }
- dataDb.set("state", {
- ...currentState,
- ...state,
- });
- }
-};
-
-module.exports.getItems = () => {
- if (dataDb) {
- try {
- return dataDb.get("items");
- } catch (error) {
- console.log(error);
- return [];
- }
- }
- return [];
-};
-
-module.exports.addItem = (item) => {
- try {
- let items = dataDb.get("items") || [];
- items.push(item);
- dataDb.set("items", items);
- browserWindow = browserUtility.getBrowserWindow();
- browserWindow.webContents.send("DATA_CHANGE");
- } catch (error) {
- console.log(error);
- }
-};
-
-module.exports.updateItems = (items) => {
- try {
- dataDb.set("items", items);
- browserWindow = browserUtility.getBrowserWindow();
- browserWindow.webContents.send("DATA_CHANGE");
- } catch (error) {
- console.log(error);
- }
-};
-
-module.exports.deleteItems = (ids) => {
- try {
- ids.map((id) => {
- removeItemById(id);
- });
- browserWindow = browserUtility.getBrowserWindow();
- browserWindow.webContents.send("DATA_CHANGE");
- return Promise.resolve({
- status: STATUSES.SUCCESS,
- message: "Element removed successfully",
- });
- } catch (error) {
- return Promise.resolve({ status: STATUSES.ERROR, message: error.message });
- }
-};
-
-module.exports.getItemById = (id) => {
- try {
- const data = getItemById(id);
- return data;
- } catch (error) {
- return null;
- }
-};
-
-module.exports.getConfig = () => {
- try {
- return configDb.get("config");
- } catch (error) {
- return {};
- }
-};
-
-module.exports.updateConfig = (config) => {
- try {
- configDb.set("config", config);
- browserWindow = browserUtility.getBrowserWindow();
- browserWindow.webContents.send("CONFIG_CHANGE");
- } catch (error) {
- console.log(error);
- }
-};
-
-module.exports.getCredentials = () => {
- try {
- return credentialDb.get("credentials");
- } catch (error) {
- return {};
- }
-};
-
-module.exports.updateCredentials = (credentials) => {
- try {
- credentialDb.set("credentials", credentials);
- browserWindow = browserUtility.getBrowserWindow();
- browserWindow.webContents.send("CREDENTIAL_CHANGE");
- } catch (error) {
- console.log(error);
- }
-};
-
-module.exports.getMetadata = () => {
- try {
- let metadata = {};
- for (const key of Object.keys(defaultMeta)) {
- metadata[key] = metaDb.get(key);
- }
- return metadata;
- } catch (error) {
- console.log(`Unable to retrieve metadata: ${error}`);
- return {};
- }
-};
-
-module.exports.updateMetadata = (meta) => {
- try {
- for (const [key, value] of Object.entries(meta)) {
- metaDb.set(key, value);
- }
- if (meta.configPath) {
- configDb = new JSONdb(meta.configPath, jsonDbConfig);
- }
- if (meta.credentialsPath) {
- credentialDb = new JSONdb(meta.credentialsPath, jsonDbConfig);
- }
- if (meta.sessionDataPath) {
- dataDb = new JSONdb(meta.sessionDataPath, jsonDbConfig);
- }
- browserWindow = browserUtility.getBrowserWindow();
- browserWindow.webContents.send("META_CHANGE");
- } catch (error) {
- console.log(error);
- }
-};
-
-module.exports.getNotes = () => {
- try {
- const data = dataDb.get("notes");
- return data;
- } catch (error) {
- return [];
- }
-};
-
-module.exports.updateNotes = (notes) => {
- try {
- dataDb.set("notes", notes);
- browserWindow = browserUtility.getBrowserWindow();
- browserWindow.webContents.send("DATA_CHANGE");
- } catch (error) {
- console.log(error);
- }
-};
-
-module.exports.resetData = () => {
- try {
- dataDb.set("items", []);
- dataDb.set("notes", {
- content: "",
- text: "",
- });
- browserWindow = browserUtility.getBrowserWindow();
- browserWindow.webContents.send("DATA_CHANGE");
- } catch (error) {
- console.log(error);
- }
-};
diff --git a/src/modules/FileSystemUtility.js b/src/modules/FileSystemUtility.js
index 0b9f390e..d6cdcddc 100644
--- a/src/modules/FileSystemUtility.js
+++ b/src/modules/FileSystemUtility.js
@@ -8,13 +8,13 @@ const uuidv4 = require("uuid");
const configDir = (app || remote.app).getPath("userData");
-const databaseUtility = require("./DatabaseUtility");
+const persistenceUtility = require("./PersistenceUtility");
const captureUtility = require("./CaptureUtility");
-const { STATUSES } = require("./constants");
+const { STATUSES, FILE_TYPES } = require("./constants");
module.exports.exportItems = async (ids) => {
const fileName =
- "yattie-export-" + dayjs().format("YYYY-MM-DD_HH-mm-ss-ms") + ".zip";
+ "pinata-export-" + dayjs().format("YYYY-MM-DD_HH-mm-ss-ms") + ".zip";
const { filePath } = await dialog.showSaveDialog({
title: "Save Items",
@@ -32,14 +32,14 @@ module.exports.exportItems = async (ids) => {
const zip = new AdmZip();
ids.map((id) => {
- const item = databaseUtility.getItemById(id);
+ const item = persistenceUtility.getItemById(id);
if (item.filePath) {
const sanitizedPath =
item.filePath.substring(item.filePath.length - 1) !== "?"
? item.filePath
: item.filePath.substring(0, item.filePath.length - 1);
- zip.addLocalFile(sanitizedPath, item.fileType);
+ zip.addLocalFile(sanitizedPath, FILE_TYPES[item.fileType]);
}
});
@@ -61,23 +61,16 @@ module.exports.exportItems = async (ids) => {
};
module.exports.createNewSession = async (state) => {
- const dataFolder = path.join(configDir, "sessions", state.id);
+ state.session.sessionID = uuidv4();
+ state.case.caseID = uuidv4();
+ const dataFolder = path.join(configDir, "sessions", state.session.sessionID);
if (!fs.existsSync(dataFolder)) {
fs.mkdirSync(dataFolder, { recursive: true });
}
- databaseUtility.createNewSession(state);
+ persistenceUtility.createNewSession(state);
};
module.exports.saveSession = async (data) => {
- const notesFileName =
- "yattie-session-" + dayjs().format("YYYY-MM-DD_HH-mm-ss-ms") + "-notes.txt";
- const notes = databaseUtility.getNotes();
- let notesFilePath = "";
- if (notes.text) {
- const notesItem = captureUtility.saveNote(notes, notesFileName)
- notesFilePath = notesItem.item.filePath;
- }
-
const fileName = "TestSession.test";
const { filePath } = await dialog.showSaveDialog({
title: "Save Session",
@@ -94,9 +87,8 @@ module.exports.saveSession = async (data) => {
}
return new Promise(function (resolve) {
- const items = databaseUtility.getItems();
- data.notes = notes;
- data.sessions = items;
+ const items = persistenceUtility.getItems();
+ data.session.items = items;
const metaPath = path.join(configDir, "metadata.txt");
const jsonStr = JSON.stringify(data);
const encodedStr = Buffer.from(jsonStr).toString("hex");
@@ -122,10 +114,6 @@ module.exports.saveSession = async (data) => {
}
});
- if (notesFilePath) {
- zip.addLocalFile(notesFilePath);
- }
-
zip.writeZip(filePath, (error) => {
if (error) {
return resolve({
@@ -155,7 +143,7 @@ module.exports.openSession = async () => {
if (canceled) {
return Promise.resolve({
status: STATUSES.ERROR,
- message: "No file selected",
+ message: "No file selected", // TODO i18n
});
}
@@ -170,7 +158,7 @@ module.exports.openSession = async () => {
const encoded = fs.readFileSync(metaPath, "utf8");
const state = JSON.parse(Buffer.from(encoded, "hex").toString());
- const id = state.id;
+ const id = state.id || uuidv4();
const dataFolder = path.join(configDir, "sessions", id);
if (fs.existsSync(dataFolder)) {
fs.rmSync(dataFolder, { recursive: true });
@@ -178,16 +166,16 @@ module.exports.openSession = async () => {
fs.renameSync(target, dataFolder);
const sessionDataPath = path.join(dataFolder, "sessionData.json");
- databaseUtility.updateMetadata({ sessionDataPath });
+ persistenceUtility.updateMetadata({ sessionDataPath });
// TODO - Should we restore state here or in Main and Default?
- databaseUtility.updateItems(state.sessions);
- delete state.sessions;
+ persistenceUtility.updateItems(state.session.items);
+ // delete state.sessions;
return Promise.resolve({
status: STATUSES.SUCCESS,
- message: "Session extracted successfully",
+ message: "Session extracted successfully", // TODO i18n
state: state,
});
} catch (err) {
@@ -200,24 +188,23 @@ module.exports.openSession = async () => {
module.exports.exportSession = async (params) => {
const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss-ms");
- const id = params.id;
- const notesFileName = "yattie-session-" + timestamp + "-notes.txt";
- const notes = databaseUtility.getNotes();
- let notesFilePath = "";
-
- if (notes.text) {
- const notesItem = captureUtility.saveNote(notes, notesFileName)
- notesFilePath = notesItem.item.filePath;
- console.log(notesFilePath);
- }
+ const id = persistenceUtility.getSessionID();
// show save dialog
- const fileName = "yattie-session-" + timestamp + ".zip";
- const { filePath } = await dialog.showSaveDialog({
- title: "Save Items",
+ const fileName =
+ params.type === "pdf"
+ ? "pinata-session-" + timestamp + ".pdf"
+ : "pinata-session-" + timestamp + ".zip";
+ const options = {
+ title: params.type === "pdf" ? "Save Pdf" : "Save Items",
defaultPath: fileName,
- filters: [{ name: "Zip archives only", extensions: ["zip"] }],
+ filters: [
+ params.type === "pdf"
+ ? { name: "Pdf files only", extensions: ["pdf"] }
+ : { name: "Zip archives only", extensions: ["zip"] },
+ ],
properties: ["createDirectory", "showOverwriteConfirmation"],
- });
+ };
+ const { filePath } = await dialog.showSaveDialog(options);
if (!filePath) {
return Promise.resolve({ status: "canceled" });
@@ -245,73 +232,146 @@ module.exports.exportSession = async (params) => {
pdfWin.webContents.on("did-finish-load", () => {
pdfWin.webContents.send("ACTIVE_PDF", params);
- pdfWin.webContents
- .printToPDF({})
- .then((data) => {
- const pdfName = "yattie-session-" + timestamp + "-report.pdf";
- const pdfPath = path.join(configDir, "sessions", id, pdfName);
-
- fs.writeFile(pdfPath, data, (error) => {
- if (error) {
- return Promise.resolve({
- status: STATUSES.ERROR,
- message: error,
- });
- }
- try {
- const zip = new AdmZip();
-
- const items = databaseUtility.getItems();
-
- items.map((item) => {
- if (item.filePath) {
- const sanitizedPath =
- item.filePath.substring(item.filePath.length - 1) !== "?"
- ? item.filePath
- : item.filePath.substring(0, item.filePath.length - 1);
- zip.addLocalFile(sanitizedPath, item.fileType);
+ setTimeout(() => {
+ pdfWin.webContents
+ .printToPDF({})
+ .then((data) => {
+ const pdfName = "pinata-session-" + timestamp + "-report.pdf";
+ const pdfPath = path.join(configDir, "sessions", id, pdfName);
+ if (params.type === "pdf") {
+ fs.writeFile(filePath, data, (error) => {
+ if (error) {
+ return Promise.resolve({
+ status: STATUSES.ERROR,
+ message: error,
+ });
}
});
-
- if (notesFilePath) {
- zip.addLocalFile(notesFilePath, "text");
- }
-
- zip.addLocalFile(pdfPath);
-
- zip.writeZip(filePath, (error) => {
+ } else {
+ fs.writeFile(pdfPath, data, (error) => {
if (error) {
return Promise.resolve({
status: STATUSES.ERROR,
message: error,
});
}
+ try {
+ const zip = new AdmZip();
+
+ const items = persistenceUtility.getItems();
+ items.map((item) => {
+ if (item.filePath) {
+ const sanitizedPath =
+ item.filePath.substring(item.filePath.length - 1) !== "?"
+ ? item.filePath
+ : item.filePath.substring(0, item.filePath.length - 1);
+ zip.addLocalFile(sanitizedPath, FILE_TYPES[item.fileType]);
+ }
+ });
- return Promise.resolve({
- status: STATUSES.SUCCESS,
- message: "Session Exported Successfully",
- filePath,
- });
- });
- } catch (error) {
- return Promise.resolve({
- status: STATUSES.ERROR,
- message: error,
+ zip.addLocalFile(pdfPath);
+ if (params.logoPath) zip.addLocalFile(params.logoPath);
+ zip.writeZip(filePath, (error) => {
+ if (error) {
+ return Promise.resolve({
+ status: STATUSES.ERROR,
+ message: error,
+ });
+ }
+
+ return Promise.resolve({
+ status: STATUSES.SUCCESS,
+ message: "Session Exported Successfully",
+ filePath,
+ });
+ });
+ } catch (error) {
+ return Promise.resolve({
+ status: STATUSES.ERROR,
+ message: error,
+ });
+ }
});
}
+ })
+ .catch((error) => {
+ return Promise.resolve({
+ status: STATUSES.ERROR,
+ message: error,
+ });
});
- })
- .catch((error) => {
- return Promise.resolve({
- status: STATUSES.ERROR,
- message: error,
- });
+ }, 4000);
+ });
+};
+
+const deleteFolder = function (folderPath) {
+ if (fs.existsSync(folderPath) && fs.lstatSync(folderPath).isDirectory()) {
+ fs.readdirSync(folderPath).forEach((file, index) => {
+ const curPath = path.join(folderPath, file);
+ if (fs.lstatSync(curPath).isDirectory()) {
+ deleteFolder(curPath);
+ } else {
+ fs.unlinkSync(curPath);
+ }
+ });
+ fs.rmdirSync(folderPath);
+ return true;
+ } else {
+ return false;
+ }
+};
+
+const deleteOldFiles = (directoryPath, retentionPeriod) => {
+ fs.readdir(directoryPath, (err, files) => {
+ if (err) {
+ console.error("Error reading directory:", err);
+ return false;
+ }
+
+ files.forEach((file) => {
+ const filePath = path.join(directoryPath, file);
+
+ fs.stat(filePath, (err, stats) => {
+ if (err) {
+ console.error("Error getting file stats:", err);
+ return false;
+ }
+
+ const currentTime = new Date();
+ const fileModifiedTime = new Date(stats.mtime);
+
+ const timeDifference = currentTime - fileModifiedTime;
+ const daysDifference = timeDifference / (1000 * 60 * 60 * 24);
+ if (daysDifference > retentionPeriod) {
+ let status = deleteFolder(filePath);
+ if (!status) return false;
+ }
});
+ });
+ });
+ return true;
+};
+
+module.exports.deleteSession = async (type) => {
+ let status;
+ const metadata = persistenceUtility.getMetadata();
+ if (type === "all") status = deleteFolder(metadata.sessionPath);
+ else {
+ let config = persistenceUtility.getConfig();
+ status = deleteOldFiles(metadata.sessionPath, config.cache.retentionPeriod);
+ }
+ if (status)
+ return Promise.resolve({
+ status: STATUSES.SUCCESS,
+ message: "Session deleted successfully", // TODO i18n
+ });
+ return Promise.resolve({
+ status: STATUSES.ERROR,
+ message: "Session deleted failed", // TODO i18n
});
};
module.exports.openConfigFile = async () => {
- console.log("openConfigFile");
const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ["openFile"],
filters: [{ name: "Config File", extensions: ["json"] }],
@@ -333,9 +393,9 @@ module.exports.openConfigFile = async () => {
}
});
- const metadata = databaseUtility.getMetadata();
+ const metadata = persistenceUtility.getMetadata();
metadata.configPath = filePath;
- databaseUtility.updateMetadata(metadata);
+ persistenceUtility.updateMetadata(metadata);
return Promise.resolve({
status: STATUSES.SUCCESS,
message: "Config file imported successfully",
@@ -349,7 +409,6 @@ module.exports.openConfigFile = async () => {
};
module.exports.openCredentialsFile = async () => {
- console.log("openCredentialsFile");
const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ["openFile"],
filters: [{ name: "Credentials File", extensions: ["json"] }],
@@ -371,9 +430,9 @@ module.exports.openCredentialsFile = async () => {
}
});
- const metadata = databaseUtility.getMetadata();
+ const metadata = persistenceUtility.getMetadata();
metadata.credentialsPath = filePath;
- databaseUtility.updateMetadata(metadata);
+ persistenceUtility.updateMetadata(metadata);
return Promise.resolve({
status: STATUSES.SUCCESS,
message: "Credentials file imported successfully",
diff --git a/src/modules/IpcHandlers.js b/src/modules/IpcHandlers.js
index b5452099..3222d225 100644
--- a/src/modules/IpcHandlers.js
+++ b/src/modules/IpcHandlers.js
@@ -1,11 +1,12 @@
import { ipcMain } from "electron";
import { IPC_HANDLERS, IPC_FUNCTIONS } from "./constants";
const captureUtility = require("./CaptureUtility");
-const databaseUtility = require("./DatabaseUtility");
+const persistenceUtility = require("./PersistenceUtility");
const fileSystemUtility = require("./FileSystemUtility");
const menuUtility = require("./MenuUtility");
const windowUtility = require("./WindowUtility");
const serverUtility = require("./ServerUtility");
+const systemInfoUtility = require("./SystemInfoUtility");
ipcMain.handle(IPC_HANDLERS.BROWSER, async (event, args) => {
switch (args.func) {
@@ -18,8 +19,6 @@ ipcMain.handle(IPC_HANDLERS.CAPTURE, async (event, args) => {
switch (args.func) {
case IPC_FUNCTIONS.GET_MEDIA_SOURCE:
return captureUtility.getMediaSource();
- case IPC_FUNCTIONS.SAVE_NOTE:
- return captureUtility.saveNote(args.data);
case IPC_FUNCTIONS.CREATE_IMAGE:
return captureUtility.createImage(args.data);
case IPC_FUNCTIONS.UPDATE_IMAGE:
@@ -51,22 +50,14 @@ ipcMain.handle(IPC_HANDLERS.WINDOW, async (event, args) => {
switch (args.func) {
case IPC_FUNCTIONS.SET_DEV_MODE:
return windowUtility.setDevMode(args.data);
- case IPC_FUNCTIONS.OPEN_ADD_WINDOW:
- return windowUtility.openAddWindow(args.data);
- case IPC_FUNCTIONS.CLOSE_ADD_WINDOW:
- return windowUtility.closeAddWindow(args.data);
- case IPC_FUNCTIONS.OPEN_EDIT_WINDOW:
- return windowUtility.openEditWindow(args.data);
- case IPC_FUNCTIONS.CLOSE_EDIT_WINDOW:
- return windowUtility.closeEditWindow(args.data);
case IPC_FUNCTIONS.OPEN_SETTING_WINDOW:
return windowUtility.openSettingWindow(args.data);
case IPC_FUNCTIONS.CLOSE_SETTING_WINDOW:
return windowUtility.closeSettingWindow(args.data);
- case IPC_FUNCTIONS.OPEN_MINIMIZE_WINDOW:
- return windowUtility.openMinimizeWindow(args.data);
- case IPC_FUNCTIONS.CLOSE_MINIMIZE_WINDOW:
- return windowUtility.closeMinimizeWindow(args.data);
+ case IPC_FUNCTIONS.OPEN_LOWPROFILE_WINDOW:
+ return windowUtility.openLowProfileWindow(args.data);
+ case IPC_FUNCTIONS.CLOSE_LOWPROFILE_WINDOW:
+ return windowUtility.closeLowProfileWindow(args.data);
case IPC_FUNCTIONS.CLOSE_SESSION_AND_MINIIMIZED_WINDOW:
return windowUtility.closeSessionAndMinimizedWindow(args.data);
case IPC_FUNCTIONS.SET_WINDOW_SIZE:
@@ -75,55 +66,57 @@ ipcMain.handle(IPC_HANDLERS.WINDOW, async (event, args) => {
return windowUtility.openModalWindow(args.data);
case IPC_FUNCTIONS.CLOSE_MODAL_WINDOW:
return windowUtility.closeModalWindow(args.data);
- case IPC_FUNCTIONS.OPEN_NOTES_WINDOW:
- return windowUtility.openNotesWindow(args.data);
- case IPC_FUNCTIONS.CLOSE_NOTES_WINDOW:
- return windowUtility.closeNotesWindow();
case IPC_FUNCTIONS.MOVE_WINDOW:
return windowUtility.moveWindow(args.data);
+ case IPC_FUNCTIONS.RESET_SESSION:
+ return windowUtility.resetSession(args.data);
default:
return null;
}
});
-ipcMain.handle(IPC_HANDLERS.DATABASE, async (event, args) => {
+ipcMain.handle(IPC_HANDLERS.PERSISTENCE, async (event, args) => {
switch (args.func) {
case IPC_FUNCTIONS.INITIALIZE_SESSION:
- return databaseUtility.initializeSession();
+ return persistenceUtility.initializeSession();
case IPC_FUNCTIONS.GET_SESSION_ID:
- return databaseUtility.getSessionID();
+ return persistenceUtility.getSessionID();
+ case IPC_FUNCTIONS.GET_CASE_ID:
+ return persistenceUtility.getCaseID();
case IPC_FUNCTIONS.GET_STATE:
- return databaseUtility.getState();
+ return persistenceUtility.getState();
case IPC_FUNCTIONS.UPDATE_STATE:
- return databaseUtility.updateState(args.data);
+ return persistenceUtility.updateState(args.data);
case IPC_FUNCTIONS.ADD_ITEM:
- return databaseUtility.addItem(args.data);
+ return persistenceUtility.addItem(args.data);
case IPC_FUNCTIONS.GET_ITEMS:
- return databaseUtility.getItems();
+ return persistenceUtility.getItems();
+ case IPC_FUNCTIONS.UPDATE_ITEM:
+ return persistenceUtility.updateItem(args.data);
case IPC_FUNCTIONS.UPDATE_ITEMS:
- return databaseUtility.updateItems(args.data);
+ return persistenceUtility.updateItems(args.data);
case IPC_FUNCTIONS.DELETE_ITEMS:
- return databaseUtility.deleteItems(args.data);
+ return persistenceUtility.deleteItems(args.data);
case IPC_FUNCTIONS.GET_ITEM_BY_ID:
- return databaseUtility.getItemById(args.data);
+ return persistenceUtility.getItemById(args.data);
case IPC_FUNCTIONS.GET_CONFIG:
- return databaseUtility.getConfig(args.data);
+ return persistenceUtility.getConfig(args.data);
case IPC_FUNCTIONS.UPDATE_CONFIG:
- return databaseUtility.updateConfig(args.data);
+ return persistenceUtility.updateConfig(args.data);
case IPC_FUNCTIONS.GET_CREDENTIALS:
- return databaseUtility.getCredentials(args.data);
+ return persistenceUtility.getCredentials(args.data);
case IPC_FUNCTIONS.UPDATE_CREDENTIALS:
- return databaseUtility.updateCredentials(args.data);
+ return persistenceUtility.updateCredentials(args.data);
case IPC_FUNCTIONS.GET_METADATA:
- return databaseUtility.getMetadata(args.data);
+ return persistenceUtility.getMetadata(args.data);
case IPC_FUNCTIONS.UPDATE_METADATA:
- return databaseUtility.updateMetadata(args.data);
+ return persistenceUtility.updateMetadata(args.data);
case IPC_FUNCTIONS.UPDATE_NOTES:
- return databaseUtility.updateNotes(args.data);
+ return persistenceUtility.updateNotes(args.data);
case IPC_FUNCTIONS.GET_NOTES:
- return databaseUtility.getNotes(args.data);
+ return persistenceUtility.getNotes(args.data);
case IPC_FUNCTIONS.RESET_DATA:
- return databaseUtility.resetData(args.data);
+ return persistenceUtility.resetData(args.data);
default:
return null;
}
@@ -135,12 +128,15 @@ ipcMain.handle(IPC_HANDLERS.FILE_SYSTEM, async (event, args) => {
return fileSystemUtility.exportItems(args.data);
case IPC_FUNCTIONS.CREATE_NEW_SESSION:
return fileSystemUtility.createNewSession(args.data);
+ case IPC_FUNCTIONS.RESET_SESSION:
case IPC_FUNCTIONS.SAVE_SESSION:
return fileSystemUtility.saveSession(args.data);
case IPC_FUNCTIONS.OPEN_SESSION:
return fileSystemUtility.openSession(args.data);
case IPC_FUNCTIONS.EXPORT_SESSION:
return fileSystemUtility.exportSession(args.data);
+ case IPC_FUNCTIONS.DELETE_SESSION:
+ return fileSystemUtility.deleteSession(args.data);
case IPC_FUNCTIONS.OPEN_CONFIG_FILE:
return fileSystemUtility.openConfigFile(args.data);
case IPC_FUNCTIONS.OPEN_CREDENTIALS_FILE:
@@ -169,3 +165,10 @@ ipcMain.handle(IPC_HANDLERS.SERVER, async (event, args) => {
return serverUtility.stopServer(args.data);
}
});
+
+ipcMain.handle(IPC_HANDLERS.SYSTEMINFO, async (event, args) => {
+ switch (args.func) {
+ case IPC_FUNCTIONS.GET_SYSTEM_INFO:
+ return await systemInfoUtility.getSystemInfo();
+ }
+});
diff --git a/src/modules/PersistenceUtility.js b/src/modules/PersistenceUtility.js
new file mode 100644
index 00000000..16b3d4f7
--- /dev/null
+++ b/src/modules/PersistenceUtility.js
@@ -0,0 +1,740 @@
+const JSONdb = require("simple-json-db");
+const { app, remote } = require("electron");
+const path = require("path");
+const fs = require("fs");
+const browserUtility = require("./BrowserWindowUtility");
+const { STATUSES } = require("./constants");
+
+const configDir = (app || remote.app).getPath("userData");
+const jsonDbConfig = {
+ jsonSpaces: 2,
+};
+
+const currentVersion = app.getVersion();
+
+let metaDb, configDb, credentialDb, dataDb;
+let browserWindow;
+
+const defaultMeta = {
+ configPath: path.join(configDir, "config.json"),
+ credentialsPath: path.join(configDir, "credentials.json"),
+ sessionPath: path.join(configDir, "sessions"),
+ sessionDataPath: "",
+ version: currentVersion,
+};
+
+const defaultConfig = {
+ localOnly: false,
+ theme: "light",
+ defaultColor: "#1976D2FF",
+ commentType: "Comment",
+ audioCapture: false,
+ videoQuality: "high",
+ debugMode: false,
+ summaryRequired: false,
+ ai: {
+ enabled: false,
+ },
+ defaultTags: [],
+ templates: {
+ image: {
+ content: "",
+ text: "",
+ },
+ video: {
+ content: "",
+ text: "",
+ },
+ audio: {
+ content: "",
+ text: "",
+ },
+ text: {
+ content: "",
+ text: "",
+ },
+ file: {
+ content: "",
+ text: "",
+ },
+ mindmap: {
+ content: "",
+ text: "",
+ },
+ },
+ checklist: {
+ presession: {
+ tasks: [],
+ status: false,
+ },
+ postsession: {
+ tasks: [],
+ status: false,
+ },
+ },
+ hotkeys: {
+ general: {
+ cancel: ["ctrl", "c"],
+ save: ["ctrl", "s"],
+ },
+ home: {
+ quickTest: ["ctrl", "q"],
+ newExploratorySession: ["ctrl", "e"],
+ openExploratorySession: ["ctrl", "o"],
+ },
+ sessionPlanning: {
+ title: ["ctrl", "t"],
+ charter: ["ctrl", "h"],
+ timeLimit: ["ctrl", "l"],
+ preconditions: ["ctrl", "p"],
+ checklist: ["ctrl", "e"],
+ start: "general.save",
+ },
+ workspace: {
+ pause: ["ctrl", "p"],
+ resume: "workspace.pause",
+ stop: ["ctrl", "h"],
+ videoStart: ["ctrl", "v"],
+ videoStop: "workspace.videoStart",
+ screenshot: ["ctrl", "r"],
+ audioStart: ["ctrl", "a"],
+ audioStop: "workspace.audioStart",
+ note: ["ctrl", "n"],
+ mindmap: ["ctrl", "m"],
+ changeSource: ["ctrl", "o"],
+ createIssue: ["ctrl", "i"],
+ back: ["ctrl", "b"],
+ copy: ["alt", "c"],
+ paste: ["alt", "v"],
+ edit: ["alt", "e"],
+ delete: ["del"],
+ }, // Dialogs on workspace use general.save and general.cancel
+ evidence: {
+ name: ["ctrl", "n"],
+ followUp: ["ctrl", "f"],
+ comment: ["ctrl", "d"],
+ tags: ["ctrl", "t"],
+ type: ["ctrl", "y"],
+ save: "general.save",
+ cancel: "general.cancel",
+ },
+ },
+ version: currentVersion,
+ logo: {
+ enabled: false,
+ path: "",
+ name: "",
+ size: 0,
+ },
+ cache: {
+ retentionPeriod: 7,
+ },
+ colors: {
+ shapeColor: "#101828",
+ markerColor: "#101828",
+ connectorColor: "#101828",
+ textColor: "#101828",
+ },
+};
+
+module.exports.initializeSession = () => {
+ const sessionPath = path.join(configDir, "sessions");
+ if (!fs.existsSync(sessionPath)) {
+ createRootSessionDirectory();
+ }
+
+ metaDb = new JSONdb(path.join(configDir, "meta.json"), jsonDbConfig);
+ let metadata = {
+ version: currentVersion,
+ };
+ if (metaDb) {
+ metadata = this.getMetadata();
+ }
+
+ metadata = applyMigrations("meta", currentVersion, metadata);
+
+ if (!metadata.configPath) {
+ metadata.configPath = defaultMeta.configPath;
+ }
+ configDb = new JSONdb(metadata.configPath, jsonDbConfig);
+
+ const configData = applyMigrations("config", currentVersion, configDb.JSON());
+
+ if (!metadata.credentialsPath) {
+ metadata.credentialsPath = defaultMeta.credentialsPath;
+ }
+ credentialDb = new JSONdb(metadata.credentialsPath, jsonDbConfig);
+
+ const credentialData = applyMigrations(
+ "credentials",
+ currentVersion,
+ credentialDb.JSON()
+ );
+
+ let sessionData;
+ if (metadata.sessionDataPath) {
+ if (fs.existsSync(metadata.sessionDataPath)) {
+ dataDb = new JSONdb(metadata.sessionDataPath, jsonDbConfig);
+ sessionData = applyMigrations("data", currentVersion, dataDb.JSON());
+ } else {
+ metaDb.set("sessionDataPath", "");
+ }
+ }
+
+ try {
+ metaDb.JSON(metadata);
+ metaDb.sync();
+
+ configDb.JSON(configData);
+ configDb.sync();
+
+ credentialDb.JSON(credentialData);
+ credentialDb.sync();
+
+ if (sessionData) {
+ dataDb.JSON(sessionData);
+ dataDb.sync();
+ }
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+const recursivelyMerge = (oldConfig, newConfig) => {
+ if (!(oldConfig instanceof Object) || Array.isArray(oldConfig)) {
+ if (!oldConfig || oldConfig.constructor !== newConfig.constructor) {
+ // Overwriting if the type has changed in the default
+ return newConfig;
+ }
+ return oldConfig;
+ }
+ if (!(newConfig instanceof Object) || Array.isArray(newConfig)) {
+ return newConfig;
+ }
+
+ let builtConfig = {};
+ for (const key of Object.keys(newConfig)) {
+ builtConfig[key] = recursivelyMerge(
+ oldConfig[key],
+ newConfig[key],
+ `path.${key}`
+ );
+ }
+ for (const key of Object.keys(oldConfig)) {
+ // Preserving keys in the config but not in default
+ if (!Object.keys(newConfig).includes(key)) {
+ builtConfig[key] = oldConfig[key];
+ }
+ }
+ return builtConfig;
+};
+
+const applyMigrations = (type, newVersion, data) => {
+ let oldVersion = data.version || "0.0.0";
+ let migratedData = Object.assign(data, {});
+
+ if (newVersion !== oldVersion) {
+ // Split newVersion and oldVersion to compare
+ let splitNewVersion = newVersion.substring(1).split(".");
+ splitNewVersion = splitNewVersion.map((num) => parseInt(num));
+ let splitDataVersion = oldVersion.split(".");
+ splitDataVersion = splitDataVersion.map((num) => parseInt(num));
+ let direction = "up";
+ for (let i = 0; i < splitNewVersion.length; i++) {
+ if (splitNewVersion[i] < splitDataVersion[i]) {
+ direction = "down";
+ break;
+ }
+ }
+
+ // Read migration files
+
+ const isDevelopment = process.env.NODE_ENV !== "production";
+ let migrationFilesPath = isDevelopment
+ ? path.resolve(__dirname, "../src/modules/migrations/")
+ : path.resolve(process.resourcesPath, "./migrations/");
+ let migrationFiles = fs.readdirSync(migrationFilesPath);
+ let migrationVersions = migrationFiles.map((fileName) => {
+ let temp = fileName.substring(1, fileName.length - 3).split(".");
+ return temp.map((num) => parseInt(num));
+ });
+ // List is in order from lowest to highest
+ if (direction === "down") {
+ migrationFiles.reverse();
+ migrationVersions.reverse();
+ }
+
+ // Find the the next migration to run.
+ let nextMigrationIndex;
+ for (let i = 0; i < migrationVersions.length; i++) {
+ if (direction === "up") {
+ if (migrationVersions[i][0] < splitDataVersion[0]) {
+ continue;
+ }
+ if (migrationVersions[i][0] === splitDataVersion[0]) {
+ if (migrationVersions[i][1] < splitDataVersion[1]) {
+ continue;
+ }
+
+ if (migrationVersions[i][1] > splitDataVersion[1]) {
+ nextMigrationIndex = i;
+ break;
+ }
+
+ if (migrationVersions[i][1] === splitDataVersion[1]) {
+ if (migrationVersions[i][2] <= splitDataVersion[2]) {
+ continue;
+ } else {
+ nextMigrationIndex = i;
+ break;
+ }
+ }
+ }
+ if (migrationVersions[i][0] > splitDataVersion[0]) {
+ nextMigrationIndex = i;
+ break;
+ }
+ } else {
+ if (migrationVersions[i][0] > splitDataVersion[0]) {
+ continue;
+ }
+ if (migrationVersions[i][0] === splitDataVersion[0]) {
+ if (migrationVersions[i][1] > splitDataVersion[1]) {
+ continue;
+ }
+
+ if (migrationVersions[i][1] < splitDataVersion[1]) {
+ nextMigrationIndex = i;
+ break;
+ }
+
+ if (migrationVersions[i][1] === splitDataVersion[1]) {
+ if (migrationVersions[i][2] >= splitDataVersion[2]) {
+ continue;
+ } else {
+ nextMigrationIndex = i;
+ break;
+ }
+ }
+ }
+ if (migrationVersions[i][0] < splitDataVersion[0]) {
+ nextMigrationIndex = i;
+ break;
+ }
+ }
+ }
+
+ // Run migrations in order
+ for (const migration of migrationFiles.slice(
+ nextMigrationIndex,
+ migrationFiles.length
+ )) {
+ const { migrationStruct } = require(`./migrations/${migration}`);
+ if (!migrationStruct[direction][type]) continue;
+
+ // Order of operations here - move first, then functions
+ let moveUpMigrations = {};
+ let moveLateralMigrations = {};
+ let otherMigrations = {};
+ for (const [key, value] of Object.entries(
+ migrationStruct[direction][type]
+ )) {
+ if (
+ value === ".." ||
+ (value.length > 1 && value.split(".").length > 1)
+ ) {
+ moveUpMigrations[key] = value;
+ } else if (value.constructor === String) {
+ moveLateralMigrations[key] = value;
+ } else {
+ otherMigrations[key] = value;
+ }
+ }
+
+ migratedData = migrateKeys(moveUpMigrations, migratedData);
+ migratedData = migrateKeys(moveLateralMigrations, migratedData);
+ migratedData = migrateKeys(otherMigrations, migratedData);
+ }
+ }
+
+ let updatedData = migratedData;
+ switch (type) {
+ case "meta":
+ updatedData = recursivelyMerge(migratedData, defaultMeta);
+ break;
+ case "config":
+ updatedData = recursivelyMerge(migratedData, defaultConfig);
+ break;
+ }
+ updatedData.version = newVersion;
+
+ return updatedData;
+};
+
+const migrateKeys = (migrations, data) => {
+ // Apply migration transformations
+ for (const [key, value] of Object.entries(migrations)) {
+ if (value.constructor === String) {
+ if (data[key]) {
+ if (value === "..") {
+ for (const [subKey, subValue] of Object.entries(data[key])) {
+ data[subKey] = subValue;
+ }
+ } else if (value.length > 1 && value.split(".") > 1) {
+ if (/^[A-za-z0-9.-_]+$/.test(value)) {
+ eval(`data.${value} = data[key];`);
+ } else {
+ console.log(`Invalid migration value [${value}] skipping...`);
+ }
+ } else if (value !== "") {
+ data[value] = data[key];
+ }
+ delete data[key];
+ }
+ } else if (value.constructor === Function) {
+ if (data[key]) {
+ data[key] = value(data[key]);
+ }
+ }
+ }
+ return data;
+};
+
+const createRootSessionDirectory = () => {
+ let sessionPaths = [path.join(configDir, "sessions")];
+ sessionPaths.forEach((path) => {
+ fs.mkdirSync(path, { recursive: true });
+ });
+};
+
+const removeItemById = (id) => {
+ let session = dataDb.get("session");
+ const items = session.items;
+ session.items = items.filter((item) => item.stepID !== id);
+ dataDb.set("session", session);
+};
+
+const getItemById = (id) => {
+ const session = dataDb.get("session");
+ const item = session.items.find((item) => item.stepID === id);
+ return item;
+};
+
+module.exports.createNewSession = (state) => {
+ const sessionDataPath = path.join(
+ configDir,
+ "sessions",
+ state.session.sessionID,
+ "sessionData.json"
+ );
+
+ metaDb.set("sessionDataPath", sessionDataPath);
+ dataDb = new JSONdb(sessionDataPath, jsonDbConfig);
+ let session = state.session;
+ session.items = [];
+ session.notes = {
+ content: "",
+ text: "",
+ };
+ dataDb.set("session", session);
+ dataDb.set("case", state.case);
+ dataDb.set("version", currentVersion);
+};
+
+module.exports.getSessionID = () => {
+ try {
+ if (dataDb) {
+ const session = dataDb.get("session");
+ return session.sessionID;
+ }
+ return "";
+ } catch (error) {
+ console.log(error);
+ return "";
+ }
+};
+
+module.exports.getCaseID = () => {
+ try {
+ if (dataDb) {
+ const cas = dataDb.get("case");
+ return cas.caseID;
+ }
+ return "";
+ } catch (error) {
+ console.log(error);
+ return "";
+ }
+};
+
+module.exports.getState = () => {
+ try {
+ if (dataDb) {
+ return {
+ case: dataDb.get("case"),
+ session: dataDb.get("session"),
+ version: dataDb.get("version"),
+ };
+ }
+ return {};
+ } catch (error) {
+ console.log(error);
+ return {};
+ }
+};
+
+// You must pass the entire state to use this.
+module.exports.updateState = (state) => {
+ if (dataDb) {
+ let session, cse;
+ try {
+ cse = dataDb.get("case");
+ session = dataDb.get("session");
+ } catch (error) {
+ console.log(error);
+ cse = cse || {};
+ session = session || {};
+ }
+
+ dataDb.set("case", { ...cse, ...state.case });
+ dataDb.set("session", { ...session, ...state.session });
+ }
+};
+
+module.exports.getItems = () => {
+ if (dataDb) {
+ try {
+ const session = dataDb.get("session");
+ return session.items;
+ } catch (error) {
+ console.log(error);
+ return [];
+ }
+ }
+ return [];
+};
+
+module.exports.addItem = (item) => {
+ try {
+ let session = dataDb.get("session");
+ let items = session.items || [];
+ items.push(item);
+ session.items = items;
+ dataDb.set("session", session);
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("DATA_CHANGE");
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+module.exports.updateItem = (newItem) => {
+ try {
+ debugger;
+ let session = dataDb.get("session");
+ let items = session.items.map((item) => {
+ if (item.stepID === newItem.stepID) {
+ return newItem;
+ }
+ return item;
+ });
+ session.items = items;
+ dataDb.set("session", session);
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("DATA_CHANGE");
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+module.exports.updateItems = (items) => {
+ try {
+ let session = dataDb.get("session");
+ if (!session) {
+ dataDb.set("session", {});
+ session = {};
+ }
+ session.items = items;
+ dataDb.set("session", session);
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("DATA_CHANGE");
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+module.exports.deleteItems = (ids) => {
+ try {
+ ids.map((id) => {
+ removeItemById(id);
+ });
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("DATA_CHANGE");
+ return Promise.resolve({
+ status: STATUSES.SUCCESS,
+ message: "Element removed successfully",
+ });
+ } catch (error) {
+ return Promise.resolve({ status: STATUSES.ERROR, message: error.message });
+ }
+};
+
+module.exports.getItemById = (id) => {
+ try {
+ const data = getItemById(id);
+ return data;
+ } catch (error) {
+ return null;
+ }
+};
+
+module.exports.getConfig = () => {
+ try {
+ return configDb.JSON();
+ } catch (error) {
+ return {};
+ }
+};
+
+module.exports.updateConfig = (config) => {
+ try {
+ configDb.JSON(config);
+ configDb.sync();
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("CONFIG_CHANGE");
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+module.exports.getCredentials = () => {
+ try {
+ // eslint-disable-next-line no-unused-vars
+ const { version: _, ...credentials } = credentialDb.JSON();
+ return credentials;
+ } catch (error) {
+ return {};
+ }
+};
+
+module.exports.updateCredentials = (credentials) => {
+ try {
+ credentialDb.JSON(credentials);
+ credentialDb.sync();
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("CREDENTIAL_CHANGE");
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+module.exports.getMetadata = () => {
+ try {
+ return metaDb.JSON();
+ } catch (error) {
+ console.log(`Unable to retrieve metadata: ${error}`);
+ return {};
+ }
+};
+
+module.exports.updateMetadata = (meta) => {
+ try {
+ for (const [key, value] of Object.entries(meta)) {
+ metaDb.set(key, value);
+ }
+ if (meta.configPath) {
+ configDb = new JSONdb(meta.configPath, jsonDbConfig);
+ }
+ if (meta.credentialsPath) {
+ credentialDb = new JSONdb(meta.credentialsPath, jsonDbConfig);
+ }
+ if (meta.sessionDataPath) {
+ dataDb = new JSONdb(meta.sessionDataPath, jsonDbConfig);
+ }
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("META_CHANGE");
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+module.exports.getNotes = () => {
+ try {
+ const session = dataDb.get("session");
+ return session.notes;
+ } catch (error) {
+ return [];
+ }
+};
+
+module.exports.updateNotes = (notes) => {
+ try {
+ let session = dataDb.get("session");
+ session.notes = notes;
+ dataDb.set("session", session);
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("DATA_CHANGE");
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+module.exports.getNodes = () => {
+ try {
+ const session = dataDb.get("session");
+ return session.nodes;
+ } catch (error) {
+ return [];
+ }
+};
+
+module.exports.updateNodes = (nodes) => {
+ try {
+ let session = dataDb.get("session");
+ session.nodes = nodes;
+ dataDb.set("session", session);
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("DATA_CHANGE");
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+module.exports.getConnections = () => {
+ try {
+ const session = dataDb.get("session");
+ return session.connections;
+ } catch (error) {
+ return [];
+ }
+};
+
+module.exports.updateConnections = (connections) => {
+ try {
+ let session = dataDb.get("session");
+ session.connections = connections;
+ dataDb.set("session", session);
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("DATA_CHANGE");
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+module.exports.resetData = () => {
+ if (fs.existsSync(metaDb.get("sessionDataPath"))) {
+ try {
+ let session = dataDb.get("session");
+ session.items = [];
+ session.notes = {
+ content: "",
+ text: "",
+ };
+ dataDb.set("session", session);
+ browserWindow = browserUtility.getBrowserWindow();
+ browserWindow.webContents.send("DATA_CHANGE");
+ } catch (error) {
+ console.log(error);
+ }
+ }
+};
diff --git a/src/modules/SystemInfoUtility.js b/src/modules/SystemInfoUtility.js
new file mode 100644
index 00000000..8f382f53
--- /dev/null
+++ b/src/modules/SystemInfoUtility.js
@@ -0,0 +1,66 @@
+const os = require("os");
+const si = require("systeminformation");
+
+async function getCurrentDateTime() {
+ return new Date().toLocaleString("en-US", { timeZone: "UTC" });
+}
+
+async function getComputerName() {
+ return os.hostname();
+}
+
+async function getOperatingSystem() {
+ const osInfo = await si.osInfo();
+ return `${osInfo.distro} ${osInfo.release} ${osInfo.arch}`;
+}
+
+async function getSystemInfo() {
+ const systemInfo = await si.system();
+ return {
+ manufacturer: systemInfo.manufacturer,
+ model: systemInfo.model,
+ };
+}
+
+async function getBIOSVersion() {
+ const biosInfo = await si.bios();
+ return biosInfo.version;
+}
+
+async function getProcessor() {
+ const cpuInfo = await si.cpu();
+ return `${cpuInfo.manufacturer} ${cpuInfo.brand} (${cpuInfo.physicalCores} CPUs), ~${cpuInfo.speed}GHz`;
+}
+
+async function getMemory() {
+ const memInfo = await si.mem();
+ return `${(memInfo.total / 1024 / 1024).toFixed(0)}MB RAM`;
+}
+
+module.exports.getSystemInfo = async () => {
+ try {
+ const currentDateTime = await getCurrentDateTime();
+ const computerName = await getComputerName();
+ const operatingSystem = await getOperatingSystem();
+ const systemInfo = await getSystemInfo(); // Store the result of getSystemInfo() in a variable
+ const systemManufacturer = systemInfo.manufacturer;
+ const systemModel = systemInfo.model;
+ const biosVersion = await getBIOSVersion();
+ const processor = await getProcessor();
+ const memory = await getMemory();
+
+ return {
+ currentDateTime,
+ computerName,
+ operatingSystem,
+ systemManufacturer,
+ systemModel,
+ biosVersion,
+ processor,
+ memory,
+ };
+ } catch (error) {
+ console.error("Error fetching system information:", error);
+ return null; // or handle the error in a different way as per your requirement
+ }
+};
diff --git a/src/modules/WindowUtility.js b/src/modules/WindowUtility.js
index 9741b0d8..d52ea05a 100644
--- a/src/modules/WindowUtility.js
+++ b/src/modules/WindowUtility.js
@@ -2,7 +2,7 @@ const { app, BrowserWindow, screen } = require("electron");
let isDevelopment = process.env.NODE_ENV !== "production";
-let addWin, editWin, settingsWin, notesWin, modalWin;
+let settingsWin, modalWin;
const browserUtility = require("./BrowserWindowUtility");
const path = require("path");
@@ -15,8 +15,8 @@ module.exports.setDevMode = async ({ enabled }) => {
module.exports.getMainWindow = () => {
const win = new BrowserWindow({
- width: 800,
- height: 600,
+ width: 1440,
+ height: 900,
minWidth: 800,
minHeight: 600,
center: true,
@@ -36,24 +36,23 @@ module.exports.getMainWindow = () => {
return win;
};
-module.exports.openMinimizeWindow = (data) => {
+module.exports.openLowProfileWindow = (data) => {
const browserWindow = browserUtility.getBrowserWindow();
- const minimizedWindow = browserUtility.getMinimizedWindow();
-
+ const lowProfiledWindow = browserUtility.getLowProfiledWindow();
const url =
process.env.NODE_ENV === "development"
? "http://localhost:8080/#/minimize"
: `file://${__dirname}/index.html#minimize`;
- if (!minimizedWindow || minimizedWindow.isDestroyed) {
+ if (!lowProfiledWindow || lowProfiledWindow.isDestroyed) {
let display = screen.getPrimaryDisplay();
let displayWidth = display.bounds.width;
let minimizeWin = new BrowserWindow({
- width: 480,
- height: 84,
+ width: 1024,
+ height: 768,
minWidth: 480,
minHeight: 84,
- x: displayWidth - 480,
+ x: displayWidth - 1024,
y: 50,
frame: false,
transparent: true,
@@ -80,143 +79,38 @@ module.exports.openMinimizeWindow = (data) => {
minimizeWin.on("close", () => {
minimizeWin = null;
- browserUtility.setMinimizedWindow(null);
+ browserUtility.setLowProfiledWindow(null);
browserWindow.show();
});
- browserUtility.setMinimizedWindow(minimizeWin);
+ browserUtility.setLowProfiledWindow(minimizeWin);
browserWindow.hide();
} else {
browserWindow.hide();
- minimizedWindow.webContents.send("STATE_DATA", data.data);
- minimizedWindow.show();
+ lowProfiledWindow.webContents.send("STATE_DATA", data.data);
+ lowProfiledWindow.show();
}
browserUtility.setViewMode(VIEW_MODE.MINI);
};
-module.exports.closeMinimizeWindow = (data) => {
+module.exports.closeLowProfileWindow = (data) => {
const browserWindow = browserUtility.getBrowserWindow();
- const minimizedWindow = browserUtility.getMinimizedWindow();
- minimizedWindow.close();
+ const lowProfiledWindow = browserUtility.getLowProfiledWindow();
+ lowProfiledWindow.close();
browserWindow.webContents.send(data.bindKey, data.data);
browserUtility.setViewMode(VIEW_MODE.NORMAL);
};
-module.exports.closeSessionAndMinimizedWindow = (data) => {
+module.exports.closeSessionAndLowProfiledWindow = (data) => {
const browserWindow = browserUtility.getBrowserWindow();
- const minimizedWindow = browserUtility.getMinimizedWindow();
- minimizedWindow.hide();
+ const lowProfiledWindow = browserUtility.getLowProfiledWindow();
+ lowProfiledWindow.hide();
browserWindow.webContents.send(IPC_BIND_KEYS.END_SESSION, data.data);
browserWindow.show();
browserUtility.setViewMode(VIEW_MODE.NORMAL);
};
-module.exports.openAddWindow = ({ width, height, data }) => {
- const parentWindow = browserUtility.getParentWindow();
-
- const modalPath =
- process.env.NODE_ENV === "development"
- ? "http://localhost:8080/#/addEvidence"
- : `file://${__dirname}/index.html#addEvidence`;
-
- if (!addWin) {
- addWin = new BrowserWindow({
- width: width,
- height: height,
- minWidth: width,
- minHeight: height,
- center: true,
- parent: parentWindow,
- // eslint-disable-next-line no-undef
- icon: path.join(__static, "logo.png"),
- webPreferences: {
- devTools: true,
- nodeIntegration: true,
- webSecurity: false,
- enableRemoteModule: true,
- preload: path.join(app.getAppPath(), "preload.js"),
- },
- });
-
- addWin.loadURL(modalPath);
- addWin.setMenuBarVisibility(false);
- addWin.once("ready-to-show", () => {
- if (isDevelopment) {
- addWin.webContents.openDevTools();
- }
- addWin.show();
-
- parentWindow.webContents.send("OPEN_CHILD_WINDOW");
- });
-
- addWin.webContents.on("did-finish-load", () => {
- addWin.webContents.send("ACTIVE_SESSION", data);
- });
-
- addWin.on("close", () => {
- parentWindow.webContents.send("CLOSE_CHILD_WINDOW", { data: "add" });
- addWin = null;
- });
- }
-};
-
-module.exports.closeAddWindow = () => {
- addWin.close();
-};
-
-module.exports.openEditWindow = (data) => {
- const browserWindow = browserUtility.getBrowserWindow();
- const url =
- process.env.NODE_ENV === "development"
- ? "http://localhost:8080/#/editEvidence"
- : `file://${__dirname}/index.html#editEvidence`;
-
- if (!editWin) {
- editWin = new BrowserWindow({
- width: 800,
- height: 800,
- minWidth: 800,
- minHeight: 800,
- center: true,
- parent: browserWindow,
- // eslint-disable-next-line no-undef
- icon: path.join(__static, "logo.png"),
- webPreferences: {
- devTools: true,
- nodeIntegration: true,
- webSecurity: false,
- enableRemoteModule: true,
- preload: path.join(app.getAppPath(), "preload.js"),
- },
- });
-
- editWin.loadURL(url);
- editWin.setMenuBarVisibility(false);
-
- editWin.once("ready-to-show", () => {
- if (isDevelopment) {
- editWin.webContents.openDevTools();
- }
- editWin.show();
- browserWindow.webContents.send("OPEN_CHILD_WINDOW");
- });
-
- editWin.webContents.on("did-finish-load", () => {
- editWin.webContents.send("ACTIVE_SESSION", data);
- });
-
- editWin.on("close", () => {
- browserWindow.webContents.send("CLOSE_CHILD_WINDOW", { data: "edit" });
- editWin = null;
- });
- }
-};
-
-module.exports.closeEditWindow = () => {
- editWin.close();
-};
-
module.exports.openSettingWindow = () => {
const browserWindow = browserUtility.getBrowserWindow();
const url =
@@ -326,57 +220,8 @@ module.exports.closeModalWindow = (data) => {
modalWin.close();
};
-module.exports.openNotesWindow = (data) => {
- const browserWindow = browserUtility.getBrowserWindow();
- const modalPath =
- process.env.NODE_ENV === "development"
- ? "http://localhost:8080/#/note"
- : `file://${__dirname}/index.html#note`;
-
- if (!notesWin) {
- notesWin = new BrowserWindow({
- width: data.width,
- height: data.height,
- minWidth: data.width,
- minHeight: data.height,
- center: true,
- parent: browserWindow,
- // eslint-disable-next-line no-undef
- icon: path.join(__static, "logo.png"),
- webPreferences: {
- devTools: true,
- nodeIntegration: true,
- webSecurity: false,
- enableRemoteModule: true,
- preload: path.join(app.getAppPath(), "preload.js"),
- },
- });
-
- notesWin.loadURL(modalPath);
- notesWin.setMenuBarVisibility(false);
- notesWin.once("ready-to-show", () => {
- if (isDevelopment) {
- notesWin.webContents.openDevTools();
- }
- notesWin.show();
- browserWindow.webContents.send("OPEN_CHILD_WINDOW");
- });
-
- notesWin.webContents.on("did-finish-load", () => {});
-
- notesWin.on("close", () => {
- browserWindow.webContents.send("CLOSE_CHILD_WINDOW", { data: "notes" });
- notesWin = null;
- });
- }
-};
-
-module.exports.closeNotesWindow = () => {
- notesWin.close();
-};
-
module.exports.moveWindow = (data) => {
- const minimizeWindow = browserUtility.getMinimizedWindow();
+ const minimizeWindow = browserUtility.getLowProfiledWindow();
const currentPosition = minimizeWindow.getPosition();
minimizeWindow.setPosition(
diff --git a/src/modules/constants.js b/src/modules/constants.js
index 208b8930..fc87c302 100644
--- a/src/modules/constants.js
+++ b/src/modules/constants.js
@@ -1,17 +1,18 @@
export const IPC_HANDLERS = {
BROWSER: "browser",
CAPTURE: "capture",
- DATABASE: "database",
+ PERSISTENCE: "persistence",
FILE_SYSTEM: "fileSystem",
STORE: "store",
MENU: "menu",
WINDOW: "window",
SERVER: "server",
+ SYSTEMINFO: "systemInfo",
};
export const IPC_FUNCTIONS = {
+ GET_SYSTEM_INFO: "getSystemInfo",
GET_MEDIA_SOURCE: "getMediaSource",
- SAVE_NOTE: "saveNote",
CREATE_IMAGE: "createImage",
UPDATE_IMAGE: "updateImage",
CREATE_VIDEO: "createVideo",
@@ -24,29 +25,25 @@ export const IPC_FUNCTIONS = {
DROP_FILE: "dropFile",
SET_DEV_MODE: "setDevMode",
- OPEN_ADD_WINDOW: "openAddWindow",
- CLOSE_ADD_WINDOW: "closeAddWindow",
- OPEN_EDIT_WINDOW: "openEditWindow",
- CLOSE_EDIT_WINDOW: "closeEditWindow",
OPEN_SETTING_WINDOW: "openSettingWindow",
CLOSE_SETTING_WINDOW: "closeSettingWindow",
- OPEN_MINIMIZE_WINDOW: "openMinimizeWindow",
- CLOSE_MINIMIZE_WINDOW: "closeMinimizeWindow",
+ OPEN_LOWPROFILE_WINDOW: "openLowProfileWindow",
+ CLOSE_LOWPROFILE_WINDOW: "closeLowProfileWindow",
CLOSE_SESSION_AND_MINIIMIZED_WINDOW: "closeSessionAndMinimizedWindow",
OPEN_MODAL_WINDOW: "openModalWindow",
CLOSE_MODAL_WINDOW: "closeModalWindow",
SET_WINDOW_SIZE: "setWindowSize",
SET_APPEARANCE: "setAppearance",
- OPEN_NOTES_WINDOW: "openNotesWindow",
- CLOSE_NOTES_WINDOW: "closeNotesWindow",
MOVE_WINDOW: "moveWindow",
INITIALIZE_SESSION: "initializeSession",
GET_SESSION_ID: "getSessionID",
+ GET_CASE_ID: "getCaseID",
GET_STATE: "getState",
UPDATE_STATE: "updateState",
ADD_ITEM: "addItem",
GET_ITEMS: "getItems",
+ UPDATE_ITEM: "updateItem",
UPDATE_ITEMS: "updateItems",
DELETE_ITEMS: "deleteItems",
GET_ITEM_BY_ID: "getItemById",
@@ -61,16 +58,22 @@ export const IPC_FUNCTIONS = {
UPDATE_NOTES: "updateNotes",
RESET_DATA: "resetData",
+ GET_NODES: "getNotes",
+ UPDATE_NODES: "updateNotes",
+ GET_CONNECTIONS: "getConnections",
+ UPDATE_CONNECTIONS: "updateConnections",
+
EXPORT_ITEMS: "exportItems",
CREATE_NEW_SESSION: "createNewSession",
SAVE_SESSION: "saveSession",
+ RESET_SESSION: "resetSession",
OPEN_SESSION: "openSession",
EXPORT_SESSION: "exportSession",
+ DELETE_SESSION: "deleteSession",
OPEN_CONFIG_FILE: "openConfigFile",
OPEN_CREDENTIALS_FILE: "openCredentialsFile",
DRAG_ITEM: "dragItem",
OPEN_EXTERNAL_LINK: "openExternalLink",
-
CHANGE_MENUITEM_STATUS: "changeMenuItemStatus",
START_SERVER: "startServer",
@@ -104,15 +107,6 @@ export const SESSION_STATUSES = {
END: "end",
};
-export const SESSION_TYPES = [
- "Screenshot",
- "Video",
- "Audio",
- "Note",
- "File",
- "Mindmap",
-];
-
export const HOTKEY_PAGES = [
"general",
"home",
@@ -126,10 +120,51 @@ export const VIEW_MODE = {
MINI: "minimized",
};
+export const DEFAULT_FILE_TYPES = {
+ image: {
+ type: "image/png",
+ suffix: "png",
+ },
+ video: {
+ type: "video/mp4",
+ suffix: "mp4",
+ },
+ audio: {
+ type: "audio/mp3",
+ suffix: "mp3",
+ },
+ text: {
+ type: "text/plain",
+ suffix: "txt",
+ },
+ mindmap: {
+ type: "application/json",
+ suffix: "mindmap",
+ },
+};
+
+export const FILE_TYPES = {
+ "image/png": "image",
+ "image/jpeg": "image",
+ "image/bmp": "image",
+ "image/gif": "image",
+ "image/svg+xml": "image",
+ "image/webp": "image",
+ "video/mp4": "video",
+ "video/mpeg": "video",
+ "video/webm": "video",
+ "video/x-msvideo": "video",
+ "audio/mp3": "audio",
+ "audio/mpeg": "audio",
+ "audio/wav": "audio",
+ "audio/webm": "audio",
+ "application/json": "mindmap",
+};
+
export const TEXT_TYPES = {
Comment: {
icon: "fa-solid fa-comment",
- fill: "#6D28D9",
+ fill: "#0C2FF3",
},
Problem: {
icon: "fa-solid fa-triangle-exclamation",
@@ -182,14 +217,24 @@ export const VIDEO_RESOLUTION = [
export const DEFAULT_CHARTER_MAP_NODES = [
{
id: "5e274797-4db7-4fe8-a983-8b8abf8771c5",
- text: "System Under Test",
- url: "https://features.yattie.ai",
+ content: "System Under Test",
+ fileType: "text/plain",
+ comment: {
+ type: "Problem",
+ content: "The system is not working as expected",
+ },
+ url: "https://features.testfiesta.com",
fx: -210.9125181819311,
fy: -583.1010883631283,
},
{
id: "4763495c-62b7-4625-9083-2d40045b6550",
- text: "Feature #1",
+ content: "Feature #1",
+ fileType: "text/plain",
+ comment: {
+ type: "Idea",
+ content: "This feature is important for the system to work",
+ },
fx: 99.1983655368465,
fy: -582.6407249084972,
},
@@ -205,16 +250,42 @@ export const DEFAULT_CHARTER_MAP_CONNECTIONS = [
export const DEFAULT_MAP_NODES = [
{
id: "5e274797-4db7-4fe8-a983-8b8abf8771c5",
- text: "Mind Map",
- url: "https://features.yattie.ai",
+ content: "Mind Map",
+ fileType: "text/plain",
+ comment: {
+ type: "Problem",
+ content: "The Mindmap is not working as expected",
+ },
+ url: "https://features.testfiesta.com",
fx: -210.9125181819311,
fy: -583.1010883631283,
+ status: "Passed",
},
{
id: "4763495c-62b7-4625-9083-2d40045b6550",
- text: "First Node",
+ content: "First Node",
+ fileType: "text/plain",
+ comment: {
+ type: "Idea",
+ content: "This first node is important for the system to work",
+ },
fx: 99.1983655368465,
fy: -582.6407249084972,
+ status: "Failed",
+ url: "https://features.testfiesta.com",
+ },
+ {
+ id: "4763495c-62b7-4525-9083-2d40045b6550",
+ content: "Second Node",
+ fileType: "text/plain",
+ comment: {
+ type: "Question",
+ content: "What is the second node?",
+ },
+ fx: 199.1983655368465,
+ fy: -382.6407249084972,
+ status: "In Progress",
+ url: "https://features.testfiesta.com",
},
];
@@ -223,6 +294,10 @@ export const DEFAULT_MAP_CONNECTIONS = [
source: "5e274797-4db7-4fe8-a983-8b8abf8771c5",
target: "4763495c-62b7-4625-9083-2d40045b6550",
},
+ {
+ source: "4763495c-62b7-4625-9083-2d40045b6550",
+ target: "4763495c-62b7-4525-9083-2d40045b6550",
+ },
];
export const AI_ENABLED_FIELDS = {
diff --git a/src/modules/migrations/v0.11.0.js b/src/modules/migrations/v0.11.0.js
new file mode 100644
index 00000000..854059ba
--- /dev/null
+++ b/src/modules/migrations/v0.11.0.js
@@ -0,0 +1,122 @@
+const uuidv4 = require("uuid");
+const { DEFAULT_FILE_TYPES } = require("../constants");
+
+export const migrationStruct = {
+ up: {
+ config: {
+ config: "..",
+ useLocal: "localOnly",
+ apperance: "theme",
+ showIssue: "",
+ appLabel: "",
+ summary: "summaryRequired",
+ templates: (templates) => {
+ return templates.reduce((final, template) => {
+ let newType;
+ switch (template.type) {
+ case "Screenshot":
+ newType = "image";
+ break;
+ case "Video":
+ newType = "video";
+ break;
+ case "Audio":
+ newType = "audio";
+ break;
+ case "Note":
+ newType = "text";
+ break;
+ case "Mindmap":
+ newType = "mindmap";
+ break;
+ default:
+ newType = "file";
+ }
+ return Object.assign(final, {
+ [newType]: template.precondition,
+ });
+ }, {});
+ },
+ },
+ data: {
+ items: (items) => {
+ return items.map((item) => {
+ item.sessionID = item.id;
+ item.caseID = uuidv4();
+
+ delete item.id;
+ delete item.sessionType;
+ return item;
+ });
+ },
+ },
+ credentials: {
+ credentials: "..",
+ },
+ },
+ down: {
+ config: {
+ localOnly: "config.useLocal",
+ theme: "config.apperance",
+ defaultColor: "config.defaultColor",
+ commentType: "config.commentType",
+ audioCapture: "config.audioCapture",
+ videoQuality: "config.videoQuality",
+ debugMode: "config.debugMode",
+ summaryRequired: "config.summary",
+ ai: "config.ai",
+ templates: "config.templates",
+ checklist: "config.checklist",
+ hotkeys: "config.hotkeys",
+ version: "config.version",
+ // eslint-disable-next-line no-dupe-keys
+ templates: (templates) => {
+ let newTemplates = [];
+ for (const [type, content] of Object.entries(templates)) {
+ let oldType;
+ switch (template.type) {
+ case "image":
+ oldType = "Screenshot";
+ break;
+ case "video":
+ oldType = "Video";
+ break;
+ case "audio":
+ oldType = "Audio";
+ break;
+ case "text":
+ oldType = "Note";
+ break;
+ case "mindmap":
+ oldType = "Mindmap";
+ break;
+ default:
+ oldType = "File";
+ }
+ newTemplates.push({
+ oldType,
+ precondition: content,
+ issue: "",
+ isBug: false,
+ });
+ }
+ return newTemplates;
+ },
+ },
+ data: {
+ items: (items) => {
+ return items.map((item) => {
+ item.fileType = DEFAULT_FILE_TYPES[item.fileType].type;
+ delete item.type;
+ return item;
+ });
+ },
+ },
+ credentials: {
+ testfiesta: "credentials.testfiesta",
+ jira: "credentials.jira",
+ testrail: "credentials.testrail",
+ version: "",
+ },
+ },
+};
diff --git a/src/modules/migrations/v0.6.0.js b/src/modules/migrations/v0.6.0.js
new file mode 100644
index 00000000..01aaa037
--- /dev/null
+++ b/src/modules/migrations/v0.6.0.js
@@ -0,0 +1,12 @@
+export const migrationStruct = {
+ up: {
+ meta: {
+ credentialPath: "credentialsPath",
+ },
+ },
+ down: {
+ meta: {
+ credentialsPath: "credentialPath",
+ },
+ },
+};
diff --git a/src/modules/migrations/v0.8.0.js b/src/modules/migrations/v0.8.0.js
new file mode 100644
index 00000000..9e327614
--- /dev/null
+++ b/src/modules/migrations/v0.8.0.js
@@ -0,0 +1,16 @@
+export const migrationStruct = {
+ up: {
+ meta: {
+ meta: "..",
+ dataPath: "sessionDataPath",
+ },
+ },
+ down: {
+ meta: {
+ configPath: "meta.configPath",
+ credentialsPath: "meta.credentialsPath",
+ sessionDataPath: "meta.dataPath",
+ version: "meta.version",
+ },
+ },
+};
diff --git a/src/modules/mindmap/sass/mindmap.sass b/src/modules/mindmap/sass/mindmap.sass
index 8498958e..7fece1a7 100644
--- a/src/modules/mindmap/sass/mindmap.sass
+++ b/src/modules/mindmap/sass/mindmap.sass
@@ -3,6 +3,10 @@ $grey500: #9e9e9e
$grey800: #424242
$grey900: #212121
$orange700: #f57c00
+$cyan100: #D6E4EE
+$cyan200: #A0D8F3
+$cyan300: #80B3FF
+
$white: #fff
$shadow: 0 2px 2px 0 rgba(0, 0, 0, .14), 0 3px 1px -2px rgba(0, 0, 0, .2), 0 1px 5px 0 rgba(0, 0, 0, .12)
@@ -59,10 +63,27 @@ $shadow: 0 2px 2px 0 rgba(0, 0, 0, .14), 0 3px 1px -2px rgba(0, 0, 0, .2), 0 1px
stroke-dasharray: 10px 4px
stroke-width: 3px
+.mindmap-connector-line
+ fill: transparent
+ stroke: $grey500
+ stroke-dasharray: 10px 4px
+ stroke-width: 3px
+
+.mindmap-label
+ fill: $grey500
+ font-size: 12px
+ text-anchor: middle
+
.mindmap-emoji
height: 24px
vertical-align: bottom
width: 24px
+.mindmap-overlay
+ fill: $cyan100
+ stroke: $cyan300
+ border: 1px solid $cyan200
+ box-shadow: $shadow
+
.reddit-emoji
border-radius: 50%
\ No newline at end of file
diff --git a/src/modules/mindmap/utils/d3.js b/src/modules/mindmap/utils/d3.js
index 31d60321..f4cec199 100644
--- a/src/modules/mindmap/utils/d3.js
+++ b/src/modules/mindmap/utils/d3.js
@@ -1,4 +1,4 @@
-import { drag, event, zoom } from "d3";
+import { drag, event, zoom, mouse, select } from "d3";
import { getViewBox } from "./dimensions";
/**
@@ -8,45 +8,89 @@ import { getViewBox } from "./dimensions";
const bindData = (root, data, tag) =>
root.append("g").selectAll(tag).data(data).enter().append(tag);
+/**
+ * Bind connection labels to text on the given SVG
+ */
+export const d3Labels = (svg, connections) =>
+ bindData(svg, connections, "text")
+ .attr("class", "mindmap-label")
+ .text((connection) => connection.label ?? "");
+
/**
* Bind connections to PATH tags on the given SVG
*/
-export const d3Connections = (svg, connections) =>
+export const d3Connections = (svg, connections, labels) =>
bindData(svg, connections, "path")
.attr("class", "mindmap-connection")
- .style("fill", "transparent")
- .style("stroke", "#9e9e9e")
- .style("stroke-dasharray", "10px 4px")
- .style("stroke-width", "3px");
+ .on("dblclick", (clickedConn) => {
+ // Prevent event propagation to the document
+ event.stopPropagation();
+ // Get the clicked connection index
+ const clickedConnectionIndex = connections.indexOf(clickedConn);
+
+ // Get the label text for the clicked connection
+ const label = prompt("Enter the label for the connection:");
+ clickedConn.label = label;
+
+ // Update the connection labels data with the label
+ labels
+ .data(connections)
+ .text((_, index) => (index === clickedConnectionIndex ? label : ""));
+ })
+ .on("click", () => {
+ // Prevent event propagation to the document
+ event.stopPropagation();
+ });
-/* eslint-disable no-param-reassign */
/**
- * Bind rodes to FOREIGNOBJECT tags on the given SVG,
- * and set dimensions and html.
+ * Bind rodes to FOREIGNOBJECT tags on the given SVG
*/
-export const d3Nodes = (svg, nodes) => {
- const selection = svg
- .append("g")
- .selectAll("foreignObject")
- .data(nodes)
- .enter();
-
- const d3nodes = selection
- .append("foreignObject")
+export const d3Nodes = (svg, data) =>
+ bindData(svg, data, "foreignObject")
.attr("class", "mindmap-node")
- .attr("width", (node) => node.width + 50)
- .attr("height", (node) => node.height + 10)
- .html((node) => node.html);
+ .attr("width", (node) => node.width)
+ .attr("height", (node) => node.height);
- return {
- nodes: d3nodes,
- };
+/**
+ * connect nodes
+ */
+export const d3Connector = (svg, source) => {
+ // Function to handle mousemove event for connector line
+ function handleMouseMove() {
+ select(".mindmap-connector-line")
+ .attr("x2", mouse(this)[0] - 10)
+ .attr("y2", mouse(this)[1] - 10);
+ }
+
+ if (!source) {
+ // link icons' inactive color
+ svg.selectAll(".fa-link").style("color", "unset");
+
+ // Remove the connector line from the SVG
+ svg.select(".mindmap-connector-line").remove();
+ // Remove the mousemove and mouseup event listeners
+ svg.on("mousemove", null);
+ }
+
+ if (source) {
+ svg.on("mousemove", handleMouseMove);
+
+ svg
+ .append("g")
+ .append("line")
+ .attr("class", "mindmap-connector-line")
+ .attr("x1", source.x + source.width / 2 - 20)
+ .attr("y1", source.y - source.height / 2 + 20);
+
+ // link icons' active color
+ svg.selectAll(".fa-link").style("color", "#22c55e");
+ }
};
/**
* Callback for forceSimulation tick event.
*/
-export const onTick = (conns, nodes) => {
+export const onTick = (conns, nodes, labels) => {
const d = (conn) =>
[
"M",
@@ -75,13 +119,62 @@ export const onTick = (conns, nodes) => {
nodes
.attr("x", (node) => node.x - node.width / 2)
.attr("y", (node) => node.y - node.height / 2);
+
+ // Re-position the connection labels
+ labels
+ .attr("x", (d) => (d.source.x + d.target.x) / 2)
+ .attr("y", (d) => (d.source.y + d.target.y) / 2 - 4);
+};
+
+export const onNextTick = (conns, nodes, labels) => {
+ const d = (conn) =>
+ [
+ "M",
+ conn.source.x,
+ ",",
+ conn.source.y - 100,
+ " ",
+ "C",
+ (conn.source.x + conn.target.x) / 2,
+ ",",
+ conn.source.y,
+ " ",
+ (conn.source.x + conn.target.x) / 2,
+ ",",
+ conn.target.y,
+ " ",
+ conn.target.x,
+ ",",
+ conn.target.y - 100,
+ ].join("");
+
+ // Set the connections path.
+ conns.attr("d", d);
+
+ // Set nodes position.
+ nodes
+ .attr("x", (node) => node.x - node.width / 2)
+ .attr("y", (node) => node.y - node.height / 2);
+
+ // Re-position the connection labels
+ labels
+ .attr("x", (d) => (d.source.x + d.target.x) / 2)
+ .attr("y", (d) => (d.source.y + d.target.y) / 2 - 4);
};
/*
* Return drag behavior to use on d3.selection.call().
*/
-export const d3Drag = (simulation, svg, nodes) => {
+export const d3Drag = (
+ simulation,
+ svg,
+ nodes,
+ isWrapper,
+ dragging,
+ updateNodes
+) => {
const dragStart = (node) => {
+ dragging = false;
if (!event.active) {
simulation.alphaTarget(0.2).restart();
}
@@ -91,6 +184,7 @@ export const d3Drag = (simulation, svg, nodes) => {
};
const dragged = (node) => {
+ dragging = true;
node.fx = event.x;
node.fy = event.y;
};
@@ -99,13 +193,117 @@ export const d3Drag = (simulation, svg, nodes) => {
if (!event.active) {
simulation.alphaTarget(0);
}
-
+ if (isWrapper && dragging) {
+ updateNodes();
+ dragging = false;
+ }
svg.attr("viewBox", getViewBox(nodes.data()));
};
return drag().on("start", dragStart).on("drag", dragged).on("end", dragEnd);
};
+/**
+ * select the nodes by draging
+ */
+export const d3Selection = (svg, nodes) => {
+ // Add the drag overlay
+ const overlay = svg
+ .append("rect")
+ .attr("class", "mindmap-overlay")
+ .style("opacity", 0)
+ .style("pointer-events", "none");
+
+ svg.on("mousedown", mouseDown);
+ svg.on("mousemove", dragMove);
+ svg.on("mouseup", mouseUp);
+
+ let startX,
+ startY,
+ isSelecting = false;
+ let mouseButton = "left",
+ mouseClicked = false;
+ function mouseDown() {
+ // Check if Ctrl or Cmd key is pressed
+ // const isKeyPressed = event.ctrlKey || event.metaKey;
+ // if (!isKeyPressed) return;
+ mouseClicked = true;
+ if (event.button === 2) {
+ mouseButton = "right";
+ select("body").style("cursor", "auto");
+ const [relativeX, relativeY] = mouse(this);
+ startX = relativeX;
+ startY = relativeY;
+ isSelecting = true;
+
+ // Enable the overlay
+ overlay.style("opacity", 0.5).style("pointer-events", "all");
+ } else if (event.button === 0) {
+ mouseButton = "left";
+ select("body").style("cursor", "auto");
+ }
+ }
+
+ function dragMove() {
+ if (mouseClicked) {
+ if (mouseButton === "right") {
+ if (!isSelecting) return;
+ const [relativeX, relativeY] = mouse(this);
+ const mouseX = relativeX;
+ const mouseY = relativeY;
+
+ // Calculate the position and size of the box
+ const minX = Math.min(startX, mouseX);
+ const minY = Math.min(startY, mouseY);
+ const maxX = Math.max(startX, mouseX);
+ const maxY = Math.max(startY, mouseY);
+ const width = maxX - minX;
+ const height = maxY - minY;
+ // Update the overlay attributes
+ select("body").style("cursor", "auto");
+ overlay
+ .attr("x", minX)
+ .attr("y", minY)
+ .attr("width", width)
+ .attr("height", height);
+ } else if (mouseButton === "left") {
+ // select("body").style("cursor", "crosshair");
+ // select("body").style("cursor", "grabbing");
+ select("body").style("cursor", "auto");
+ }
+ }
+ }
+
+ function mouseUp() {
+ select("body").style("cursor", "auto");
+ mouseClicked = false;
+ if (event.button === 2) {
+ isSelecting = false;
+ // Hide the overlay
+ overlay.style("opacity", 0).style("pointer-events", "none");
+
+ const selectedNodes = [];
+
+ // Iterate through each node's coordinates
+ // and check if it falls within the box
+ nodes.forEach((node) => {
+ if (
+ node.x >= overlay.attr("x") &&
+ node.x <= +overlay.attr("x") + +overlay.attr("width") &&
+ node.y >= overlay.attr("y") &&
+ node.y <= +overlay.attr("y") + +overlay.attr("height")
+ ) {
+ selectedNodes.push(node);
+ }
+ });
+
+ // callback(selectedNodes);
+ }
+ }
+
+ return svg;
+};
+
/* eslint-enable no-param-reassign */
/*
@@ -117,22 +315,3 @@ export const d3PanZoom = (el) =>
.on("zoom", () =>
el.selectAll("svg > g").attr("transform", event.transform)
);
-
-/*
- * d3 node click event
- */
-export const d3NodeClick = () => {
- event.stopPropagation();
- let target = event.target;
-
- switch (target.className) {
- case "fas fa-plus-circle":
- return "add";
- case "fas fa-pencil-alt":
- return "edit";
- case "fas fa-trash-alt":
- return "remove";
- case "node-text":
- return "click";
- }
-};
diff --git a/src/plugins/vuetify.js b/src/plugins/vuetify.js
index 5a0d1749..3bd05606 100644
--- a/src/plugins/vuetify.js
+++ b/src/plugins/vuetify.js
@@ -13,8 +13,9 @@ export default new Vuetify({
themes: {
dark: {
default: "#6B7280",
- primary: "#6D28D9",
+ primary: "#0C2FF3",
secondary: "#FFFFFF",
+ third: "#F2F4F7",
black: "#000000",
white: "#FFFFFF",
danger: colors.red.accent3,
@@ -27,8 +28,9 @@ export default new Vuetify({
},
light: {
default: "#6B7280",
- primary: "#6D28D9",
+ primary: "#0C2FF3",
secondary: "#111827",
+ third: "#F2F4F7",
black: "#000000",
white: "#FFFFFF",
danger: colors.red.accent3,
diff --git a/src/router/index.js b/src/router/index.js
index c6c4b0b0..6657aad2 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -3,20 +3,20 @@ import VueRouter from "vue-router";
import HomeView from "../views/HomeView.vue";
import MainView from "../views/MainView.vue";
-import AddEvidence from "../views/AddEvidence.vue";
-import EditEvidence from "../views/EditEvidence.vue";
import ResultView from "../views/ResultView.vue";
import PrintView from "../views/PrintView.vue";
-import NoteView from "../views/NoteView.vue";
-import MinimizeView from "../views/MinimizeView.vue";
+import LowProfileView from "../views/LowProfileView.vue";
import AuthenticationView from "../views/AuthenticationView.vue";
import SignupMainWrapper from "@/components/authentication/SignupMainWrapper";
-import SignupYattieWrapper from "@/components/authentication/SignupYattieWrapper";
+import SignupPinataWrapper from "@/components/authentication/SignupPinataWrapper";
import SigninWrapper from "@/components/authentication/SigninWrapper";
-import SigninYattWrapper from "@/components/authentication/SigninYattWrapper";
+import SigninTestfiestaWrapper from "@/components/authentication/SigninTestfiestaWrapper";
import SigninJiraWrapper from "@/components/authentication/SigninJiraWrapper";
import SigninTestRailWrapper from "@/components/authentication/SigninTestRailWrapper";
+import SigninXrayWrapper from "@/components/authentication/SigninXrayWrapper";
+import SigninZephyrSquadWrapper from "@/components/authentication/SigninZephyrSquadWrapper";
+import SigninZephyrScaleWrapper from "@/components/authentication/SigninZephyrScaleWrapper";
import SettingView from "../views/SettingView.vue";
import ConnectionsTab from "@/components/settings/ConnectionsTab.vue";
@@ -27,11 +27,7 @@ import ConfigCheckListTab from "@/components/settings/ConfigCheckListTab.vue";
import ReportsTab from "@/components/settings/ReportsTab.vue";
import AddonsTab from "@/components/settings/AddonsTab.vue";
import HotkeysTab from "@/components/settings/HotkeysTab.vue";
-
-import NoteEditorView from "../views/NoteEditorView.vue";
-import SummaryEditorView from "../views/SummaryEditorView.vue";
-import EndSessionView from "../views/EndSessionView.vue";
-import ShareOAuthView from "../views/ShareOAuthView.vue";
+import TagsTab from "@/components/settings/TagsTab.vue";
import store from "@/store";
@@ -54,9 +50,9 @@ const routes = [
props: true,
},
{
- path: "signupYattie",
- name: "signupYattie",
- component: SignupYattieWrapper,
+ path: "signupPinata",
+ name: "signupPinata",
+ component: SignupPinataWrapper,
props: true,
},
{
@@ -66,9 +62,9 @@ const routes = [
props: true,
},
{
- path: "signinYatt",
- name: "signinYatt",
- component: SigninYattWrapper,
+ path: "signinTestfiesta",
+ name: "signinTestfiesta",
+ component: SigninTestfiestaWrapper,
props: true,
},
{
@@ -83,17 +79,31 @@ const routes = [
component: SigninTestRailWrapper,
props: true,
},
+ {
+ path: "signinXray",
+ name: "signinXray",
+ component: SigninXrayWrapper,
+ props: true,
+ },
+ {
+ path: "signinZephyrSquad",
+ name: "signinZephyrSquad",
+ component: SigninZephyrSquadWrapper,
+ props: true,
+ },
+ {
+ path: "signinZephyrScale",
+ name: "signinZephyrScale",
+ component: SigninZephyrScaleWrapper,
+ props: true,
+ },
],
},
{
path: "/main",
name: "main",
component: MainView,
- children: [
- {
- path: "workspace",
- },
- ],
+ children: [{ path: "workspace" }, { path: "workspace/:execID" }],
},
{
path: "/settings",
@@ -147,18 +157,14 @@ const routes = [
component: HotkeysTab,
props: true,
},
+ {
+ path: "tabs",
+ name: "tabs",
+ component: TagsTab,
+ props: true,
+ },
],
},
- {
- path: "/addEvidence",
- name: "addEvidence",
- component: AddEvidence,
- },
- {
- path: "/EditEvidence",
- name: "EditEvidence",
- component: EditEvidence,
- },
{
path: "/result",
name: "result",
@@ -169,36 +175,11 @@ const routes = [
name: "print",
component: PrintView,
},
- {
- path: "/note",
- name: "note",
- component: NoteView,
- },
{
path: "/minimize",
name: "minimize",
meta: { layout: "minimize" },
- component: MinimizeView,
- },
- {
- path: "/noteEditor",
- name: "noteEditor",
- component: NoteEditorView,
- },
- {
- path: "/summaryEditor",
- name: "summaryEditor",
- component: SummaryEditorView,
- },
- {
- path: "/shareOAuth",
- name: "shareOAuth",
- component: ShareOAuthView,
- },
- {
- path: "/endsession",
- name: "endsession",
- component: EndSessionView,
+ component: LowProfileView,
},
];
@@ -210,8 +191,12 @@ const router = new VueRouter({
router.beforeEach((to, from, next) => {
// This prevents us from saving store on initial load where name is null
- if (from.matched.length > 0 && !to.path.includes("settings")) {
- store.commit("setPath", to.path);
+ if (
+ from.matched.length > 0 &&
+ !to.path.includes("settings") &&
+ store.state.session.sessionID
+ ) {
+ store.commit("setSessionPath", to.path);
}
next();
});
diff --git a/src/scss/variables.scss b/src/scss/variables.scss
index 0ac9bfce..fd9c66b6 100644
--- a/src/scss/variables.scss
+++ b/src/scss/variables.scss
@@ -36,7 +36,9 @@ body {
display: none !important;
}
-.tui-image-editor-container.top.tui-image-editor-top-optimization .tui-image-editor-controls ul {
+.tui-image-editor-container.top.tui-image-editor-top-optimization
+ .tui-image-editor-controls
+ ul {
text-align: center !important;
}
@@ -72,7 +74,7 @@ body {
.v-timeline::before {
left: calc(16px - 1px) !important;
- background: #6d28d9 !important;
+ background: #0c2ff3 !important;
-webkit-print-color-adjust: exact !important;
}
@@ -101,6 +103,10 @@ body {
line-height: 1.5rem;
}
+.title-text {
+ font-size: 30px;
+}
+
.date-text {
display: flex;
align-items: center;
@@ -158,12 +164,59 @@ body {
font-size: 13px;
}
+.flex {
+ display: flex;
+}
.control-btn {
border: 1px solid #d1d5db;
color: #6b7280;
background-color: #fff;
}
+.cursor-pointer {
+ cursor: pointer;
+}
+.justify-center {
+ justify-content: center;
+}
+
+.align-center {
+ align-items: center;
+}
+
+.gap-xs {
+ gap: 5px;
+}
+
+.gap-sm {
+ gap: 10px;
+}
+
+.gap-md {
+ gap: 15px;
+}
+
+.gap-xl {
+ gap: 20px;
+}
+
+.gap-xxl {
+ gap: 30px;
+}
+
+.mindmap-control-btn {
+ position: relative;
+ color: #6b7280;
+ padding: 10px;
+ border-radius: 10px;
+ line-height: 1;
+ background-color: #fff;
+ box-shadow: 0px 4px 34px 0px rgba(0, 0, 0, 0.16);
+}
+
+.fit-content {
+ width: fit-content;
+}
// .v-input--selection-controls__input {
// width: 16px;
// height: 16px;
@@ -234,9 +287,8 @@ body {
background-color: #fff;
border: 3px solid #bb199a;
border-radius: 15px;
- margin-top: 30px;
- min-height: 60px;
- min-width: 125px;
+ // min-height: 200px;
+ // min-width: 125px;
padding: 5px 10px;
.options {
@@ -273,7 +325,7 @@ body {
text-align: center;
text-decoration: none;
transition: background-color 0.2s, color 0.2s ease-out;
-
+
&:hover {
cursor: pointer;
}
@@ -300,10 +352,9 @@ body {
.mindmap-node--editable {
cursor: all-scroll;
- &>a {
+ & > a {
pointer-events: none;
}
-
}
.mindmap-subnode-group {
@@ -315,11 +366,10 @@ body {
a {
color: #212121;
- font-family: 'Raleway';
+ font-family: "Raleway";
font-size: 16px;
padding: 2px 5px;
}
-
}
.mindmap-connection {
@@ -339,8 +389,15 @@ body {
border-radius: 50%;
}
+.v-text-field__details {
+ display: none;
+}
+
.theme--light {
- .v-tabs > .v-tabs-bar .v-tab:not(.v-tab--active), .v-tabs > .v-tabs-bar .v-tab:not(.v-tab--active) > .v-icon, .v-tabs > .v-tabs-bar .v-tab:not(.v-tab--active) > .v-btn, .v-tabs > .v-tabs-bar .v-tab--disabled {
+ .v-tabs > .v-tabs-bar .v-tab:not(.v-tab--active),
+ .v-tabs > .v-tabs-bar .v-tab:not(.v-tab--active) > .v-icon,
+ .v-tabs > .v-tabs-bar .v-tab:not(.v-tab--active) > .v-btn,
+ .v-tabs > .v-tabs-bar .v-tab--disabled {
color: rgba(255, 255, 255, 0.6);
}
}
@@ -348,8 +405,8 @@ body {
.v-text-field,
.v-text-field--solo {
.v-input__control > .v-input__slot {
- background-color: #6B7280;
- border-color: #6B7280;
+ background-color: #6b7280;
+ border-color: #6b7280;
}
}
.v-icon {
@@ -364,12 +421,11 @@ body {
}
.v-input__control > .v-input__slot {
header {
- background-color: #6B7280!important;
+ background-color: #6b7280 !important;
}
.ProseMirror {
- background-color: #4B5563;
+ background-color: #4b5563;
}
}
}
}
-
diff --git a/src/services/electronService.js b/src/services/electronService.js
index 900b132e..961bbaf2 100644
--- a/src/services/electronService.js
+++ b/src/services/electronService.js
@@ -86,13 +86,6 @@ export default class ElectronService {
});
}
- async openAddWindow(data) {
- await window.ipc.invoke(IPC_HANDLERS.WINDOW, {
- func: IPC_FUNCTIONS.OPEN_ADD_WINDOW,
- data: { width: 700, height: 800, data },
- });
- }
-
async closeAddWindow() {
return await window.ipc.invoke(IPC_HANDLERS.WINDOW, {
func: IPC_FUNCTIONS.CLOSE_ADD_WINDOW,
@@ -120,90 +113,13 @@ export default class ElectronService {
});
}
- async openShareOauthWindow(credentials) {
- return await window.ipc.invoke(IPC_HANDLERS.WINDOW, {
- func: IPC_FUNCTIONS.OPEN_MODAL_WINDOW,
- data: {
- path: "shareOAuth",
- size: {
- width: 400,
- height: 550,
- },
- data: credentials,
- },
- });
- }
-
- async openSourcePickerWindow(sources) {
+ async openLowProfileWindow() {
return await window.ipc.invoke(IPC_HANDLERS.WINDOW, {
- func: IPC_FUNCTIONS.OPEN_MODAL_WINDOW,
- data: {
- path: "sourcepicker",
- size: {
- width: 600,
- height: 500,
- },
- data: sources,
- },
- });
- }
-
- async openNoteEditorWindow(config) {
- return window.ipc.invoke(IPC_HANDLERS.WINDOW, {
- func: IPC_FUNCTIONS.OPEN_MODAL_WINDOW,
- data: {
- path: "noteEditor",
- size: {
- width: 500,
- height: 550,
- },
- data: config,
- },
- });
- }
-
- async openSummaryWindow(config) {
- return await window.ipc.invoke(IPC_HANDLERS.WINDOW, {
- func: IPC_FUNCTIONS.OPEN_MODAL_WINDOW,
- data: {
- path: "summaryEditor",
- size: {
- width: 500,
- height: 500,
- },
- data: config,
- },
- });
- }
-
- async openEndSessionWindow(config) {
- return await window.ipc.invoke(IPC_HANDLERS.WINDOW, {
- func: IPC_FUNCTIONS.OPEN_MODAL_WINDOW,
- data: {
- path: "endsession",
- size: {
- width: 450,
- height: 500,
- },
- data: config,
- },
- });
- }
-
- async openMinimizeWindow() {
- return await window.ipc.invoke(IPC_HANDLERS.WINDOW, {
- func: IPC_FUNCTIONS.OPEN_MINIMIZE_WINDOW,
+ func: IPC_FUNCTIONS.OPEN_LOWPROFILE_WINDOW,
data: { width: 400, height: 84 },
});
}
- async openEditWindow(data) {
- return await window.ipc.invoke(IPC_HANDLERS.WINDOW, {
- func: IPC_FUNCTIONS.OPEN_EDIT_WINDOW,
- data: data,
- });
- }
-
async closeEditWindow() {
return await window.ipc.invoke(IPC_HANDLERS.WINDOW, {
func: IPC_FUNCTIONS.CLOSE_EDIT_WINDOW,
@@ -224,6 +140,7 @@ export default class ElectronService {
}
async dragItem(item) {
+ console.log(item);
return await window.ipc.invoke(IPC_HANDLERS.FILE_SYSTEM, {
func: IPC_FUNCTIONS.DRAG_ITEM,
data: item,
@@ -330,10 +247,10 @@ export default class ElectronService {
});
}
- async setAppearance(appearance) {
+ async setAppearance(theme) {
return await window.ipc.invoke(IPC_HANDLERS.CAPTURE, {
func: IPC_FUNCTIONS.SET_APPEARANCE,
- data: { appearance },
+ data: { theme },
});
}
@@ -344,6 +261,13 @@ export default class ElectronService {
});
}
+ async deleteSession(data) {
+ return await window.ipc.invoke(IPC_HANDLERS.FILE_SYSTEM, {
+ func: IPC_FUNCTIONS.DELETE_SESSION,
+ data,
+ });
+ }
+
async stopServer() {
return await window.ipc.invoke(IPC_HANDLERS.SERVER, {
func: IPC_FUNCTIONS.STOP_SERVER,
@@ -356,4 +280,10 @@ export default class ElectronService {
data,
});
}
+
+ async resetSession() {
+ return await window.ipc.invoke(IPC_HANDLERS.WINDOW, {
+ func: IPC_FUNCTIONS.RESET_SESSION,
+ });
+ }
}
diff --git a/src/services/storage-options/localJsonDbService.js b/src/services/storage-options/localJsonDbService.js
index c146b54d..f947fbaa 100644
--- a/src/services/storage-options/localJsonDbService.js
+++ b/src/services/storage-options/localJsonDbService.js
@@ -3,90 +3,137 @@ import { IPC_FUNCTIONS, IPC_HANDLERS } from "@/modules/constants";
export default class LocalJsonDbService extends StorageInterface {
async getState() {
- return await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.GET_STATE,
});
}
async updateState(state) {
- window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.UPDATE_STATE,
data: state,
});
}
async getConfig() {
- return await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.GET_CONFIG,
});
}
async updateConfig(config) {
- return window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ return window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.UPDATE_CONFIG,
data: config,
});
}
async getCredentials() {
- return await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.GET_CREDENTIALS,
});
}
async getMetaData() {
- return await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.GET_METADATA,
});
}
async updateCredentials(credentials) {
- window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.UPDATE_CREDENTIALS,
data: credentials,
});
}
async getItems() {
- return await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.GET_ITEMS,
});
}
async getItemById(id) {
- return await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.GET_ITEM_BY_ID,
data: id,
});
}
+ async addItem(item) {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.ADD_ITEM,
+ data: item,
+ });
+ }
+
+ async updateItem(item) {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.UPDATE_ITEM,
+ data: item,
+ });
+ }
+
async updateItems(items) {
- window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.UPDATE_ITEMS,
data: items,
});
}
async deleteItems(items) {
- return await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.DELETE_ITEMS,
data: items,
});
}
async getNotes() {
- return await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.GET_NOTES,
});
}
async updateNotes(notes) {
- await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.UPDATE_NOTES,
data: notes,
});
}
+ async deleteNotes(notes) {
+ await await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.DELETE_NOTES,
+ data: notes,
+ });
+ }
+
+ async getNodes() {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.GET_NODES,
+ });
+ }
+
+ async updateNodes(nodes) {
+ await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.UPDATE_NODES,
+ data: nodes,
+ });
+ }
+
+ async getConnections() {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.GET_CONNECTIONS,
+ });
+ }
+
+ async updateConnections(connections) {
+ await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.UPDATE_CONNECTIONS,
+ data: connections,
+ });
+ }
+
async createNewSession(data) {
return await window.ipc.invoke(IPC_HANDLERS.FILE_SYSTEM, {
func: IPC_FUNCTIONS.CREATE_NEW_SESSION,
@@ -95,11 +142,17 @@ export default class LocalJsonDbService extends StorageInterface {
}
async getSessionId() {
- return await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.GET_SESSION_ID,
});
}
+ async getCaseId() {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
+ func: IPC_FUNCTIONS.GET_CASE_ID,
+ });
+ }
+
async saveSession(data) {
return await window.ipc.invoke(IPC_HANDLERS.FILE_SYSTEM, {
func: IPC_FUNCTIONS.SAVE_SESSION,
@@ -108,15 +161,8 @@ export default class LocalJsonDbService extends StorageInterface {
}
async resetData() {
- return await window.ipc.invoke(IPC_HANDLERS.DATABASE, {
+ return await window.ipc.invoke(IPC_HANDLERS.PERSISTENCE, {
func: IPC_FUNCTIONS.RESET_DATA,
});
}
-
- async saveNote(note) {
- return await window.ipc.invoke(IPC_HANDLERS.CAPTURE, {
- func: IPC_FUNCTIONS.SAVE_NOTE,
- data: note,
- });
- }
}
diff --git a/src/services/storage-options/restApiService.js b/src/services/storage-options/restApiService.js
index 1e151d9e..edf5375b 100644
--- a/src/services/storage-options/restApiService.js
+++ b/src/services/storage-options/restApiService.js
@@ -1,30 +1,155 @@
import axios from "axios";
+import dayjs from "dayjs";
import StorageInterface from "../storageInterface";
+import store from "@/store";
+import TestfiestaIntegrationHelpers from "@/integrations/TestfiestaIntegrationHelpers";
export default class RestApiService extends StorageInterface {
- async getState() {
- const response = await axios.get(`http://localhost:3000/state`);
- return response.data;
+ async getState(executionId) {
+ const credential = store.state.auth.credentials?.testfiesta[0];
+ const headers = await TestfiestaIntegrationHelpers.getHeaders(credential);
+ const { data } = await axios.get(
+ `http://localhost:5000/v1/pinata/executions/${executionId}`,
+ headers
+ );
+ return data;
}
async updateState(state) {
- console.log(state);
- // saving state endpoint here
+ const data = {
+ case: state.case,
+ session: state.session,
+ };
+ const credential = state.auth.credentials?.testfiesta[0];
+
+ let returnResponse = {
+ link: "",
+ };
+ const headers = await TestfiestaIntegrationHelpers.getHeaders(credential);
+ await axios
+ .patch(`http://localhost:5000/v1/pinata/executions`, data, headers)
+ .then((postedSession) => {
+ returnResponse = postedSession.data;
+ })
+ .catch((error) => {
+ returnResponse.error = error.response.data.errors;
+ });
+
+ if (returnResponse?.steps) {
+ returnResponse.steps.map(async (step) => {
+ if (step.uploadURL) {
+ const match = state.session.items.find(
+ (item) => item.stepID === step.external_id
+ );
+ if (match?.filePath) {
+ const fetchResponse = await fetch(match.filePath);
+ const fileBlob = await fetchResponse.blob();
+ const file = new File([fileBlob], step?.uid, {
+ type: match.fileType,
+ });
+ await axios
+ .put(step.uploadURL, file, {
+ headers: {
+ "Content-Type": match.fileType,
+ "X-Upload-Content-Length": match.fileSize,
+ },
+ })
+ .then(async (resp) => {
+ console.log("File upload response");
+ console.log(resp);
+ const attachmentResp = await this.getAttachment(
+ step.custom_fields.attachmentID
+ );
+ console.log({ attachmentResp });
+ store.commit("replaceAttachmentUrl", {
+ attachmentID: attachmentResp.external_id,
+ url: attachmentResp.url,
+ });
+ })
+ .catch((error) => {
+ returnResponse.error.push(...error.response.data.errors);
+ });
+ }
+ }
+ });
+ }
+ if (returnResponse?.data) {
+ store.commit("setSessionIDFromBackend", returnResponse.data.sessionID);
+ store.commit("setCaseIDFromBackend", returnResponse.data.caseID);
+ }
}
async getConfig() {
- const response = await axios.get(`http://localhost:3000/config`);
+ const response = await axios.get(
+ `http://localhost:5000/v1/app/org/f352ae63-11fc-4dbe-bab1-72561aa25fca/config/5e0f71ff-987d-4240-85eb-df6adf568c31`
+ );
+ return response.data.config;
+ }
+
+ async getAttachment(attachmentId) {
+ const credential = store.state.auth.credentials?.testfiesta[0];
+ const headers = await TestfiestaIntegrationHelpers.getHeaders(credential);
+ const response = await axios.get(
+ `http://localhost:5000/v1/attachments/${attachmentId}/object`,
+ headers
+ );
return response.data;
}
async updateConfig(config) {
- console.log(config);
- // saving credentials endpoint here
+ const credential = store.state.auth.credentials?.testfiesta[0];
+ const headers = await TestfiestaIntegrationHelpers.getHeaders(credential);
+ const response = await axios.put(
+ `http://localhost:5000/v1/app/org/f352ae63-11fc-4dbe-bab1-72561aa25fca/config/5e0f71ff-987d-4240-85eb-df6adf568c31`,
+ config,
+ headers
+ );
+ return response.data.config;
}
async getCredentials() {
- const response = await axios.get(`http://localhost:3000/credentials`);
- return response.data;
+ let data = { user: {} };
+
+ const allCookies = document.cookie;
+ const cookieArray = allCookies.split("; ");
+ let accessToken = null;
+ for (const cookie of cookieArray) {
+ const [name, value] = cookie.split("=");
+ if (name.trim() === "access_token") {
+ accessToken = value;
+ break;
+ }
+ }
+ data = store.state.auth.credentials?.testfiesta[0];
+ if (accessToken) {
+ data.type = "cookie";
+ } else {
+ const response = await axios.get(
+ "http://localhost:5000/v1/app/profile/token"
+ );
+ data = response.data;
+ data.type = "bearer";
+ }
+ return {
+ testfiesta: [
+ {
+ accessToken: data.accessToken,
+ expiresAt: data.expiresAt,
+ type: data.type || "bearer",
+ loggedInAt: data.loggedInAt || dayjs().format("YYYY-MM-DD HH:mm:ss"),
+ oauthTokenIds: data.oauthTokenIds,
+ user: {
+ id: data.user.uid,
+ email: data.user.email,
+ name: data.user.first_name + " " + data.user.last_name,
+ avatar: data.user.avatar_url,
+ locale: data.user.preferences?.locale,
+ verified: data.user.preferences?.verified,
+ },
+ orgs: data.orgs,
+ },
+ ],
+ };
}
async getMetaData() {}
@@ -35,14 +160,16 @@ export default class RestApiService extends StorageInterface {
}
async getItems() {
- const response = await axios.get(`http://localhost:3000/items`);
- return response.data;
+ // const response = await axios.get(`http://localhost:8082/items`);
+ // return response.data;
+ return [];
}
async getItemById(id) {
- console.log(id);
- const response = await axios.get(`http://localhost:3000/item`);
- return response.data;
+ const itemInStore = store.state.session.items.find(
+ (item) => item.stepID === id
+ );
+ return itemInStore;
}
async updateItems(items) {
@@ -55,7 +182,7 @@ export default class RestApiService extends StorageInterface {
}
async getNotes() {
- const response = await axios.get(`http://localhost:3000/notes`);
+ const response = await axios.get(`http://localhost:8082/notes`);
return response.data;
}
@@ -64,6 +191,26 @@ export default class RestApiService extends StorageInterface {
// saving notes endpoint here
}
+ async getNodes() {
+ const response = await axios.get(`http://localhost:8082/nodes`);
+ return response.data;
+ }
+
+ async updateNodes(nodes) {
+ console.log(nodes);
+ // saving nodes endpoint here
+ }
+
+ async getConnections() {
+ const response = await axios.get(`http://localhost:8082/connections`);
+ return response.data;
+ }
+
+ async updateConnections(connections) {
+ console.log(connections);
+ // saving connections endpoint here
+ }
+
async createNewSession(data) {
console.log(data);
}
diff --git a/src/services/storageInterface.js b/src/services/storageInterface.js
index 742460bf..9bbbb58d 100644
--- a/src/services/storageInterface.js
+++ b/src/services/storageInterface.js
@@ -1,5 +1,6 @@
export default class StorageInterface {
- async getState() {
+ // eslint-disable-next-line
+ async getState(executionId) {
throw new Error("Method 'getState()' must be implemented.");
}
diff --git a/src/services/storageService.js b/src/services/storageService.js
index 138df02b..f71a9642 100644
--- a/src/services/storageService.js
+++ b/src/services/storageService.js
@@ -8,8 +8,8 @@ export default class StorageService {
: new RestApiService();
}
- async getState() {
- return await this.storage.getState();
+ async getState(executionId) {
+ return await this.storage.getState(executionId);
}
async updateState(state) {
@@ -44,6 +44,14 @@ export default class StorageService {
return await this.storage.getItemById(id);
}
+ async addItem(item) {
+ return this.storage.addItem(item);
+ }
+
+ async updateItem(item) {
+ return this.storage.updateItem(item);
+ }
+
async updateItems(items) {
return this.storage.updateItems(items);
}
@@ -60,6 +68,26 @@ export default class StorageService {
return this.storage.updateNotes(notes);
}
+ async deleteNotes(notes) {
+ return this.storage.deleteNotes(notes);
+ }
+
+ async getNodes() {
+ return await this.storage.getNodes();
+ }
+
+ async updateNodes(nodes) {
+ return this.storage.updateNodes(nodes);
+ }
+
+ async getConnections() {
+ return await this.storage.getConnections();
+ }
+
+ async updateConnections(connections) {
+ return this.storage.updateConnections(connections);
+ }
+
async createNewSession(data) {
return this.storage.createNewSession(data);
}
@@ -68,6 +96,10 @@ export default class StorageService {
return this.storage.getSessionId();
}
+ async getCaseId() {
+ return this.storage.getCaseId();
+ }
+
async saveSession(data) {
return await this.storage.saveSession(data);
}
@@ -79,4 +111,8 @@ export default class StorageService {
async saveNote(note) {
return this.storage.saveNote(note);
}
+
+ async getAttachment(attachmentId) {
+ return this.storage.getAttachment(attachmentId);
+ }
}
diff --git a/src/store/index.js b/src/store/index.js
index 19cdcd7b..0844eb2b 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,184 +1,384 @@
import Vue from "vue";
import Vuex from "vuex";
+import VuexPersist from "vuex-persist";
import {
SESSION_STATUSES,
DEFAULT_CHARTER_MAP_NODES,
DEFAULT_CHARTER_MAP_CONNECTIONS,
-} from "../modules/constants";
+} from "@/modules/constants";
import { auth } from "@/store/modules/auth";
import { config } from "@/store/modules/config";
Vue.use(Vuex);
+const isElectronApp = navigator.userAgent.includes("Electron");
+
+const vuexLocalStorage = new VuexPersist({
+ key: "pinata-state",
+ storage: window.localStorage,
+});
+
const store = new Vuex.Store({
state: {
- id: null,
- title: "",
- charter: {
- content: "",
- text: "",
- },
- mindmap: {
- nodes: DEFAULT_CHARTER_MAP_NODES,
- connections: DEFAULT_CHARTER_MAP_CONNECTIONS,
- },
- preconditions: {
- content: "",
- text: "",
- },
- duration: 0,
- status: SESSION_STATUSES.PENDING,
- timer: 0,
- started: "",
- ended: "",
- quickTest: false,
- path: "",
- preSessionTasks: [],
- postSessionTasks: [],
+ case: {
+ caseID: null,
+ title: "",
+ charter: {
+ content: "",
+ text: "",
+ },
+ preconditions: {
+ content: "",
+ text: "",
+ },
+ duration: 0,
+ mindmap: {
+ nodes: DEFAULT_CHARTER_MAP_NODES,
+ connections: DEFAULT_CHARTER_MAP_CONNECTIONS,
+ },
+ },
+ session: {
+ sessionID: "",
+ status: SESSION_STATUSES.PENDING,
+ timer: 0,
+ started: "",
+ ended: "",
+ quickTest: false,
+ path: "",
+ remote: false,
+ preSessionTasks: [],
+ postSessionTasks: [],
+ items: [],
+ notes: {
+ content: "",
+ text: "",
+ },
+ nodes: [],
+ connections: [],
+ },
+ savedTimer: 0,
},
mutations: {
- setSessionId(state, payload) {
- state.id = payload;
+ replaceAttachmentUrl(state, { attachmentID, url }) {
+ const uploadedAttachment = state.session.items.find(
+ (item) => item.attachmentID === attachmentID
+ );
+ uploadedAttachment.filePath = url;
+ uploadedAttachment.uploaded = true;
+ },
+ setSessionIDFromBackend(state, payload) {
+ state.session.sessionID = payload;
+ },
+ setCaseIDFromBackend(state, payload) {
+ state.case.caseID = payload;
+ },
+ setCaseID(state, payload) {
+ state.case.caseID = payload;
this._vm.$storageService.updateState(state);
},
- setTitle(state, payload) {
- state.title = payload;
+ setCaseTitle(state, payload) {
+ state.case.title = payload;
this._vm.$storageService.updateState(state);
},
- setCharter(state, payload) {
- state.charter.content = payload.content;
- state.charter.text = payload.text;
+ setCaseCharter(state, payload) {
+ state.case.charter.content = payload.content;
+ state.case.charter.text = payload.text;
this._vm.$storageService.updateState(state);
},
- setMindmap(state, payload) {
- state.mindmap.nodes = payload.nodes;
- state.mindmap.connections = payload.connections;
+ setCasePrecondition(state, payload) {
+ state.case.preconditions.content = payload.content;
+ state.case.preconditions.text = payload.text;
this._vm.$storageService.updateState(state);
},
- setPrecondition(state, payload) {
- state.preconditions.content = payload.content;
- state.preconditions.text = payload.text;
+ setCaseDuration(state, payload) {
+ state.case.duration = payload;
this._vm.$storageService.updateState(state);
},
- setDuration(state, payload) {
- state.duration = payload;
+ setCaseMindmap(state, payload) {
+ state.case.mindmap.nodes = payload.nodes;
+ state.case.mindmap.connections = payload.connections;
this._vm.$storageService.updateState(state);
},
- setStarted(state, payload) {
- state.started = payload;
+ setSessionID(state, payload) {
+ state.session.sessionID = payload;
this._vm.$storageService.updateState(state);
},
- setEnded(state, payload) {
- state.ended = payload;
+ setSessionStarted(state, payload) {
+ state.session.started = payload;
this._vm.$storageService.updateState(state);
},
- setQuickTest(state, payload) {
- state.quickTest = payload;
+ setSessionEnded(state, payload) {
+ state.session.ended = payload;
this._vm.$storageService.updateState(state);
},
- setPath(state, payload) {
- state.path = payload;
+ setSessionQuickTest(state, payload) {
+ state.session.quickTest = payload;
this._vm.$storageService.updateState(state);
},
+ setSessionPath(state, payload) {
+ state.session.path = payload;
+ this._vm.$storageService.updateState(state);
+ },
+ setSessionRemote(state, payload) {
+ state.session.remote = payload;
+ this._vm.$storageService.updateState(state);
+ },
+ setPreSessionTasks(state, payload) {
+ state.session.preSessionTasks = payload;
+ },
+ setPostSessionTasks(state, payload) {
+ state.session.postSessionTasks = payload;
+ },
+ setSessionItems(state, payload) {
+ state.session.items = payload;
+
+ if (Vue.prototype.$isElectron) {
+ this._vm.$storageService.updateItems(payload);
+ } else {
+ this._vm.$storageService.updateState(state);
+ }
+ },
+ setSessionNodes(state, payload) {
+ state.session.nodes = payload;
+ if (Vue.prototype.$isElectron) {
+ this._vm.$storageService.updateNodes(payload);
+ } else {
+ this._vm.$storageService.updateState(state);
+ }
+ },
+ setSessionConnections(state, payload) {
+ state.session.connections = payload;
+ if (Vue.prototype.$isElectron) {
+ this._vm.$storageService.updateConnections(payload);
+ } else {
+ this._vm.$storageService.updateState(state);
+ }
+ },
+ setSessionItemsFromExternalWindow(state, payload) {
+ state.session.items = payload;
+ },
+ addSessionItem(state, payload) {
+ state.session.items.push(payload);
+ if (Vue.prototype.$isElectron) {
+ this._vm.$storageService.addItem(payload);
+ } else {
+ this._vm.$storageService.updateState(state);
+ }
+ },
+ updateSessionItem(state, payload) {
+ const currentItemIndex = state.session.items.findIndex(
+ (item) => item.stepID === payload.stepID
+ );
+ if (currentItemIndex !== -1) {
+ state.session.items[currentItemIndex] = payload;
+ }
+ this._vm.$storageService.updateItem(payload);
+ },
+
+ deleteSessionItems(state, ids) {
+ state.session.items = ids.reduce((acc, currentId) => {
+ return acc.filter((item) => item.stepID !== currentId);
+ }, state.session.items);
+ this._vm.$storageService.deleteItems(ids);
+ },
+
+ setSessionNotes(state, payload) {
+ state.session.notes.content = payload.content;
+ state.session.notes.text = payload.text;
+ if (!this.$isElectron) {
+ this._vm.$storageService.updateState(state);
+ }
+ },
+
updateSession(state, payload) {
- if (state.status !== payload.status) {
- state.status = payload.status;
+ let isStatusChanged = false;
+ if (state.session.status !== payload.status) {
+ state.session.status = payload.status;
+ isStatusChanged = true;
}
- if (state.timer !== payload.timer) {
- state.timer = payload.timer;
+ if (state.session.timer !== payload.timer) {
+ state.session.timer = payload.timer;
}
- if (state.duration !== payload.duration) {
- state.duration = payload.duration;
+ if (state.case.duration !== payload.duration) {
+ state.case.duration = payload.duration;
+ }
+ if (state.session.ended !== payload.ended && payload.ended) {
+ state.session.ended = payload.ended;
+ }
+ if (state.session.quickTest !== payload.quickTest && payload.quickTest) {
+ state.session.quickTest = payload.quickTest;
+ }
+ if (state.session.sessionID !== payload.sessionID && payload.sessionID) {
+ state.session.sessionID = payload.sessionID;
+ }
+
+ if (
+ Vue.prototype.$isElectron ||
+ isStatusChanged ||
+ payload.isForce ||
+ payload.timer - state.savedTimer >= 10
+ ) {
+ state.savedTimer = payload.timer;
+ this._vm.$storageService.updateState(state);
}
- this._vm.$storageService.updateState(state);
},
+
clearState(state) {
- state.id = null;
- state.title = "";
- state.charter = {
+ state.case.caseID = null;
+ state.case.title = "";
+ state.case.charter = {
content: "",
text: "",
};
- state.preconditions = {
+ state.case.preconditions = {
content: "",
text: "",
};
- state.mindmap = {
+ state.case.duration = 0;
+ state.case.mindmap = {
nodes: DEFAULT_CHARTER_MAP_NODES,
connections: DEFAULT_CHARTER_MAP_CONNECTIONS,
};
- state.duration = 0;
- state.status = SESSION_STATUSES.PENDING;
- state.timer = 0;
+ state.session.sessionID = "";
+ state.session.status = SESSION_STATUSES.PENDING;
+ state.session.timer = 0;
+ state.session.started = "";
+ state.session.ended = "";
+ state.session.quickTest = false;
+ state.session.remote = false;
+ state.session.items = [];
+
+ state.session.notes = {
+ content: "",
+ text: "",
+ };
+ state.session.nodes = [];
+ state.session.connections = [];
+ this._vm.$storageService.updateState(state);
+ },
+
+ startQuickTest(state) {
+ state.case.caseID = null;
+ state.case.title = "";
+ state.case.charter = {
+ content: "",
+ text: "",
+ };
+ state.case.preconditions = {
+ content: "",
+ text: "",
+ };
+ state.case.duration = 0;
+ state.case.mindmap = {
+ nodes: DEFAULT_CHARTER_MAP_NODES,
+ connections: DEFAULT_CHARTER_MAP_CONNECTIONS,
+ };
- state.started = "";
- state.ended = "";
- state.quickTest = false;
+ state.session.sessionID = "";
+ state.session.path = "/main/workspace";
+ state.session.status = SESSION_STATUSES.PENDING;
+ state.session.timer = 0;
+ state.session.started = "";
+ state.session.ended = "";
+ state.session.quickTest = true;
+ state.session.remote = false;
+ state.session.items = [];
+ state.session.notes = {
+ content: "",
+ text: "",
+ };
+ state.session.nodes = [];
+ state.session.connections = [];
this._vm.$storageService.updateState(state);
},
+
resetState(state) {
- state.status = SESSION_STATUSES.PENDING;
- state.timer = 0;
+ state.session.status = SESSION_STATUSES.PENDING;
+ state.session.timer = 0;
- state.started = "";
- state.ended = "";
+ state.session.started = "";
+ state.session.ended = "";
this._vm.$storageService.updateState(state);
},
+
restoreState(state, payload) {
- state.id = payload.id;
- state.title = payload.title;
- state.charter.content = payload?.charter?.content || "";
- state.charter.text = payload?.charter?.text || "";
- state.mindmap.nodes =
- payload?.mindmap?.nodes || DEFAULT_CHARTER_MAP_NODES;
- state.mindmap.connections =
- payload?.mindmap?.connections || DEFAULT_CHARTER_MAP_CONNECTIONS;
- state.preconditions.content = payload?.preconditions?.content || "";
- state.preconditions.text = payload?.preconditions?.text || "";
- state.status = payload.status;
- state.timer = payload.timer;
- state.duration = payload.duration;
- state.started = payload.started;
- state.ended = payload.ended;
- state.quickTest = payload.quickTest;
- state.path = payload.path;
+ state.case = {
+ ...state.case,
+ ...payload?.case,
+ charter: {
+ content: payload?.case?.charter?.content || "",
+ text: payload?.case?.charter?.text || "",
+ },
+ preconditions: {
+ content: payload?.case?.preconditions?.content || "",
+ text: payload?.case?.preconditions?.text || "",
+ },
+ mindmap: {
+ nodes: payload?.case?.mindmap?.nodes || DEFAULT_CHARTER_MAP_NODES,
+ connections:
+ payload?.case?.mindmap?.connections ||
+ DEFAULT_CHARTER_MAP_CONNECTIONS,
+ },
+ };
+
+ state.session = {
+ ...state.session,
+ ...payload?.session,
+ remote: payload?.session?.remote || false,
+ notes: {
+ content: payload?.session?.notes?.content || "",
+ text: payload?.session?.notes?.text || "",
+ },
+ };
+
this._vm.$storageService.updateState(state);
},
- setPreSessionTasks(state, payload) {
- state.preSessionTasks = payload;
- },
+
togglePreSessionTask(state, { taskId, checked }) {
- const taskIndex = state.preSessionTasks.findIndex(
+ const taskIndex = state.session.preSessionTasks.findIndex(
(task) => task.id === taskId
);
if (taskIndex !== -1) {
- state.preSessionTasks[taskIndex].checked = checked;
+ state.session.preSessionTasks[taskIndex].checked = checked;
}
},
- setPostSessionTasks(state, payload) {
- state.postSessionTasks = payload;
- },
+
togglePostSessionTask(state, { taskId, checked }) {
- const taskIndex = state.postSessionTasks.findIndex(
+ const taskIndex = state.session.postSessionTasks.findIndex(
(task) => task.id === taskId
);
if (taskIndex !== -1) {
- state.postSessionTasks[taskIndex].checked = checked;
+ state.session.postSessionTasks[taskIndex].checked = checked;
}
},
},
actions: {},
getters: {
+ fullSession(state) {
+ return state.session;
+ },
+ sessionItems(state) {
+ return state.session.items;
+ },
+ sessionNodes(state) {
+ return state.session.nodes;
+ },
+ sessionConnections(state) {
+ return state.session.connections;
+ },
+ sessionQuickTest(state) {
+ return state.session.quickTest;
+ },
requiredPreSessionTasksChecked(state) {
- const uncheckedTasks = state.preSessionTasks.filter(
+ const uncheckedTasks = state.session.preSessionTasks.filter(
(task) => !task.checked && task.required
);
return uncheckedTasks.length === 0;
},
requiredPostSessionTasksChecked(state) {
- const uncheckedTasks = state.postSessionTasks.filter(
+ const uncheckedTasks = state.session.postSessionTasks.filter(
(task) => !task.checked && task.required
);
return uncheckedTasks.length === 0;
@@ -188,37 +388,8 @@ const store = new Vuex.Store({
auth,
config,
},
+ plugins: isElectronApp ? [] : [vuexLocalStorage.plugin],
strict: process.env.NODE_ENV !== "production",
});
-// store.subscribe((mutation) => {
-// if (
-// mutation.type === "config/addPresessionTask" ||
-// mutation.type === "config/editPresessionTaskContent" ||
-// mutation.type === "config/editPresessionTaskRequired" ||
-// mutation.type === "config/deletePresessionTask"
-// ) {
-// const presessionTasks = store.getters["config/checklistPresessionTasks"];
-// store.commit(
-// "setPreSessionTasks",
-// presessionTasks.map((task) => {
-// return { ...task, checked: false };
-// })
-// );
-// } else if (
-// mutation.type === "config/addPostsessionTask" ||
-// mutation.type === "config/editPostsessionTaskContent" ||
-// mutation.type === "config/editPostsessionTaskRequired" ||
-// mutation.type === "config/deletePostsessionTask"
-// ) {
-// const postsessionTasks = store.getters["config/checklistPostsessionTasks"];
-// store.commit(
-// "setPostSessionTasks",
-// postsessionTasks.map((task) => {
-// return { ...task, checked: false };
-// })
-// );
-// }
-// });
-
export default store;
diff --git a/src/store/modules/config.js b/src/store/modules/config.js
index 9a32fbff..93c9ba63 100644
--- a/src/store/modules/config.js
+++ b/src/store/modules/config.js
@@ -2,7 +2,7 @@ import Vue from "vue";
export const config = {
namespaced: true,
state: () => ({
- useLocal: true,
+ localOnly: false,
apperance: "light",
ai: {
enabled: false,
@@ -16,6 +16,7 @@ export const config = {
debugMode: false,
summary: false,
templates: [],
+ defaultTags: [],
checklist: {
presession: {
status: false,
@@ -46,6 +47,10 @@ export const config = {
Vue.set(state, key, payload[key]);
});
},
+ saveDefaultTags(state, payload) {
+ state.defaultTags = payload;
+ this._vm.$storageService.updateConfig(state);
+ },
setPresessionStatus(state, payload) {
state.checklist.presession.status = payload;
this._vm.$storageService.updateConfig(state);
@@ -114,11 +119,12 @@ export const config = {
fullConfig: (state) => {
return state;
},
+ defaultTags: (state) => state.defaultTags,
hotkeys: (state) => state.hotkeys,
- checklistPresessionStatus: (state) => state.checklist.presession.status,
- checklistPresessionTasks: (state) => state.checklist.presession.tasks,
+ checklistPresessionStatus: (state) => state.checklist?.presession.status,
+ checklistPresessionTasks: (state) => state.checklist?.presession.tasks,
isAiAssistEnabled: (state) => state.ai?.enabled || false,
- postSessionData: (state) => state.checklist.postsession,
+ postSessionData: (state) => state.checklist?.postsession,
checklistPostsessionStatus: (state) =>
state?.checklist?.postsession?.status,
checklistPostsessionTasks: (state) => state.checklist.postsession.tasks,
diff --git a/src/views/AddEvidence.vue b/src/views/AddEvidence.vue
deleted file mode 100644
index b4dab228..00000000
--- a/src/views/AddEvidence.vue
+++ /dev/null
@@ -1,738 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- {{ emoji.data }}
- mdi-close
-
-
-
-
-
-
-
-
-
-
-
- {{ $tc("caption.add_reaction", 1) }}
-
-
-
-
-
-
-
-
- {{ $tc("caption.required_follow_up", 1) }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{
- previousComment?.content
- ? "mdi-robot-off-outline"
- : "mdi-robot-outline"
- }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $tc("caption.create_new_issue", 1) }}
-
-
-
-
-
-
-
-
-
-
-
- {{
- $tc("caption.jira", 1)
- }}
-
-
-
-
-
-
{{ $tc("caption.cancel_creating_issue", 1) }}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/views/EditEvidence.vue b/src/views/EditEvidence.vue
deleted file mode 100644
index cc7819f2..00000000
--- a/src/views/EditEvidence.vue
+++ /dev/null
@@ -1,688 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- {{ emoji.data }}
- mdi-close
-
-
-
-
-
-
-
-
-
-
- {{ $tc("caption.add_reaction", 1) }}
-
-
-
-
-
-
-
-
- {{ $tc("caption.required_follow_up", 1) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{
- previousComment?.content
- ? "mdi-robot-off-outline"
- : "mdi-robot-outline"
- }}
-
-
-
-
-
-
-
- {{ emoji.data }}
- mdi-close
-
-
-
-
-
-
-
-
-
-
-
- {{ $tc("caption.add_reaction", 1) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/views/EndSessionView.vue b/src/views/EndSessionView.vue
deleted file mode 100644
index d4b83d99..00000000
--- a/src/views/EndSessionView.vue
+++ /dev/null
@@ -1,135 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index f8b9b185..0814d25c 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -1,163 +1,331 @@
-
+
-
-
-
- {{ $tc("caption.quick_test", 1) }}
-
-
-
-
- {{ $tc("caption.new_exploratory_session", 1) }}
-
-
- {{ $tc("caption.open_exploratory_session", 1) }}
-
-
-
-
- {{ $tc("caption.sign_in_with", 1) }}..
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
- {{
- loggedInServices.jira
- ? $tc("caption.logged_in_jira", 1)
- : $tc("caption.not_logged_in_jira", 1)
- }}
-
-
-
-
-
-
-
-
-
-
- {{
- loggedInServices.testrail
- ? $tc("caption.logged_in_testrail", 1)
- : $tc("caption.not_logged_in_testrail", 1)
- }}
-
-
-
-
-
-
-
-
-
-
- {{
- loggedInServices.qtest
- ? $tc("caption.logged_in_qtest", 1)
- : $tc("caption.not_logged_in_qtest", 1)
- }}
-
-
-
-
-
-
-
-
-
-
- {{
- loggedInServices.practitest
- ? $tc("caption.logged_in_practitest", 1)
- : $tc("caption.not_logged_in_practitest", 1)
- }}
-
-
+ >
+ mdi-account
+
+
+
+
+ Log In
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $tc("caption.quick_test", 1) }}
+
+
+
+
+ {{ $tc("caption.new_exploratory_session", 1) }}
+
+
+ {{ $tc("caption.scripted_test_session", 1) }}
+
+
+
@@ -221,7 +389,7 @@ export default {
},
async newSession() {
this.$store.commit("clearState");
- await this.$store.commit("setQuickTest", false);
+ this.$store.commit("setSessionQuickTest", false);
if (this.$router.history.current.path === "/") {
await this.$router.push("/main");
}
@@ -237,35 +405,20 @@ export default {
this.$store.commit("restoreState", state);
const currentPath = this.$router.history.current.path;
- if (currentPath !== state.path) {
- await this.$router.push({ path: state.path });
+ if (currentPath !== state.session.path) {
+ await this.$router.push({ path: state.session.path });
}
}
} else {
// todo Add web version handler
}
},
- setInitialPreSession() {
- this.$store.commit(
- "setPreSessionTasks",
- this.checklistPresessionTasks.map((task) => {
- return { ...task, checked: false };
- })
- );
- },
- setInitialPostSession() {
- console.log(456);
- // this.$store.commit(
- // "setPostSessionTasks",
- // this.checklistPostsessionTasks.map((task) => {
- // return { ...task, checked: false };
- // })
- // );
- },
- handleQuickTest() {
+ async handleQuickTest() {
this.$store.commit("clearState");
- this.$store.commit("setQuickTest", true);
- this.$router.push("/main/workspace");
+ this.$store.commit("setSessionQuickTest", true);
+ // if (this.$router.history.current.path === "/") {
+ await this.$router.push("/main");
+ // }
},
},
};
@@ -279,17 +432,19 @@ export default {
display: flex;
flex-direction: column;
/* align-items: center; */
+ background: #f2f4f7;
justify-content: flex-start;
- max-width: 320px;
}
.header {
width: 100%;
- flex-grow: 0;
display: flex;
align-items: center;
- justify-content: flex-end;
+ justify-content: space-between;
column-gap: 15px;
- padding: 10px 0;
+ padding: 15px;
+ background-color: #ffffff;
+ box-shadow: 0px 4px 34px 0px rgba(0, 0, 0, 0.16);
+ border-radius: 15px;
}
.content {
flex-grow: 1;
@@ -299,6 +454,21 @@ export default {
width: 100%;
padding-top: 50px;
}
+.test-wrapper {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ justify-content: center;
+ background-color: #ffffff;
+ padding: 25px;
+ min-width: 400px;
+ box-shadow: -10px 12px 34px 0px rgba(48, 98, 254, 0.15);
+ border-radius: 15px;
+}
+.integration-image {
+ border-radius: 50%;
+ border: 1px solid #e5e7eb;
+}
.new-section {
width: 100%;
padding: 0;
@@ -313,15 +483,30 @@ export default {
}
.open-section.social {
display: flex;
+ flex-direction: row;
+ justify-content: center;
align-items: center;
column-gap: 10px;
padding: 10px 0;
+ border-top: 0px solid #e5e7eb;
}
.open-section .open-btn {
background: #ede9fe;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
+.secondary-btn {
+ background: #f2f4f7;
+ font-weight: 700;
+ box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.05);
+ border-radius: 4px;
+}
+.integration-section {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ justify-content: center;
+}
.social-btn {
flex: 1;
background-color: transparent !important;
@@ -330,6 +515,18 @@ export default {
box-shadow: none;
position: relative;
}
+.social-integration {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.social-logo {
+ border-radius: 50%;
+ border: 2px solid #ebebeb;
+ padding: 3px;
+}
.social-btn::before:hover,
.social-btn:hover {
background-color: transparent !important;
diff --git a/src/views/MinimizeView.vue b/src/views/LowProfileView.vue
similarity index 79%
rename from src/views/MinimizeView.vue
rename to src/views/LowProfileView.vue
index ac21c8d5..e105c984 100644
--- a/src/views/MinimizeView.vue
+++ b/src/views/LowProfileView.vue
@@ -21,6 +21,7 @@
@@ -253,7 +271,7 @@ export default {
flex-direction: column;
height: 100vh;
width: 100%;
- max-width: 600px;
+ background: #f2f4f7;
overflow-y: auto;
border-left: 1px solid rgba(0, 0, 0, 0.12);
border-right: 1px solid rgba(0, 0, 0, 0.12);
@@ -265,6 +283,10 @@ export default {
justify-content: center;
column-gap: 15px;
padding: 15px;
+ background-color: #ffffff;
+ box-shadow: 0px 4px 34px 0px rgba(0, 0, 0, 0.16);
+ border-radius: 15px;
+ margin-bottom: 10px;
}
.header .tabs {
flex-grow: 1;
@@ -278,10 +300,18 @@ export default {
justify-content: center;
align-items: center;
}
+.content-container {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+}
.content {
- flex-grow: 1;
overflow: auto;
- padding: 0 5px;
+ min-width: 400px;
+ box-shadow: -10px 12px 34px 0px rgba(48, 98, 254, 0.15);
+ border-radius: 15px;
}
.footer {
width: 100%;
@@ -291,6 +321,19 @@ export default {
align-items: center;
padding: 0;
}
+.w-400 {
+ width: 400px;
+ margin-top: 200px;
+}
+.w-60 {
+ width: 60%;
+}
+.vh-full {
+ height: 100vh;
+}
+.h-full {
+ height: 100%;
+}
.v-tabs {
width: auto !important;
flex: none !important;
@@ -306,8 +349,8 @@ export default {
font-weight: 500;
}
.v-tab.v-tab--active {
- background: #6d28d9;
- border: 1px solid #6d28d9;
+ background: #0a26c3;
+ border: 1px solid #586af3;
color: #fff;
}
.v-tab.test-tab {
@@ -320,8 +363,8 @@ export default {
}
.theme--light.v-tabs .v-tabs-bar .v-tab--disabled,
.theme--light.v-tabs .v-tabs-bar .v-tab:not(.v-tab--active) {
- color: #6d28d9;
- border: 1px solid #6d28d9;
+ color: #0a26c3;
+ border: 1px solid #596def;
}
.theme--dark.v-tabs .v-tabs-bar .v-tab--disabled,
.theme--dark.v-tabs .v-tabs-bar .v-tab:not(.v-tab--active) {
diff --git a/src/views/NoteEditorView.vue b/src/views/NoteEditorView.vue
deleted file mode 100644
index d3893e3f..00000000
--- a/src/views/NoteEditorView.vue
+++ /dev/null
@@ -1,203 +0,0 @@
-
-
-
-
- {{ $tc("caption.take_note", 1) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $tc("caption.note_type", 1) }}
-
-
-
-
-
- {{ $tc("caption.clear", 1) }}
-
-
-
-
-
-
-
-
-
- {{ $tc("caption.discard", 1) }}
-
-
-
-
- {{ $tc("caption.save", 1) }}
-
-
-
-
-
-
-
-
-
diff --git a/src/views/NoteView.vue b/src/views/NoteView.vue
deleted file mode 100644
index cb839905..00000000
--- a/src/views/NoteView.vue
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/views/PrintView.vue b/src/views/PrintView.vue
index 9e3bd4cd..b38bbae9 100644
--- a/src/views/PrintView.vue
+++ b/src/views/PrintView.vue
@@ -1,7 +1,7 @@
-
{{ screenWidth }} x {{ screenHeight }}
+
+
+ {{ $tc("caption.current_date_time", 1) }}:
+
+ {{ this.currentDateTime }}
+
+
+
+ {{ $tc("caption.computer_name", 1) }}:
+
+ {{ this.computerName }}
+
+
+
+ {{ $tc("caption.os_system", 1) }}:
+
+ {{ this.operatingSystem }}
+
+
+
+ {{ $tc("caption.system_manufacturer", 1) }}:
+
+ {{ this.systemManufacturer }}
+
+
+
+ {{ $tc("caption.system_model", 1) }}:
+
+ {{ this.systemModel }}
+
+
+
+ {{ $tc("caption.bios", 1) }}:
+
+ {{ this.biosVersion }}
+
+
+
+ {{ $tc("caption.processor", 1) }}:
+
+ {{ this.processor }}
+
+
+
+ {{ $tc("caption.memory", 1) }}:
+
+ {{ this.memory }}
+
@@ -416,7 +464,12 @@ import {
VIcon,
VDivider,
} from "vuetify/lib/components";
-import { IPC_HANDLERS, IPC_FUNCTIONS, TEXT_TYPES } from "../modules/constants";
+import {
+ IPC_HANDLERS,
+ IPC_FUNCTIONS,
+ TEXT_TYPES,
+ FILE_TYPES,
+} from "../modules/constants";
export default {
name: "PrintView",
@@ -433,6 +486,8 @@ export default {
return {
items: [],
textTypes: TEXT_TYPES,
+ reportLogo: false,
+ logoPath: "",
title: "",
charter: "",
preconditions: "",
@@ -442,6 +497,14 @@ export default {
window: "",
screenWidth: "",
screenHeight: "",
+ currentDateTime: "",
+ computerName: "",
+ operatingSystem: "",
+ systemManufacturer: "",
+ systemModel: "",
+ biosVersion: "",
+ processor: "",
+ memory: "",
};
},
created() {
@@ -451,13 +514,14 @@ export default {
this.detectEnvironment();
if (!window.ipc) return;
-
window.ipc.on("ACTIVE_PDF", (data) => {
this.title = data.title;
this.charter = data.charter;
this.preconditions = data.preconditions;
this.timer = data.timer;
this.duration = data.duration;
+ this.reportLogo = data.reportLogo;
+ this.logoPath = data.logoPath;
});
},
computed: {
@@ -549,6 +613,9 @@ export default {
},
},
methods: {
+ getType(type) {
+ return FILE_TYPES[type];
+ },
formatTime(timeInSeconds) {
const seconds = ("0" + (timeInSeconds % 60)).slice(-2);
const minutes = ("0" + (parseInt(timeInSeconds / 60, 10) % 60)).slice(-2);
@@ -560,7 +627,21 @@ export default {
if (!window.ipc) return;
await window.ipc
- .invoke(IPC_HANDLERS.DATABASE, { func: IPC_FUNCTIONS.GET_ITEMS })
+ .invoke(IPC_HANDLERS.SYSTEMINFO, {
+ func: IPC_FUNCTIONS.GET_SYSTEM_INFO,
+ })
+ .then((result) => {
+ this.currentDateTime = result.currentDateTime;
+ this.computerName = result.computerName;
+ this.operatingSystem = result.operatingSystem;
+ this.systemManufacturer = result.systemManufacturer;
+ this.systemModel = result.systemModel;
+ this.biosVersion = result.biosVersion;
+ this.processor = result.processor;
+ this.memory = result.memory;
+ });
+ await window.ipc
+ .invoke(IPC_HANDLERS.PERSISTENCE, { func: IPC_FUNCTIONS.GET_ITEMS })
.then((result) => {
this.items = result;
});
diff --git a/src/views/ResultView.vue b/src/views/ResultView.vue
index c063998f..15ad51e1 100644
--- a/src/views/ResultView.vue
+++ b/src/views/ResultView.vue
@@ -1,7 +1,7 @@
-
-
+
+
@@ -17,159 +17,7 @@