This is an automated email from the ASF dual-hosted git repository. ppawar pushed a commit to branch ATLAS-5169 in repository https://gitbox.apache.org/repos/asf/atlas.git
commit 091ffb313270cbfa6597bccc1d95ad659955eecc Author: Prasad Pawar <[email protected]> AuthorDate: Wed Dec 31 17:21:46 2025 +0530 ATLAS-5169: Atlas UI creates multi-valued Business Metadata with SINGLE cardinality --- .../BusinessMetadataAtrributeForm.tsx | 113 ++++++++++++++------- .../BusinessMetadata/BusinessMetadataForm.tsx | 23 ++++- .../BusinessMetadataAtrribute.tsx | 16 ++- .../BusinessMetadataDetailsLayout.tsx | 11 +- dashboardv2/public/css/scss/business-metadata.scss | 8 ++ .../BusinessMetadataAttributeItemView_tmpl.html | 12 ++- .../BusinessMetadataAttrTableLayoutView.js | 30 ++++++ .../BusinessMetadataAttributeItemView.js | 79 ++++++++++++++ .../BusinessMetadataTableLayoutView.js | 18 +++- .../CreateBusinessMetadataLayoutView.js | 28 ++++- dashboardv3/public/css/scss/business-metadata.scss | 8 ++ .../BusinessMetadataAttributeItemView_tmpl.html | 12 ++- .../BusinessMetadataAttrTableLayoutView.js | 30 ++++++ .../BusinessMetadataAttributeItemView.js | 79 ++++++++++++++ .../BusinessMetadataTableLayoutView.js | 18 +++- .../CreateBusinessMetadataLayoutView.js | 28 ++++- 16 files changed, 460 insertions(+), 53 deletions(-) diff --git a/dashboard/src/views/BusinessMetadata/BusinessMetadataAtrributeForm.tsx b/dashboard/src/views/BusinessMetadata/BusinessMetadataAtrributeForm.tsx index 1f4bc2acf..883015078 100644 --- a/dashboard/src/views/BusinessMetadata/BusinessMetadataAtrributeForm.tsx +++ b/dashboard/src/views/BusinessMetadata/BusinessMetadataAtrributeForm.tsx @@ -37,7 +37,9 @@ import { tooltipClasses, TooltipProps, styled, - FilterOptionsState + FilterOptionsState, + ToggleButton, + ToggleButtonGroup } from "@mui/material"; import { customSortBy, isEmpty, serverError } from "@utils/Utils"; import { Controller, useForm } from "react-hook-form"; @@ -463,41 +465,82 @@ const BusinessMetadataAttributeForm = ({ watched?.[index] && watched?.[index]?.multiValueSelect) || isEmpty(editbmAttribute)) && ( - <Controller - control={control} - name={`attributeDefs.${index}.multiValueSelect` as const} - key={`attributeDefs.${index}.multiValueSelect`} - data-cy={`attributeDefs.${index}.multiValueSelect`} - defaultValue={field?.multiValueSelect} - render={({ field: { value, onChange } }) => ( - <> - <Grid - container - columnSpacing={{ xs: 1, sm: 2, md: 2 }} - marginBottom="1rem" - alignItems="center" - > - <Grid item md={3} textAlign="right"> - <InputLabel>Enable Multivalues</InputLabel> - </Grid> - <Grid item md={7}> - {" "} - <FormControlLabel - control={ - <Checkbox - disabled={isEmpty(editbmAttribute) ? false : true} - size="small" - checked={value} - onChange={onChange} + <> + <Controller + control={control} + name={`attributeDefs.${index}.multiValueSelect` as const} + key={`attributeDefs.${index}.multiValueSelect`} + data-cy={`attributeDefs.${index}.multiValueSelect`} + defaultValue={field?.multiValueSelect} + render={({ field: { value, onChange } }) => ( + <> + <Grid + container + columnSpacing={{ xs: 1, sm: 2, md: 2 }} + marginBottom="1rem" + alignItems="center" + > + <Grid item md={3} textAlign="right"> + <InputLabel>Enable Multivalues</InputLabel> + </Grid> + <Grid item md={7}> + <Stack direction="row" spacing={2} alignItems="center"> + <FormControlLabel + control={ + <Checkbox + disabled={isEmpty(editbmAttribute) ? false : true} + size="small" + checked={value} + onChange={(e) => { + onChange(e.target.checked); + // Reset cardinality toggle when multivalues is unchecked + if (!e.target.checked) { + attributeDefsSetValue( + `attributeDefs.${index}.cardinalityToggle`, + "SET" + ); + } + }} + /> + } + label={undefined} /> - } - label={undefined} - /> - </Grid>{" "} - </Grid> - </> - )} - /> + {value && ( + <Controller + control={control} + name={`attributeDefs.${index}.cardinalityToggle` as const} + key={`attributeDefs.${index}.cardinalityToggle`} + defaultValue={field?.cardinalityToggle || "SET"} + render={({ field: { value: toggleValue, onChange: toggleOnChange } }) => ( + <ToggleButtonGroup + size="small" + value={toggleValue || "SET"} + exclusive + disabled={!isEmpty(editbmAttribute)} + onChange={(e, newValue) => { + if (newValue !== null) { + toggleOnChange(newValue); + } + }} + aria-label="cardinality toggle" + > + <ToggleButton value="SET" aria-label="SET"> + SET + </ToggleButton> + <ToggleButton value="LIST" aria-label="LIST"> + LIST + </ToggleButton> + </ToggleButtonGroup> + )} + /> + )} + </Stack> + </Grid> + </Grid> + </> + )} + /> + </> )} {watched?.[index] && watched?.[index]?.typeName == "string" && ( <Controller diff --git a/dashboard/src/views/BusinessMetadata/BusinessMetadataForm.tsx b/dashboard/src/views/BusinessMetadata/BusinessMetadataForm.tsx index 5fcbb22ed..4a3ed8a18 100644 --- a/dashboard/src/views/BusinessMetadata/BusinessMetadataForm.tsx +++ b/dashboard/src/views/BusinessMetadata/BusinessMetadataForm.tsx @@ -115,7 +115,9 @@ const BusinessMetaDataForm = ({ ...(currentTypeName == "enumeration" && { enumValues: enumTypeOptions }), - multiValueSelect: str.indexOf("<") != -1 ? true : false + multiValueSelect: str.indexOf("<") != -1 ? true : false, + // Set cardinalityToggle based on existing cardinality (SET or LIST), default to SET + cardinalityToggle: editbmAttribute?.cardinality === "LIST" ? "LIST" : "SET" } ]; const dispatchState = useAppDispatch(); @@ -142,6 +144,7 @@ const BusinessMetaDataForm = ({ }, isOptional: true, cardinality: "SINGLE", + cardinalityToggle: "SET", // Default toggle value when multivalues is enabled valuesMinCount: 0, valuesMaxCount: 1, isUnique: false, @@ -157,7 +160,7 @@ const BusinessMetaDataForm = ({ control, name: "attributeDefs", rules: { - required: true + required: false // Allow creating BM without attributes } }); @@ -203,7 +206,9 @@ const BusinessMetaDataForm = ({ ...(currentTypeName == "enumeration" && { enumValues: enumTypeOptions }), - multiValueSelect: str.indexOf("<") != -1 ? true : false + multiValueSelect: str.indexOf("<") != -1 ? true : false, + // Set cardinalityToggle based on existing cardinality (SET or LIST), default to SET + cardinalityToggle: editbmAttribute?.cardinality === "LIST" ? "LIST" : "SET" } ]; if (!isEmpty(editbmAttribute)) { @@ -255,10 +260,19 @@ const BusinessMetaDataForm = ({ let attributes = !isEmpty(attributeDefsData) ? attributeDefsData.map((item) => { - const { multiValueSelect, enumType, enumValues, ...rest } = item; + const { multiValueSelect, enumType, enumValues, cardinality, cardinalityToggle, ...rest } = item; + + // Determine cardinality based on multiValueSelect and cardinality toggle + let finalCardinality = "SINGLE"; + if (multiValueSelect) { + // If multivalues is enabled, use the cardinalityToggle (SET or LIST) + // Default to SET if not specified + finalCardinality = cardinalityToggle === "LIST" ? "LIST" : (cardinality === "LIST" ? "LIST" : "SET"); + } const baseObj = { ...rest, + cardinality: finalCardinality, options: { applicableEntityTypes: JSON.stringify( rest.options.applicableEntityTypes @@ -508,6 +522,7 @@ const BusinessMetaDataForm = ({ }, isOptional: true, cardinality: "SINGLE", + cardinalityToggle: "SET", // Default toggle value when multivalues is enabled valuesMinCount: 0, valuesMaxCount: 1, isUnique: false, diff --git a/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataAtrribute.tsx b/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataAtrribute.tsx index 8ed291d90..af9c1a355 100644 --- a/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataAtrribute.tsx +++ b/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataAtrribute.tsx @@ -84,6 +84,18 @@ const BusinessMetadataAtrribute = ({ componentProps, row }: any) => { header: "Enable Multivalues", enableSorting: false }, + { + accessorKey: "cardinality", + cell: (info: any) => + !isEmpty(info.getValue()) ? ( + <Typography>{info.getValue()}</Typography> + ) : ( + <span>N/A</span> + ), + header: "Cardinality", + enableSorting: true, + show: true + }, { accessorKey: "maxStrLength", cell: (info: any) => { @@ -217,7 +229,9 @@ const BusinessMetadataAtrribute = ({ componentProps, row }: any) => { ...(currentTypeName == "enumeration" && { enumValues: enumTypeOptions }), - multiValueSelect: str.indexOf("<") != -1 ? true : false + multiValueSelect: str.indexOf("<") != -1 ? true : false, + // Set cardinalityToggle based on existing cardinality (SET or LIST), default to SET + cardinalityToggle: original?.cardinality === "LIST" ? "LIST" : "SET" } ]; setForm(true); diff --git a/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataDetailsLayout.tsx b/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataDetailsLayout.tsx index 1638565ff..b3d419246 100644 --- a/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataDetailsLayout.tsx +++ b/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataDetailsLayout.tsx @@ -179,10 +179,19 @@ const BusinessMetadataDetailsLayout = () => { let attributeDefsData = [...formAttributes]; let attributes = attributeDefsData.map((item) => { - const { multiValueSelect, enumValues, enumType, ...rest } = item; + const { multiValueSelect, enumValues, enumType, cardinality, cardinalityToggle, ...rest } = item; + + // Determine cardinality based on multiValueSelect and cardinality toggle + let finalCardinality = "SINGLE"; + if (multiValueSelect) { + // If multivalues is enabled, use the cardinalityToggle (SET or LIST) + // Default to SET if not specified + finalCardinality = cardinalityToggle === "LIST" ? "LIST" : (cardinality === "LIST" ? "LIST" : "SET"); + } return { ...rest, + cardinality: finalCardinality, ...{ options: { applicableEntityTypes: JSON.stringify( diff --git a/dashboardv2/public/css/scss/business-metadata.scss b/dashboardv2/public/css/scss/business-metadata.scss index 5bcbb5df9..2cadd14bf 100644 --- a/dashboardv2/public/css/scss/business-metadata.scss +++ b/dashboardv2/public/css/scss/business-metadata.scss @@ -240,4 +240,12 @@ .business-metadata-detail-attr-key { width: 30%; } +} + +// Disabled state for cardinality toggle buttons +.cardinality-btn.disabled, +.cardinality-btn[disabled] { + opacity: 0.6; + cursor: not-allowed; + pointer-events: none; } \ No newline at end of file diff --git a/dashboardv2/public/js/templates/business_metadata/BusinessMetadataAttributeItemView_tmpl.html b/dashboardv2/public/js/templates/business_metadata/BusinessMetadataAttributeItemView_tmpl.html index aedb6dabe..8ffb689b4 100644 --- a/dashboardv2/public/js/templates/business_metadata/BusinessMetadataAttributeItemView_tmpl.html +++ b/dashboardv2/public/js/templates/business_metadata/BusinessMetadataAttributeItemView_tmpl.html @@ -68,7 +68,17 @@ <div class="form-group" data-id="multiValueSelect"> <label class="control-label col-sm-3" for="multiValSelect">Enable Multivalues</label> <div class="col-sm-8"> - <input type="checkbox" class="form-check-input multi-value-select" data-id="multiValueSelectStatus"> + <div class="row"> + <div class="col-sm-6"> + <input type="checkbox" class="form-check-input multi-value-select" data-id="multiValueSelectStatus"> + </div> + <div class="col-sm-6" data-id="cardinalityToggleContainer" style="display: none;"> + <div class="btn-group" role="group" data-id="cardinalityToggle"> + <button type="button" class="btn btn-sm btn-default cardinality-btn" data-value="SET" data-id="cardinalitySET">SET</button> + <button type="button" class="btn btn-sm btn-default cardinality-btn active" data-value="LIST" data-id="cardinalityLIST">LIST</button> + </div> + </div> + </div> </div> </div> <div class="form-group enumtype-container" data-id="enumTypeSelectorContainer"> diff --git a/dashboardv2/public/js/views/business_metadata/BusinessMetadataAttrTableLayoutView.js b/dashboardv2/public/js/views/business_metadata/BusinessMetadataAttrTableLayoutView.js index 37058bbe2..37e6e7a83 100644 --- a/dashboardv2/public/js/views/business_metadata/BusinessMetadataAttrTableLayoutView.js +++ b/dashboardv2/public/js/views/business_metadata/BusinessMetadataAttrTableLayoutView.js @@ -108,6 +108,15 @@ define(['require', attrDetails.typeName = attrObj.typeName.replace("array<", "").replace(">", ""); attrDetails.multiValued = true; } + // Set cardinalityToggle based on existing cardinality (SET or LIST), default to SET + if (attrObj.cardinality) { + attrDetails.cardinalityToggle = (attrObj.cardinality === "LIST") ? "LIST" : "SET"; + } else { + // If cardinality is not set but it's an array type, default to SET + if (attrObj.typeName.includes('array')) { + attrDetails.cardinalityToggle = "SET"; + } + } } }); this.showDetails = false; @@ -197,6 +206,27 @@ define(['require', } }) }, + cardinality: { + label: "Cardinality", + cell: "html", + editable: false, + sortable: true, + formatter: _.extend({}, Backgrid.CellFormatter.prototype, { + fromRaw: function(rawValue, model) { + var cardinality = model.get('cardinality'); + if (!cardinality) { + // If cardinality is not set, determine from typeName + if (model.get('typeName').indexOf('array<') > -1) { + // Default to SET for array types if cardinality is not specified + cardinality = "SET"; + } else { + cardinality = "SINGLE"; + } + } + return _.escape(cardinality); + } + }) + }, maxStrLength: { label: "Max Length", cell: "html", diff --git a/dashboardv2/public/js/views/business_metadata/BusinessMetadataAttributeItemView.js b/dashboardv2/public/js/views/business_metadata/BusinessMetadataAttributeItemView.js index 5190db031..6dddb8f65 100644 --- a/dashboardv2/public/js/views/business_metadata/BusinessMetadataAttributeItemView.js +++ b/dashboardv2/public/js/views/business_metadata/BusinessMetadataAttributeItemView.js @@ -48,6 +48,10 @@ define(['require', enumValueSelector: "[data-id='enumValueSelector']", multiValueSelect: "[data-id='multiValueSelect']", multiValueSelectStatus: "[data-id='multiValueSelectStatus']", + cardinalityToggleContainer: "[data-id='cardinalityToggleContainer']", + cardinalityToggle: "[data-id='cardinalityToggle']", + cardinalitySET: "[data-id='cardinalitySET']", + cardinalityLIST: "[data-id='cardinalityLIST']", stringLengthContainer: "[data-id='stringLengthContainer']", stringLengthValue: "[data-id='stringLength']", createNewEnum: "[data-id='createNewEnum']" @@ -122,15 +126,72 @@ define(['require', this.model.set({ "enumValues": this.ui.enumValueSelector.val() }); }; events["change " + this.ui.multiValueSelectStatus] = function(e) { + var that = this; this.model.set({ "multiValueSelect": e.target.checked }); var typename = this.model.get('typeName'); if (e.target.checked) { typename = "array<" + typename + ">"; + // Show cardinality toggle when multivalues is enabled + that.ui.cardinalityToggleContainer.show(); + // Disable cardinality toggle buttons in edit mode + if (that.isAttrEdit) { + that.ui.cardinalitySET.attr('disabled', 'disabled'); + that.ui.cardinalityLIST.attr('disabled', 'disabled'); + that.ui.cardinalitySET.addClass('disabled'); + that.ui.cardinalityLIST.addClass('disabled'); + } else { + that.ui.cardinalitySET.removeAttr('disabled'); + that.ui.cardinalityLIST.removeAttr('disabled'); + that.ui.cardinalitySET.removeClass('disabled'); + that.ui.cardinalityLIST.removeClass('disabled'); + } + // Set default cardinality to SET if not already set + if (!that.model.get('cardinalityToggle')) { + that.model.set({ "cardinalityToggle": "SET" }); + that.ui.cardinalitySET.addClass('active'); + that.ui.cardinalityLIST.removeClass('active'); + } else { + // Set active button based on current value + var currentCardinality = that.model.get('cardinalityToggle'); + if (currentCardinality === "LIST") { + that.ui.cardinalityLIST.addClass('active'); + that.ui.cardinalitySET.removeClass('active'); + } else { + that.ui.cardinalitySET.addClass('active'); + that.ui.cardinalityLIST.removeClass('active'); + } + } } else { typename = typename.replace('array<', '').replace('>', ''); + // Hide cardinality toggle when multivalues is disabled + that.ui.cardinalityToggleContainer.hide(); + // Reset cardinality toggle + that.model.set({ "cardinalityToggle": "SET" }); } this.model.set({ "typeName": typename }); }; + events["click " + this.ui.cardinalitySET] = function(e) { + e.preventDefault(); + var that = this; + // Disable cardinality toggle in edit mode + if (that.isAttrEdit) { + return; + } + that.model.set({ "cardinalityToggle": "SET" }); + that.ui.cardinalitySET.addClass('active'); + that.ui.cardinalityLIST.removeClass('active'); + }; + events["click " + this.ui.cardinalityLIST] = function(e) { + e.preventDefault(); + var that = this; + // Disable cardinality toggle in edit mode + if (that.isAttrEdit) { + return; + } + that.model.set({ "cardinalityToggle": "LIST" }); + that.ui.cardinalityLIST.addClass('active'); + that.ui.cardinalitySET.removeClass('active'); + }; events["change " + this.ui.entityTypeSelector] = function(e) { var options = this.model.get('options') || {}; options.applicableEntityTypes = JSON.stringify(this.ui.entityTypeSelector.val()); @@ -223,6 +284,24 @@ define(['require', this.ui.multiValueSelect.show(); $(this.ui.multiValueSelectStatus).prop('checked', true).trigger('change'); this.ui.multiValueSelectStatus.attr("disabled", "false"); + // Set cardinality toggle based on existing cardinality (SET or LIST), default to SET + var existingCardinality = this.model.get("cardinality"); + var cardinalityToggle = (existingCardinality === "LIST") ? "LIST" : "SET"; + this.model.set({ "cardinalityToggle": cardinalityToggle }); + if (cardinalityToggle === "LIST") { + this.ui.cardinalityLIST.addClass('active'); + this.ui.cardinalitySET.removeClass('active'); + } else { + this.ui.cardinalitySET.addClass('active'); + this.ui.cardinalityLIST.removeClass('active'); + } + // Disable cardinality toggle buttons in edit mode + if (this.isAttrEdit) { + this.ui.cardinalitySET.attr('disabled', 'disabled'); + this.ui.cardinalityLIST.attr('disabled', 'disabled'); + this.ui.cardinalitySET.addClass('disabled'); + this.ui.cardinalityLIST.addClass('disabled'); + } } } }, diff --git a/dashboardv2/public/js/views/business_metadata/BusinessMetadataTableLayoutView.js b/dashboardv2/public/js/views/business_metadata/BusinessMetadataTableLayoutView.js index c02236843..53e14b9c4 100644 --- a/dashboardv2/public/js/views/business_metadata/BusinessMetadataTableLayoutView.js +++ b/dashboardv2/public/js/views/business_metadata/BusinessMetadataTableLayoutView.js @@ -244,11 +244,11 @@ define(['require', accordion: false, alwaysVisible: true, expand: function(el, model) { - el.attr('colspan', '8'); + el.attr('colspan', '9'); var attrValues = '', attrTable = $('table'), attrTableBody = $('tbody'), - attrTableHeading = "<thead><td style='display:table-cell'><b>Attribute</b></td><td style='display:table-cell'><b>Type</b></td><td style='display:table-cell'><b>Search Weight</b></td><td style='display:table-cell'><b>Enable Multivalues</b></td><td style='display:table-cell'><b>Max Length</b></td><td style='display:table-cell'><b>Applicable Type(s)</b></td><td style='display:table-cell'><b>Action</b></td></thead>", + attrTableHeading = "<thead><td style='display:table-cell'><b>Attribute</b></td><td style='display:table-cell'><b>Type</b></td><td style='display:table-cell'><b>Search Weight</b></td><td style='display:table-cell'><b>Enable Multivalues</b></td><td style='display:table-cell'><b>Cardinality</b></td><td style='display:table-cell'><b>Max Length</b></td><td style='display:table-cell'><b>Applicable Type(s)</b></td><td style='display:table-cell'><b>Action</b></td> [...] attrRow = '', attrTableDetails = ''; if (model.attributes && model.attributes.attributeDefs.length) { @@ -256,7 +256,8 @@ define(['require', var applicableEntityTypes = '', typeName = attrObj.typeName, multiSelect = '', - maxString = 'NA'; + maxString = 'NA', + cardinality = 'SINGLE'; if (attrObj.options && attrObj.options.applicableEntityTypes) { var entityTypes = JSON.parse(attrObj.options.applicableEntityTypes); _.each(entityTypes, function(values) { @@ -266,12 +267,21 @@ define(['require', if (typeName.includes('array')) { typeName = _.escape(typeName); multiSelect = 'checked'; + // Determine cardinality - use existing cardinality value or default to SET + if (attrObj.cardinality) { + cardinality = attrObj.cardinality; + } else { + cardinality = 'SET'; + } + } else { + // For non-array types, cardinality is SINGLE + cardinality = 'SINGLE'; } if (typeName.includes('string') && attrObj.options && attrObj.options.maxStrLength) { maxString = attrObj.options.maxStrLength; } - attrRow += "<tr> <td style='display:table-cell'>" + _.escape(attrObj.name) + "</td><td style='display:table-cell'>" + typeName + "</td><td style='display:table-cell'>" + _.escape(attrObj.searchWeight) + "</td><td style='display:table-cell'><input type='checkbox' class='form-check-input multi-value-select' " + multiSelect + " disabled='disabled'> </td><td style='display:table-cell'>" + maxString + "</td><td style='display:table-cell'>" + applicableEntit [...] + attrRow += "<tr> <td style='display:table-cell'>" + _.escape(attrObj.name) + "</td><td style='display:table-cell'>" + typeName + "</td><td style='display:table-cell'>" + _.escape(attrObj.searchWeight) + "</td><td style='display:table-cell'><input type='checkbox' class='form-check-input multi-value-select' " + multiSelect + " disabled='disabled'> </td><td style='display:table-cell'>" + _.escape(cardinality) + "</td><td style='display:table-cell'>" + max [...] }); var adminText = '<div class="row"><div class="col-sm-12 attr-details"><table style="padding: 50px;">' + attrTableHeading + attrRow + '</table></div></div>'; $(el).append($('<div>').html(adminText)); diff --git a/dashboardv2/public/js/views/business_metadata/CreateBusinessMetadataLayoutView.js b/dashboardv2/public/js/views/business_metadata/CreateBusinessMetadataLayoutView.js index 165b99500..200e4ad16 100644 --- a/dashboardv2/public/js/views/business_metadata/CreateBusinessMetadataLayoutView.js +++ b/dashboardv2/public/js/views/business_metadata/CreateBusinessMetadataLayoutView.js @@ -216,6 +216,27 @@ define(['require', } }, + processAttributes: function(attributes) { + var that = this; + return attributes.map(function(item) { + var multiValueSelect = item.multiValueSelect || false; + var cardinalityToggle = item.cardinalityToggle || "SET"; + + // Determine cardinality based on multiValueSelect and cardinality toggle + var finalCardinality = "SINGLE"; + if (multiValueSelect) { + // If multivalues is enabled, use the cardinalityToggle (SET or LIST) + // Default to SET if not specified + finalCardinality = cardinalityToggle === "LIST" ? "LIST" : (item.cardinality === "LIST" ? "LIST" : "SET"); + } + + // Remove cardinalityToggle from the final object as it's not part of API + var processedItem = _.omit(item, 'cardinalityToggle'); + processedItem.cardinality = finalCardinality; + + return processedItem; + }); + }, onCreateBusinessMetadata: function() { var that = this; if (this.validateValues()) { @@ -227,6 +248,9 @@ define(['require', var attributeObj = this.collection.toJSON(); if (this.collection.length === 1 && this.collection.first().get("name") === "") { attributeObj = []; + } else { + // Process attributes to include cardinality + attributeObj = this.processAttributes(attributeObj); } this.json = { "enumDefs": [], @@ -279,7 +303,9 @@ define(['require', if (selectedBusinessMetadataClone.attributeDefs === undefined) { selectedBusinessMetadataClone.attributeDefs = []; } - selectedBusinessMetadataClone.attributeDefs = selectedBusinessMetadataClone.attributeDefs.concat(this.collection.toJSON()); + // Process new attributes to include cardinality + var newAttributes = this.processAttributes(this.collection.toJSON()); + selectedBusinessMetadataClone.attributeDefs = selectedBusinessMetadataClone.attributeDefs.concat(newAttributes); this.json = { "enumDefs": [], "structDefs": [], diff --git a/dashboardv3/public/css/scss/business-metadata.scss b/dashboardv3/public/css/scss/business-metadata.scss index efed616e1..2a593bee8 100644 --- a/dashboardv3/public/css/scss/business-metadata.scss +++ b/dashboardv3/public/css/scss/business-metadata.scss @@ -240,4 +240,12 @@ .business-metadata-detail-attr-key { width: 30%; } +} + +// Disabled state for cardinality toggle buttons +.cardinality-btn.disabled, +.cardinality-btn[disabled] { + opacity: 0.6; + cursor: not-allowed; + pointer-events: none; } \ No newline at end of file diff --git a/dashboardv3/public/js/templates/business_metadata/BusinessMetadataAttributeItemView_tmpl.html b/dashboardv3/public/js/templates/business_metadata/BusinessMetadataAttributeItemView_tmpl.html index aedb6dabe..8ffb689b4 100644 --- a/dashboardv3/public/js/templates/business_metadata/BusinessMetadataAttributeItemView_tmpl.html +++ b/dashboardv3/public/js/templates/business_metadata/BusinessMetadataAttributeItemView_tmpl.html @@ -68,7 +68,17 @@ <div class="form-group" data-id="multiValueSelect"> <label class="control-label col-sm-3" for="multiValSelect">Enable Multivalues</label> <div class="col-sm-8"> - <input type="checkbox" class="form-check-input multi-value-select" data-id="multiValueSelectStatus"> + <div class="row"> + <div class="col-sm-6"> + <input type="checkbox" class="form-check-input multi-value-select" data-id="multiValueSelectStatus"> + </div> + <div class="col-sm-6" data-id="cardinalityToggleContainer" style="display: none;"> + <div class="btn-group" role="group" data-id="cardinalityToggle"> + <button type="button" class="btn btn-sm btn-default cardinality-btn" data-value="SET" data-id="cardinalitySET">SET</button> + <button type="button" class="btn btn-sm btn-default cardinality-btn active" data-value="LIST" data-id="cardinalityLIST">LIST</button> + </div> + </div> + </div> </div> </div> <div class="form-group enumtype-container" data-id="enumTypeSelectorContainer"> diff --git a/dashboardv3/public/js/views/business_metadata/BusinessMetadataAttrTableLayoutView.js b/dashboardv3/public/js/views/business_metadata/BusinessMetadataAttrTableLayoutView.js index dd1e470e2..4646abbb1 100644 --- a/dashboardv3/public/js/views/business_metadata/BusinessMetadataAttrTableLayoutView.js +++ b/dashboardv3/public/js/views/business_metadata/BusinessMetadataAttrTableLayoutView.js @@ -108,6 +108,15 @@ define(['require', attrDetails.typeName = attrObj.typeName.replace("array<", "").replace(">", ""); attrDetails.multiValued = true; } + // Set cardinalityToggle based on existing cardinality (SET or LIST), default to SET + if (attrObj.cardinality) { + attrDetails.cardinalityToggle = (attrObj.cardinality === "LIST") ? "LIST" : "SET"; + } else { + // If cardinality is not set but it's an array type, default to SET + if (attrObj.typeName.includes('array')) { + attrDetails.cardinalityToggle = "SET"; + } + } } }); this.showDetails = false; @@ -197,6 +206,27 @@ define(['require', } }) }, + cardinality: { + label: "Cardinality", + cell: "html", + editable: false, + sortable: true, + formatter: _.extend({}, Backgrid.CellFormatter.prototype, { + fromRaw: function(rawValue, model) { + var cardinality = model.get('cardinality'); + if (!cardinality) { + // If cardinality is not set, determine from typeName + if (model.get('typeName').indexOf('array<') > -1) { + // Default to SET for array types if cardinality is not specified + cardinality = "SET"; + } else { + cardinality = "SINGLE"; + } + } + return _.escape(cardinality); + } + }) + }, maxStrLength: { label: "Max Length", cell: "html", diff --git a/dashboardv3/public/js/views/business_metadata/BusinessMetadataAttributeItemView.js b/dashboardv3/public/js/views/business_metadata/BusinessMetadataAttributeItemView.js index 5190db031..6dddb8f65 100644 --- a/dashboardv3/public/js/views/business_metadata/BusinessMetadataAttributeItemView.js +++ b/dashboardv3/public/js/views/business_metadata/BusinessMetadataAttributeItemView.js @@ -48,6 +48,10 @@ define(['require', enumValueSelector: "[data-id='enumValueSelector']", multiValueSelect: "[data-id='multiValueSelect']", multiValueSelectStatus: "[data-id='multiValueSelectStatus']", + cardinalityToggleContainer: "[data-id='cardinalityToggleContainer']", + cardinalityToggle: "[data-id='cardinalityToggle']", + cardinalitySET: "[data-id='cardinalitySET']", + cardinalityLIST: "[data-id='cardinalityLIST']", stringLengthContainer: "[data-id='stringLengthContainer']", stringLengthValue: "[data-id='stringLength']", createNewEnum: "[data-id='createNewEnum']" @@ -122,15 +126,72 @@ define(['require', this.model.set({ "enumValues": this.ui.enumValueSelector.val() }); }; events["change " + this.ui.multiValueSelectStatus] = function(e) { + var that = this; this.model.set({ "multiValueSelect": e.target.checked }); var typename = this.model.get('typeName'); if (e.target.checked) { typename = "array<" + typename + ">"; + // Show cardinality toggle when multivalues is enabled + that.ui.cardinalityToggleContainer.show(); + // Disable cardinality toggle buttons in edit mode + if (that.isAttrEdit) { + that.ui.cardinalitySET.attr('disabled', 'disabled'); + that.ui.cardinalityLIST.attr('disabled', 'disabled'); + that.ui.cardinalitySET.addClass('disabled'); + that.ui.cardinalityLIST.addClass('disabled'); + } else { + that.ui.cardinalitySET.removeAttr('disabled'); + that.ui.cardinalityLIST.removeAttr('disabled'); + that.ui.cardinalitySET.removeClass('disabled'); + that.ui.cardinalityLIST.removeClass('disabled'); + } + // Set default cardinality to SET if not already set + if (!that.model.get('cardinalityToggle')) { + that.model.set({ "cardinalityToggle": "SET" }); + that.ui.cardinalitySET.addClass('active'); + that.ui.cardinalityLIST.removeClass('active'); + } else { + // Set active button based on current value + var currentCardinality = that.model.get('cardinalityToggle'); + if (currentCardinality === "LIST") { + that.ui.cardinalityLIST.addClass('active'); + that.ui.cardinalitySET.removeClass('active'); + } else { + that.ui.cardinalitySET.addClass('active'); + that.ui.cardinalityLIST.removeClass('active'); + } + } } else { typename = typename.replace('array<', '').replace('>', ''); + // Hide cardinality toggle when multivalues is disabled + that.ui.cardinalityToggleContainer.hide(); + // Reset cardinality toggle + that.model.set({ "cardinalityToggle": "SET" }); } this.model.set({ "typeName": typename }); }; + events["click " + this.ui.cardinalitySET] = function(e) { + e.preventDefault(); + var that = this; + // Disable cardinality toggle in edit mode + if (that.isAttrEdit) { + return; + } + that.model.set({ "cardinalityToggle": "SET" }); + that.ui.cardinalitySET.addClass('active'); + that.ui.cardinalityLIST.removeClass('active'); + }; + events["click " + this.ui.cardinalityLIST] = function(e) { + e.preventDefault(); + var that = this; + // Disable cardinality toggle in edit mode + if (that.isAttrEdit) { + return; + } + that.model.set({ "cardinalityToggle": "LIST" }); + that.ui.cardinalityLIST.addClass('active'); + that.ui.cardinalitySET.removeClass('active'); + }; events["change " + this.ui.entityTypeSelector] = function(e) { var options = this.model.get('options') || {}; options.applicableEntityTypes = JSON.stringify(this.ui.entityTypeSelector.val()); @@ -223,6 +284,24 @@ define(['require', this.ui.multiValueSelect.show(); $(this.ui.multiValueSelectStatus).prop('checked', true).trigger('change'); this.ui.multiValueSelectStatus.attr("disabled", "false"); + // Set cardinality toggle based on existing cardinality (SET or LIST), default to SET + var existingCardinality = this.model.get("cardinality"); + var cardinalityToggle = (existingCardinality === "LIST") ? "LIST" : "SET"; + this.model.set({ "cardinalityToggle": cardinalityToggle }); + if (cardinalityToggle === "LIST") { + this.ui.cardinalityLIST.addClass('active'); + this.ui.cardinalitySET.removeClass('active'); + } else { + this.ui.cardinalitySET.addClass('active'); + this.ui.cardinalityLIST.removeClass('active'); + } + // Disable cardinality toggle buttons in edit mode + if (this.isAttrEdit) { + this.ui.cardinalitySET.attr('disabled', 'disabled'); + this.ui.cardinalityLIST.attr('disabled', 'disabled'); + this.ui.cardinalitySET.addClass('disabled'); + this.ui.cardinalityLIST.addClass('disabled'); + } } } }, diff --git a/dashboardv3/public/js/views/business_metadata/BusinessMetadataTableLayoutView.js b/dashboardv3/public/js/views/business_metadata/BusinessMetadataTableLayoutView.js index 274630656..099a220af 100644 --- a/dashboardv3/public/js/views/business_metadata/BusinessMetadataTableLayoutView.js +++ b/dashboardv3/public/js/views/business_metadata/BusinessMetadataTableLayoutView.js @@ -244,11 +244,11 @@ define(['require', accordion: false, alwaysVisible: true, expand: function(el, model) { - el.attr('colspan', '8'); + el.attr('colspan', '9'); var attrValues = '', attrTable = $('table'), attrTableBody = $('tbody'), - attrTableHeading = "<thead><td style='display:table-cell'><b>Attribute</b></td><td style='display:table-cell'><b>Type</b></td><td style='display:table-cell'><b>Search Weight</b></td><td style='display:table-cell'><b>Enable Multivalues</b></td><td style='display:table-cell'><b>Max Length</b></td><td style='display:table-cell'><b>Applicable Type(s)</b></td><td style='display:table-cell'><b>Action</b></td></thead>", + attrTableHeading = "<thead><td style='display:table-cell'><b>Attribute</b></td><td style='display:table-cell'><b>Type</b></td><td style='display:table-cell'><b>Search Weight</b></td><td style='display:table-cell'><b>Enable Multivalues</b></td><td style='display:table-cell'><b>Cardinality</b></td><td style='display:table-cell'><b>Max Length</b></td><td style='display:table-cell'><b>Applicable Type(s)</b></td><td style='display:table-cell'><b>Action</b></td> [...] attrRow = '', attrTableDetails = ''; if (model.attributes && model.attributes.attributeDefs.length) { @@ -256,7 +256,8 @@ define(['require', var applicableEntityTypes = '', typeName = attrObj.typeName, multiSelect = '', - maxString = 'NA'; + maxString = 'NA', + cardinality = 'SINGLE'; if (attrObj.options && attrObj.options.applicableEntityTypes) { var entityTypes = JSON.parse(attrObj.options.applicableEntityTypes); _.each(entityTypes, function(values) { @@ -266,12 +267,21 @@ define(['require', if (typeName.includes('array')) { typeName = _.escape(typeName); multiSelect = 'checked'; + // Determine cardinality - use existing cardinality value or default to SET + if (attrObj.cardinality) { + cardinality = attrObj.cardinality; + } else { + cardinality = 'SET'; + } + } else { + // For non-array types, cardinality is SINGLE + cardinality = 'SINGLE'; } if (typeName.includes('string') && attrObj.options && attrObj.options.maxStrLength) { maxString = attrObj.options.maxStrLength; } - attrRow += "<tr> <td style='display:table-cell'>" + _.escape(attrObj.name) + "</td><td style='display:table-cell'>" + typeName + "</td><td style='display:table-cell'>" + _.escape(attrObj.searchWeight) + "</td><td style='display:table-cell'><input type='checkbox' class='form-check-input multi-value-select' " + multiSelect + " disabled='disabled'> </td><td style='display:table-cell'>" + maxString + "</td><td style='display:table-cell'>" + applicableEntit [...] + attrRow += "<tr> <td style='display:table-cell'>" + _.escape(attrObj.name) + "</td><td style='display:table-cell'>" + typeName + "</td><td style='display:table-cell'>" + _.escape(attrObj.searchWeight) + "</td><td style='display:table-cell'><input type='checkbox' class='form-check-input multi-value-select' " + multiSelect + " disabled='disabled'> </td><td style='display:table-cell'>" + _.escape(cardinality) + "</td><td style='display:table-cell'>" + max [...] }); var adminText = '<div class="row"><div class="col-sm-12 attr-details"><table style="padding: 50px;">' + attrTableHeading + attrRow + '</table></div></div>'; $(el).append($('<div>').html(adminText)); diff --git a/dashboardv3/public/js/views/business_metadata/CreateBusinessMetadataLayoutView.js b/dashboardv3/public/js/views/business_metadata/CreateBusinessMetadataLayoutView.js index 165b99500..200e4ad16 100644 --- a/dashboardv3/public/js/views/business_metadata/CreateBusinessMetadataLayoutView.js +++ b/dashboardv3/public/js/views/business_metadata/CreateBusinessMetadataLayoutView.js @@ -216,6 +216,27 @@ define(['require', } }, + processAttributes: function(attributes) { + var that = this; + return attributes.map(function(item) { + var multiValueSelect = item.multiValueSelect || false; + var cardinalityToggle = item.cardinalityToggle || "SET"; + + // Determine cardinality based on multiValueSelect and cardinality toggle + var finalCardinality = "SINGLE"; + if (multiValueSelect) { + // If multivalues is enabled, use the cardinalityToggle (SET or LIST) + // Default to SET if not specified + finalCardinality = cardinalityToggle === "LIST" ? "LIST" : (item.cardinality === "LIST" ? "LIST" : "SET"); + } + + // Remove cardinalityToggle from the final object as it's not part of API + var processedItem = _.omit(item, 'cardinalityToggle'); + processedItem.cardinality = finalCardinality; + + return processedItem; + }); + }, onCreateBusinessMetadata: function() { var that = this; if (this.validateValues()) { @@ -227,6 +248,9 @@ define(['require', var attributeObj = this.collection.toJSON(); if (this.collection.length === 1 && this.collection.first().get("name") === "") { attributeObj = []; + } else { + // Process attributes to include cardinality + attributeObj = this.processAttributes(attributeObj); } this.json = { "enumDefs": [], @@ -279,7 +303,9 @@ define(['require', if (selectedBusinessMetadataClone.attributeDefs === undefined) { selectedBusinessMetadataClone.attributeDefs = []; } - selectedBusinessMetadataClone.attributeDefs = selectedBusinessMetadataClone.attributeDefs.concat(this.collection.toJSON()); + // Process new attributes to include cardinality + var newAttributes = this.processAttributes(this.collection.toJSON()); + selectedBusinessMetadataClone.attributeDefs = selectedBusinessMetadataClone.attributeDefs.concat(newAttributes); this.json = { "enumDefs": [], "structDefs": [],
