diff --git a/ui/src/components/FormValidation.vue b/ui/src/components/FormValidation.vue
new file mode 100644
index 000000000..b4e8daea9
--- /dev/null
+++ b/ui/src/components/FormValidation.vue
@@ -0,0 +1,46 @@
+
+
+
+
+ {{ invalidMessage }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/src/components/ViewEditor.vue b/ui/src/components/ViewEditor.vue
index 09ff5f463..4070f35de 100644
--- a/ui/src/components/ViewEditor.vue
+++ b/ui/src/components/ViewEditor.vue
@@ -16,11 +16,12 @@
disabled
v-model="srcProject"
/>
-
+
+ >
+
@@ -35,11 +36,12 @@
disabled
v-model="srcFolder"
/>
-
+
+
+
@@ -54,22 +56,29 @@
disabled
v-model="srcTable"
/>
-
+
+
+
@@ -90,11 +99,13 @@
disabled
v-model="vwProject"
/>
-
+
+
+
+
+
+
-
-
-
@@ -151,18 +159,21 @@ import { getProjects, getTableVariables, getProject } from "@/api/api";
import {
getRestructuredProject,
getTablesFromListOfFiles,
+ isEmpty,
} from "@/helpers/utils";
import { Project } from "@/types/api";
import { StringArray, ViewEditorData } from "@/types/types";
import { PropType, Ref, defineComponent, onMounted, ref } from "vue";
import VariableSelector from "@/components/VariableSelector.vue";
import Dropdown from "@/components/Dropdown.vue";
+import FormValidation from "@/components/FormValidation.vue";
export default defineComponent({
name: "ViewEditor",
components: {
VariableSelector,
Dropdown,
+ FormValidation
},
props: {
sourceFolder: String,
@@ -203,12 +214,9 @@ export default defineComponent({
const isSrcTableSet = () => {
return (
- props.sourceTable !== "" &&
- props.sourceFolder !== "" &&
- props.sourceProject !== "" &&
- props.sourceTable !== undefined &&
- props.sourceFolder !== undefined &&
- props.sourceProject !== undefined
+ !isEmpty(props.sourceTable) &&
+ !isEmpty(props.sourceFolder) &&
+ !isEmpty(props.sourceProject)
);
};
const fetchVariables = async () => {
@@ -235,6 +243,7 @@ export default defineComponent({
data(): ViewEditorData {
return {
projectData: {},
+ formValidated: false,
vwTable: this.viewTable ? this.viewTable : "",
vwProject: this.viewProject ? this.viewProject : "",
vwFolder: this.viewFolder ? this.viewFolder : "",
@@ -245,6 +254,7 @@ export default defineComponent({
},
methods: {
getTablesFromListOfFiles,
+ isEmpty,
async getProjectContent(project: string) {
await getProject(project)
.then((data) => {
@@ -257,7 +267,6 @@ export default defineComponent({
});
},
async getVariables(project: string, folder: string, file: string) {
- console.log(project, folder, file);
await getTableVariables(project, folder + "%2F" + file)
.then((response) => {
this.variables = response;
@@ -269,17 +278,36 @@ export default defineComponent({
});
},
updateSrcProject(event: Event) {
+ this.formValidated = false;
this.srcProject = event.toString();
},
updateVwProject(event: Event) {
this.vwProject = event.toString();
},
updateSrcFolder(event: Event) {
+ this.formValidated = false;
this.srcFolder = event.toString();
},
updateSrcTable(event: Event) {
+ this.formValidated = false;
this.srcTable = event.toString();
},
+ getSelectedVariables() {
+ return this.$refs.variableSelector && (this.$refs.variableSelector as any).selectedVariables ? (this.$refs.variableSelector as any).selectedVariables : [];
+ },
+ saveIfValid(fileInfoSet: boolean, selectedVariables: StringArray) {
+ if(fileInfoSet && !isEmpty(selectedVariables)){
+ this.onSave(
+ this.srcProject,
+ this.sourceObject,
+ this.vwProject,
+ this.linkedObject,
+ selectedVariables
+ )
+ } else {
+ this.formValidated = true;
+ }
+ }
},
watch: {
srcProject() {
@@ -294,6 +322,16 @@ export default defineComponent({
},
},
computed: {
+ allFileInformationProvided(): boolean {
+ return (
+ !isEmpty(this.vwTable) &&
+ !isEmpty(this.vwFolder) &&
+ !isEmpty(this.vwProject) &&
+ !isEmpty(this.srcProject) &&
+ !isEmpty(this.srcFolder) &&
+ !isEmpty(this.srcTable));
+
+ },
linkedObject(): string {
return `${this.vwFolder}/${this.vwTable}`;
},
@@ -303,15 +341,15 @@ export default defineComponent({
isEditMode(): boolean {
// when all items are preselected, we are in edit mode
return (
- this.sourceFolder !== undefined &&
- this.sourceProject !== undefined &&
- this.sourceTable !== undefined &&
- this.viewFolder !== undefined &&
- this.viewProject !== undefined &&
- this.viewTable !== undefined &&
- this.preselectedVariables.length > 0
+ !isEmpty(this.sourceFolder) &&
+ !isEmpty(this.sourceProject) &&
+ !isEmpty(this.sourceTable) &&
+ !isEmpty(this.viewFolder) &&
+ !isEmpty(this.viewProject) &&
+ !isEmpty(this.viewTable) &&
+ !isEmpty(this.preselectedVariables)
);
},
},
});
-
+
\ No newline at end of file
diff --git a/ui/src/helpers/utils.ts b/ui/src/helpers/utils.ts
index 7aa9bd5ea..9c4278ff2 100644
--- a/ui/src/helpers/utils.ts
+++ b/ui/src/helpers/utils.ts
@@ -219,6 +219,21 @@ export function diskSpaceBelowThreshold(diskSpace: number): boolean {
return diskSpace < 2147483648;
}
+export function isEmpty(variable: any): boolean {
+ // function will return true if empty string, empty object or empty array, else false
+ if (variable === undefined || variable === null || variable === '') {
+ return true;
+ } else if(typeof(variable) === 'object') {
+ if (Array.isArray(variable)) {
+ return variable.length === 0;
+ } else {
+ return isEmptyObject(variable);
+ }
+ } else {
+ return false;
+ }
+}
+
/**
* Convert given bytes to 2 digits precision round exponent version string.
* @param bytes number
diff --git a/ui/src/types/types.d.ts b/ui/src/types/types.d.ts
index 8cbd11beb..f2e8c9d2d 100644
--- a/ui/src/types/types.d.ts
+++ b/ui/src/types/types.d.ts
@@ -1,3 +1,4 @@
+
import { Project, User } from "./api";
export type StringObject = Record>;
@@ -128,4 +129,5 @@ export type ViewEditorData = {
srcTable: string;
srcProject: string;
srcFolder: string;
+ formValidated: boolean;
};
diff --git a/ui/src/views/ProjectsExplorer.vue b/ui/src/views/ProjectsExplorer.vue
index e604e120e..496327735 100644
--- a/ui/src/views/ProjectsExplorer.vue
+++ b/ui/src/views/ProjectsExplorer.vue
@@ -341,25 +341,7 @@ export default defineComponent({
this.loading_preview = false;
});
- getFileDetails(
- this.projectId,
- `${this.selectedObject}`
- )
- .then((data) => {
- this.fileInfo.fileSize = data["size"];
- this.fileInfo.dataSizeRows = parseInt(data["rows"]);
- this.fileInfo.dataSizeColumns = parseInt(data["columns"]);
- this.fileInfo.sourceLink = data["sourceLink"];
- this.fileInfo.variables = data["variables"];
- if (isLinkFileType(this.selectedFile)) {
- this.columnNames = this.fileInfo.variables
- } else {
- this.getTableColumnNames( this.projectId, `${this.selectedObject}`)
- }
- })
- .catch((error) => {
- this.errorMessage = `Cannot load details for [${this.selectedObject}] of project [${this.projectId}]. Because: ${error}.`;
- });
+ this.setFileDetails();
}
}
},
@@ -383,10 +365,32 @@ export default defineComponent({
isTableType,
isLinkFileType,
isNonTableType,
+ setFileDetails() {
+ getFileDetails(
+ this.projectId,
+ `${this.selectedObject}`
+ )
+ .then((data) => {
+ this.fileInfo.fileSize = data["size"];
+ this.fileInfo.dataSizeRows = parseInt(data["rows"]);
+ this.fileInfo.dataSizeColumns = parseInt(data["columns"]);
+ this.fileInfo.sourceLink = data["sourceLink"];
+ this.fileInfo.variables = data["variables"];
+ if (isLinkFileType(this.selectedFile)) {
+ this.columnNames = this.fileInfo.variables
+ } else {
+ this.setTableColumnNames( this.projectId, `${this.selectedObject}`)
+ }
+ })
+ .catch((error) => {
+ this.errorMessage = `Cannot load details for [${this.selectedObject}] of project [${this.projectId}]. Because: ${error}.`;
+ });
+ },
askIfPreviewIsEmpty() {
return isEmptyObject(this.filePreview[0]);
},
cancelView() {
+ this.setFileDetails();
this.createLinkFromSrc = false;
this.editView = false;
},
@@ -420,7 +424,7 @@ export default defineComponent({
this.errorMessage = "Folder name cannot be empty";
}
},
- getTableColumnNames (project: string, object: string) {
+ setTableColumnNames (project: string, object: string) {
getTableVariables(
project,
object
diff --git a/ui/tests/unit/components/FormValidation.spec.ts b/ui/tests/unit/components/FormValidation.spec.ts
new file mode 100644
index 000000000..d053f4a99
--- /dev/null
+++ b/ui/tests/unit/components/FormValidation.spec.ts
@@ -0,0 +1,31 @@
+import { mount, VueWrapper } from "@vue/test-utils";
+import FormValidation from "@/components/FormValidation.vue";
+
+describe("ButtonGroup", () => {
+ const msg = "Invalid form! Please fix.";
+ const mockFunction = jest.fn();
+ let wrapper: VueWrapper;
+ beforeEach(function () {
+ wrapper = mount(FormValidation, {
+ props: {
+ validationCondition: false,
+ invalidMessage: msg
+ },
+ });
+ });
+
+ test("validation message not shown when isValidated not defined", () => {
+ expect(wrapper.html()).not.toContain(msg)
+ });
+
+ test("validation message not shown when isValidated true and condition false", async () => {
+ await wrapper.setProps({ isValidated: true});
+ expect(wrapper.html()).not.toContain(msg)
+ });
+
+ test("validation message shown when isValidated true and condition false", async () => {
+ await wrapper.setProps({ validationCondition: true, isValidated: true});
+ expect(wrapper.html()).toContain(msg)
+ });
+
+});
diff --git a/ui/tests/unit/components/ViewEditor.spec.ts b/ui/tests/unit/components/ViewEditor.spec.ts
index 674ef14c0..e2c47a8a8 100644
--- a/ui/tests/unit/components/ViewEditor.spec.ts
+++ b/ui/tests/unit/components/ViewEditor.spec.ts
@@ -3,10 +3,11 @@ import ViewEditor from "@/components/ViewEditor.vue";
describe("ViewEditor", () => {
let wrapper: VueWrapper;
+ const testFunction = jest.fn()
beforeEach(function () {
wrapper = shallowMount(ViewEditor, {
props: {
-
+ onSave: testFunction
},
});
});
@@ -22,4 +23,134 @@ describe("ViewEditor", () => {
expect(wrapper.vm.sourceObject).toBe("my-folder/my-table");
});
+ test("saveIfValid doesnt save if file info data not set", () => {
+ wrapper.vm.saveIfValid(false, ["a", "b", "c"]);
+ expect(wrapper.vm.formValidated).toBe(true);
+ expect(testFunction).not.toHaveBeenCalled();
+ })
+
+ test("saveIfValid doesnt save if variables not set", () => {
+ wrapper.vm.saveIfValid(true, []);
+ expect(wrapper.vm.formValidated).toBe(true);
+ expect(testFunction).not.toHaveBeenCalled();
+ })
+
+ test("saveIfValid does save if all view data set", () => {
+ const srcProject = "project";
+ const srcFolder = "folder";
+ const srcTable = "table";
+ const vwProject = "link-project";
+ const vwFolder = "link-folder";
+ const vwTable = "filename";
+ const variables = ["a", "b", "c"];
+ wrapper.vm.srcProject = srcProject;
+ wrapper.vm.srcFolder = srcFolder;
+ wrapper.vm.srcTable = srcTable;
+ wrapper.vm.vwProject = vwProject;
+ wrapper.vm.vwFolder = vwFolder;
+ wrapper.vm.vwTable = vwTable;
+ wrapper.vm.saveIfValid(true, variables);
+ expect(testFunction).toHaveBeenCalledWith(srcProject, srcFolder+"/"+ srcTable, vwProject,vwFolder+"/"+ vwTable, variables);
+ })
+
+ test("allFileInformationProvided true when all projects, folders and tables set", () => {
+ const srcProject = "project";
+ const srcFolder = "folder";
+ const srcTable = "table";
+ const vwProject = "link-project";
+ const vwFolder = "link-folder";
+ const vwTable = "filename";
+ wrapper.vm.srcProject = srcProject;
+ wrapper.vm.srcFolder = srcFolder;
+ wrapper.vm.srcTable = srcTable;
+ wrapper.vm.vwProject = vwProject;
+ wrapper.vm.vwFolder = vwFolder;
+ wrapper.vm.vwTable = vwTable;
+ expect(wrapper.vm.allFileInformationProvided).toBe(true);
+ })
+
+ test("allFileInformationProvided false when all srcProject not set", () => {
+ const srcFolder = "folder";
+ const srcTable = "table";
+ const vwProject = "link-project";
+ const vwFolder = "link-folder";
+ const vwTable = "filename";
+ wrapper.vm.srcFolder = srcFolder;
+ wrapper.vm.srcTable = srcTable;
+ wrapper.vm.vwProject = vwProject;
+ wrapper.vm.vwFolder = vwFolder;
+ wrapper.vm.vwTable = vwTable;
+ expect(wrapper.vm.allFileInformationProvided).toBe(false);
+ })
+
+ test("allFileInformationProvided false when all srcFolder not set", () => {
+ const srcProject = "project";
+ const srcTable = "table";
+ const vwProject = "link-project";
+ const vwFolder = "link-folder";
+ const vwTable = "filename";
+ wrapper.vm.srcProject = srcProject;
+ wrapper.vm.srcTable = srcTable;
+ wrapper.vm.vwProject = vwProject;
+ wrapper.vm.vwFolder = vwFolder;
+ wrapper.vm.vwTable = vwTable;
+ expect(wrapper.vm.allFileInformationProvided).toBe(false);
+ })
+
+ test("allFileInformationProvided false when all srcTable not set", () => {
+ const srcProject = "project";
+ const srcFolder = "folder";
+ const vwProject = "link-project";
+ const vwFolder = "link-folder";
+ const vwTable = "filename";
+ wrapper.vm.srcProject = srcProject;
+ wrapper.vm.srcFolder = srcFolder;
+ wrapper.vm.vwProject = vwProject;
+ wrapper.vm.vwFolder = vwFolder;
+ wrapper.vm.vwTable = vwTable;
+ expect(wrapper.vm.allFileInformationProvided).toBe(false);
+ })
+
+ test("allFileInformationProvided false when all vwProject not set", () => {
+ const srcProject = "project";
+ const srcFolder = "folder";
+ const srcTable = "table";
+ const vwFolder = "link-folder";
+ const vwTable = "filename";
+ wrapper.vm.srcProject = srcProject;
+ wrapper.vm.srcFolder = srcFolder;
+ wrapper.vm.srcTable = srcTable;
+ wrapper.vm.vwFolder = vwFolder;
+ wrapper.vm.vwTable = vwTable;
+ expect(wrapper.vm.allFileInformationProvided).toBe(false);
+ })
+
+ test("allFileInformationProvided false when all vwFolder not set", () => {
+ const srcProject = "project";
+ const srcFolder = "folder";
+ const srcTable = "table";
+ const vwProject = "link-project";
+ const vwTable = "filename";
+ wrapper.vm.srcProject = srcProject;
+ wrapper.vm.srcFolder = srcFolder;
+ wrapper.vm.srcTable = srcTable;
+ wrapper.vm.vwProject = vwProject;
+ wrapper.vm.vwTable = vwTable;
+ expect(wrapper.vm.allFileInformationProvided).toBe(false);
+ })
+
+ test("allFileInformationProvided false when all vwTable not set", () => {
+ const srcProject = "project";
+ const srcFolder = "folder";
+ const srcTable = "table";
+ const vwProject = "link-project";
+ const vwFolder = "link-folder";
+ wrapper.vm.srcProject = srcProject;
+ wrapper.vm.srcFolder = srcFolder;
+ wrapper.vm.srcTable = srcTable;
+ wrapper.vm.vwProject = vwProject;
+ wrapper.vm.vwFolder = vwFolder;
+ expect(wrapper.vm.allFileInformationProvided).toBe(false);
+ })
});
+
\ No newline at end of file
diff --git a/ui/tests/unit/helpers/utils.spec.ts b/ui/tests/unit/helpers/utils.spec.ts
index 4088ef5fe..e2ac0a9bc 100644
--- a/ui/tests/unit/helpers/utils.spec.ts
+++ b/ui/tests/unit/helpers/utils.spec.ts
@@ -18,7 +18,8 @@ import {
isLinkFileType,
encodeUriComponent,
convertBytes,
- diskSpaceBelowThreshold
+ diskSpaceBelowThreshold,
+ isEmpty
} from "@/helpers/utils";
import { StringObject } from "@/types/types";
@@ -289,7 +290,6 @@ describe("utils", () => {
expect(actual).toEqual("project%2Did%2Ffolder%2Ffile%2Dversion1.parquet");
});
});
-});
describe("convertBytes", () => {
it("bytes", () => {
@@ -329,6 +329,53 @@ describe("utils", () => {
expect(actual).toEqual(false);
});
});
-
+ describe("isEmpty", () => {
+ it("Returns true when undefined", () => {
+ const actual = isEmpty(undefined);
+ expect(actual).toEqual(true);
+ });
+
+ it("Returns true when null", () => {
+ const actual = isEmpty(null);
+ expect(actual).toEqual(true);
+ });
+
+ it("Returns true when empty string", () => {
+ const actual = isEmpty('');
+ expect(actual).toEqual(true);
+ });
+
+ it("Returns true when empty array", () => {
+ const actual = isEmpty([]);
+ expect(actual).toEqual(true);
+ });
+
+ it("Returns true when empty object", () => {
+ const actual = isEmpty({});
+ expect(actual).toEqual(true);
+ });
+
+ it("Returns false when array with element", () => {
+ const actual = isEmpty(['element']);
+ expect(actual).toEqual(false);
+ });
+
+ it("Returns false when object with element", () => {
+ const actual = isEmpty({"el": undefined});
+ expect(actual).toEqual(false);
+ });
+
+ it("Returns false when string with content", () => {
+ const actual = isEmpty("el");
+ expect(actual).toEqual(false);
+ });
+
+ it("Returns false when number", () => {
+ const actual = isEmpty(0);
+ expect(actual).toEqual(false);
+ });
+ });
+});
+
\ No newline at end of file