Skip to content

Commit dfd63ca

Browse files
ryanjdewSanjeevani19
authored andcommitted
DHFPROD-7258: Set namespaces for structured properties
1 parent 9507c6c commit dfd63ca

File tree

14 files changed

+307
-91
lines changed

14 files changed

+307
-91
lines changed

marklogic-data-hub-central/ui/e2e/cypress/integration/modeling/writerScenario1.spec.tsx

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,6 @@ describe("Entity Modeling Senario 1: Writer Role", () => {
125125
confirmationModal.getYesButton(ConfirmationType.DeletePropertyWarn);
126126
propertyTable.getProperty("newId").should("not.exist");
127127
});
128-
it("Edit a different entity", () => {
129-
entityTypeTable.getExpandEntityIcon("Customer");
130-
propertyTable.editProperty("nicknames");
131-
propertyModal.clickCheckbox("facetable");
132-
propertyModal.clickCheckbox("sortable");
133-
propertyModal.getSubmitButton().click();
134-
propertyTable.getFacetIcon("nicknames").should("exist");
135-
propertyTable.getSortIcon("nicknames").should("exist");
136-
modelPage.getEntityModifiedAlert().should("exist");
137-
});
138128
it("edit property name with Related Entity type", () => {
139129
propertyTable.editProperty("user");
140130
propertyModal.clearPropertyName();
@@ -148,10 +138,18 @@ describe("Entity Modeling Senario 1: Writer Role", () => {
148138
entityTypeModal.getEntityDescription().should("have.value", "Description has changed");
149139
entityTypeModal.getCancelButton().click();
150140
});
151-
it("Save new Buyer entity", {defaultCommandTimeout: 120000}, () => {
152-
cy.publishEntityModel();
141+
it("Edit a different entity", () => {
142+
entityTypeTable.getExpandEntityIcon("Customer");
143+
propertyTable.editProperty("nicknames");
144+
propertyModal.clickCheckbox("facetable");
145+
propertyModal.clickCheckbox("sortable");
146+
propertyModal.getSubmitButton().click();
153147
propertyTable.getFacetIcon("nicknames").should("exist");
154148
propertyTable.getSortIcon("nicknames").should("exist");
149+
modelPage.getEntityModifiedAlert().should("exist");
150+
});
151+
it("Save new and updated entities", {defaultCommandTimeout: 120000}, () => {
152+
cy.publishEntityModel();
155153
modelPage.getEntityModifiedAlert().should("not.exist");
156154
});
157155
it("Validate the entity in explore page", () => {

marklogic-data-hub-central/ui/e2e/cypress/integration/modeling/writerScenario2.spec.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ describe("Entity Modeling: Writer Role", () => {
9090
propertyModal.verifySameNamePropertyError("A property is already using the name street. A structured type cannot use the same name as an existing property.");
9191
structuredTypeModal.clearName();
9292
structuredTypeModal.newName("Zip");
93+
// test namespace validation
94+
structuredTypeModal.newNamespace("http://example.org/test");
95+
structuredTypeModal.getAddButton().click();
96+
structuredTypeModal.verifyPrefixNameError();
97+
structuredTypeModal.newPrefix("xml");
98+
structuredTypeModal.getAddButton().click();
99+
structuredTypeModal.verifyPrefixNameError();
100+
structuredTypeModal.clearNamespace();
101+
structuredTypeModal.clearPrefix();
102+
structuredTypeModal.newPrefix("test");
103+
structuredTypeModal.getAddButton().click();
104+
structuredTypeModal.verifyNamespaceError();
105+
// reset namespace information
106+
structuredTypeModal.clearNamespace();
107+
structuredTypeModal.clearPrefix();
93108
structuredTypeModal.getAddButton().click();
94109
propertyModal.getYesRadio("multiple").click();
95110
propertyModal.getNoRadio("pii").click();

marklogic-data-hub-central/ui/e2e/cypress/support/components/model/structured-type-modal.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ class StructuredTypeModal {
77
cy.get("#structured-name").focus().clear();
88
}
99

10+
newNamespace(str: string) {
11+
cy.get("#structured-namespace").focus().type(str);
12+
}
13+
14+
clearNamespace() {
15+
cy.get("#structured-namespace").focus().clear();
16+
}
17+
18+
newPrefix(str: string) {
19+
cy.get("#structured-prefix").focus().type(str);
20+
}
21+
22+
clearPrefix() {
23+
cy.get("#structured-prefix").focus().clear();
24+
}
25+
1026
getCancelButton() {
1127
return cy.get("[aria-label=\"structured-type-modal-cancel\"");
1228
}
@@ -15,6 +31,13 @@ class StructuredTypeModal {
1531
return cy.get("[aria-label=\"structured-type-modal-submit\"");
1632
}
1733

34+
verifyPrefixNameError() {
35+
return cy.findByTestId("prefix-error").should("be.visible");
36+
}
37+
38+
verifyNamespaceError() {
39+
return cy.findByTestId("namespace-error").should("be.visible");
40+
}
1841
}
1942

2043
const structuredTypeModal = new StructuredTypeModal();

marklogic-data-hub-central/ui/src/components/modeling/entity-type-modal/entity-type-modal.tsx

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const EntityTypeModal: React.FC<Props> = (props) => {
4242
const [namespaceTouched, setisNamespaceTouched] = useState(false);
4343
const [prefix, setPrefix] = useState("");
4444
const [prefixTouched, setisPrefixTouched] = useState(false);
45-
const [errorServer, setErrorServer] = useState(""); // Uncategorized errors from backend
45+
const [errorMessage, setErrorMessage] = useState(""); // Uncategorized errors from backend
4646
const [loading, toggleLoading] = useState(false);
4747
const [colorSelected, setColorSelected] = useState("#EEEFF1");
4848
const [colorTouched, setisColorTouched] = useState(false);
@@ -67,7 +67,7 @@ const EntityTypeModal: React.FC<Props> = (props) => {
6767
setColorSelected("#EEEFF1");
6868
}
6969
setErrorName("");
70-
setErrorServer("");
70+
setErrorMessage("");
7171
toggleIsNameDisabled(true);
7272
toggleLoading(false);
7373
}
@@ -128,20 +128,31 @@ const EntityTypeModal: React.FC<Props> = (props) => {
128128
};
129129
});
130130

131+
const getErrorMessage = () => {
132+
if (errorMessage) {
133+
if (errorMessage.includes("valid absolute URI")) {
134+
return <span data-testid="namespace-error">Invalid syntax: Namespace property must be a valid absolute URI. Example: http://example.org/es/gs</span>;
135+
} else if (errorMessage.includes("prefix without specifying")) {
136+
return <span data-testid="namespace-error">You must define a namespace URI because you defined a prefix.</span>;
137+
} else if (errorMessage.includes("reserved pattern")) {
138+
return <span data-testid="prefix-error">You cannot use a reserved prefix. Examples: xml, xs, xsi</span>;
139+
} else if (errorMessage.includes("must specify a prefix")) {
140+
return <span data-testid="prefix-error">You must define a prefix because you defined a namespace URI.</span>;
141+
}
142+
}
143+
return errorMessage;
144+
};
145+
131146
// Parse server error message to determine its type
132147
// TODO Server should categorize the error messages it returns so parsing is not needed
133148
const isErrorOfType = (type: string) => {
134149
let result = false;
135-
if (errorServer) {
136-
if (errorServer.includes("type already exists")) {
150+
if (errorMessage) {
151+
if (errorMessage.includes("type already exists")) {
137152
result = type === "name";
138-
} else if (errorServer.includes("valid absolute URI")) {
139-
result = type === "namespace";
140-
} else if (errorServer.includes("prefix without specifying")) {
153+
} else if (errorMessage.includes("valid absolute URI") || errorMessage.includes("prefix without specifying")) {
141154
result = type === "namespace";
142-
} else if (errorServer.includes("reserved pattern")) {
143-
result = type === "namespacePrefix";
144-
} else if (errorServer.includes("must specify a prefix")) {
155+
} else if (errorMessage.includes("reserved pattern") || errorMessage.includes("must specify a prefix")) {
145156
result = type === "namespacePrefix";
146157
}
147158
}
@@ -157,7 +168,7 @@ const EntityTypeModal: React.FC<Props> = (props) => {
157168
} catch (error) {
158169
if (error.response.status === 400) {
159170
if (error.response.data.hasOwnProperty("message")) {
160-
setErrorServer(error["response"]["data"]["message"]);
171+
setErrorMessage(error["response"]["data"]["message"]);
161172
}
162173
} else {
163174
handleError(error);
@@ -187,9 +198,9 @@ const EntityTypeModal: React.FC<Props> = (props) => {
187198
} catch (error) {
188199
if (error.response.status === 400) {
189200
if (error.response.data.hasOwnProperty("message") && error.response.data["message"] === ErrorTooltips.entityErrorServerResp(name)) {
190-
setErrorServer("name-error");
201+
setErrorMessage("name-error");
191202
} else {
192-
setErrorServer(error["response"]["data"]["message"]);
203+
setErrorMessage(error["response"]["data"]["message"]);
193204
}
194205
} else {
195206
handleError(error);
@@ -213,7 +224,7 @@ const EntityTypeModal: React.FC<Props> = (props) => {
213224
} catch (error) {
214225
if (error.response.status === 400) {
215226
if (error.response.data.hasOwnProperty("message")) {
216-
setErrorServer(error["response"]["data"]["message"]);
227+
setErrorMessage(error["response"]["data"]["message"]);
217228
}
218229
} else {
219230
handleError(error);
@@ -228,7 +239,7 @@ const EntityTypeModal: React.FC<Props> = (props) => {
228239

229240
const onOk = (event) => {
230241
setErrorName("");
231-
setErrorServer("");
242+
setErrorMessage("");
232243
event.preventDefault();
233244
if (props.isEditModal) {
234245
toggleLoading(true);
@@ -296,7 +307,7 @@ const EntityTypeModal: React.FC<Props> = (props) => {
296307
</span>}
297308
colon={false}
298309
labelAlign="left"
299-
validateStatus={(errorName || isErrorOfType("name") ? "error" : "")}
310+
validateStatus={(isErrorOfType("name") ? "error" : "")}
300311
help={errorName}
301312
>
302313
{props.isEditModal ? <span>{name}</span> : <Input
@@ -307,8 +318,8 @@ const EntityTypeModal: React.FC<Props> = (props) => {
307318
onChange={handleChange}
308319
onBlur={handleChange}
309320
/>}
310-
{ errorServer === "name-error" ? <p aria-label="entity-name-error" className={styles.errorServer}>An entity type is already using the name <strong>{name}</strong>. An entity type cannot use the same name as an existing entity type.</p>
311-
: errorServer !== "" ? <p className={styles.errorServer}>{errorServer}</p> : null}
321+
{ errorMessage === "name-error" ? <p aria-label="entity-name-error" className={styles.errorServer}>An entity type is already using the name <strong>{name}</strong>. An entity type cannot use the same name as an existing entity type.</p>
322+
: errorMessage !== "" ? <p className={styles.errorServer}>{errorMessage}</p> : null}
312323
{props.isEditModal ? null : <MLTooltip title={ModelingTooltips.nameRegex}>
313324
<Icon type="question-circle" className={styles.icon} theme="filled" />
314325
</MLTooltip>}
@@ -342,6 +353,7 @@ const EntityTypeModal: React.FC<Props> = (props) => {
342353
<Form.Item
343354
style={{display: "inline-block"}}
344355
validateStatus={isErrorOfType("namespace") ? "error" : ""}
356+
help={getErrorMessage()}
345357
>
346358
<Input
347359
id="namespace"
@@ -359,6 +371,7 @@ const EntityTypeModal: React.FC<Props> = (props) => {
359371
colon={false}
360372
style={{display: "inline-block"}}
361373
validateStatus={isErrorOfType("namespacePrefix") ? "error" : ""}
374+
help={getErrorMessage()}
362375
>
363376
<Input
364377
id="prefix"

marklogic-data-hub-central/ui/src/components/modeling/entity-type-table/entity-type-table.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type Props = {
2323
autoExpand: string;
2424
editEntityTypeDescription: (entityTypeName: string, entityTypeDescription: string, entityTypeNamespace: string, entityTypePrefix: string, entityTypeColor: string) => void;
2525
updateEntities: () => void;
26-
updateSavedEntity: (entity: EntityModified) => void;
26+
updateSavedEntity: (entity: EntityModified, errorHandler: Function|undefined) => void;
2727
hubCentralConfig: any;
2828
}
2929

marklogic-data-hub-central/ui/src/components/modeling/property-modal/property-modal.tsx

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, {useState, useEffect, useContext} from "react";
2-
import {Modal, Form, Input, Icon, Radio, Cascader, Select} from "antd";
3-
import {MLButton, MLAlert} from "@marklogic/design-system";
1+
import React, {useContext, useEffect, useState} from "react";
2+
import {Cascader, Form, Icon, Input, Modal, Radio, Select} from "antd";
3+
import {MLAlert, MLButton, MLCheckbox, MLTooltip} from "@marklogic/design-system";
44
import {faTrashAlt} from "@fortawesome/free-solid-svg-icons";
55
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
66
import styles from "./property-modal.module.scss";
@@ -11,23 +11,17 @@ import {UserContext} from "../../../util/user-context";
1111
import {ModelingContext} from "../../../util/modeling-context";
1212
import {entityReferences, primaryEntityTypes} from "../../../api/modeling";
1313
import {ModelingTooltips} from "../../../config/tooltips.config";
14-
import {MLTooltip, MLCheckbox} from "@marklogic/design-system";
1514
import {getSystemInfo} from "../../../api/environment";
1615

17-
import {
18-
StructuredTypeOptions,
19-
PropertyOptions,
20-
EditPropertyOptions,
21-
PropertyType
22-
} from "../../../types/modeling-types";
16+
import {EditPropertyOptions, PropertyOptions, PropertyType, StructuredTypeOptions} from "../../../types/modeling-types";
2317
import {ConfirmationType} from "../../../types/common-types";
2418

2519
import {
2620
COMMON_PROPERTY_TYPES,
27-
MORE_STRING_TYPES,
28-
MORE_NUMBER_TYPES,
21+
DROPDOWN_PLACEHOLDER,
2922
MORE_DATE_TYPES,
30-
DROPDOWN_PLACEHOLDER
23+
MORE_NUMBER_TYPES,
24+
MORE_STRING_TYPES
3125
} from "../../../config/modeling.config";
3226

3327
const {Option} = Select;
@@ -40,7 +34,7 @@ type Props = {
4034
structuredTypeOptions: StructuredTypeOptions;
4135
toggleModal: (isVisible: boolean) => void;
4236
addPropertyToDefinition: (definitionName: string, propertyName: string, propertyOptions: PropertyOptions) => void;
43-
addStructuredTypeToDefinition: (structuredTypeName: string) => void;
37+
addStructuredTypeToDefinition: (structuredTypeName: string, namespace: string|undefined, prefix: string|undefined, errorHandler: Function|undefined) => void;
4438
editPropertyUpdateDefinition: (definitionName: string, propertyName: string, editPropertyOptions: EditPropertyOptions) => void;
4539
deletePropertyFromDefinition: (definitionName: string, propertyName: string) => void;
4640
};
@@ -438,9 +432,11 @@ const PropertyModal: React.FC<Props> = (props) => {
438432
}
439433
};
440434

441-
const addStructuredType = (name: string) => {
435+
const addStructuredType = async (name: string, namespace: string|undefined, namespacePrefix: string|undefined, errorHandler: Function|undefined) => {
442436
let newStructuredDefinitionObject = {
443-
name: name,
437+
name,
438+
namespace,
439+
namespacePrefix,
444440
primaryKey: "",
445441
elementRangeIndex: [],
446442
pii: [],
@@ -480,12 +476,19 @@ const PropertyModal: React.FC<Props> = (props) => {
480476
MORE_DATE_TYPES
481477
]);
482478
}
483-
484-
props.addStructuredTypeToDefinition(name);
485479
setTypeDisplayValue(["structured", name]);
486480
setSelectedPropertyOptions({...selectedPropertyOptions, type: name});
487481
setRadioValues(ALL_RADIO_DISPLAY_VALUES.slice(1, 3));
488482
toggleShowConfigurationOptions(false);
483+
484+
await props.addStructuredTypeToDefinition(name, namespace, namespacePrefix, (error) => {
485+
if (errorHandler) {
486+
errorHandler(error);
487+
}
488+
updateTypeDropdown();
489+
setTypeDisplayValue(["structured", "newPropertyType"]);
490+
setSelectedPropertyOptions({...selectedPropertyOptions, propertyType: PropertyType.Structured, identifier: "", type: "newPropertyType"});
491+
});
489492
};
490493

491494
const updateTypeDropdown = () => {

0 commit comments

Comments
 (0)