From 7f9cece724aea6309e7c47c0a66348746158db40 Mon Sep 17 00:00:00 2001 From: Shafeeq Date: Tue, 12 Dec 2023 10:46:29 +0200 Subject: [PATCH 1/3] fix uid changes --- src/FormattingSettingsInterfaces.ts | 1 + src/FormattingSettingsService.ts | 98 +++++++++++++++++------------ 2 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/FormattingSettingsInterfaces.ts b/src/FormattingSettingsInterfaces.ts index 543e05b..94ba1d2 100644 --- a/src/FormattingSettingsInterfaces.ts +++ b/src/FormattingSettingsInterfaces.ts @@ -63,5 +63,6 @@ export interface IBuildFormattingSlicesParams { slices: Slice[], objectName: string, sliceNames: { [name: string]: number; }, + selectorsMap: Record, formattingSlices: visuals.FormattingSlice[] } diff --git a/src/FormattingSettingsService.ts b/src/FormattingSettingsService.ts index a3daa96..a20e209 100644 --- a/src/FormattingSettingsService.ts +++ b/src/FormattingSettingsService.ts @@ -1,10 +1,11 @@ import powerbi from "powerbi-visuals-api"; + +import { Cards, CardGroupEntity, CompositeCard, Model, SimpleCard, Slice, SimpleSlice } from "./FormattingSettingsComponents"; import { formattingSettings } from "."; -import { Cards, CardGroupEntity, CompositeCard, Group, Model, SimpleCard, Slice } from "./FormattingSettingsComponents"; import { IBuildFormattingSlicesParams, IFormattingSettingsService } from "./FormattingSettingsInterfaces"; -import visuals = powerbi.visuals; import ILocalizationManager = powerbi.extensibility.ILocalizationManager; +import visuals = powerbi.visuals; export class FormattingSettingsService implements IFormattingSettingsService { private localizationManager: ILocalizationManager; @@ -28,7 +29,7 @@ export class FormattingSettingsService implements IFormattingSettingsService { defaultSettings.cards?.forEach((card: Cards) => { if (card instanceof CompositeCard) card.topLevelSlice?.setPropertiesValues(dataViewObjects, card.name); - const cardGroupInstances = (card instanceof SimpleCard ? [ card ] : card.groups); + const cardGroupInstances = (card instanceof SimpleCard ? [card] : card.groups); cardGroupInstances.forEach((cardGroupInstance: CardGroupEntity) => { // Set current top level toggle value cardGroupInstance.topLevelSlice?.setPropertiesValues(dataViewObjects, card.name); @@ -57,9 +58,9 @@ export class FormattingSettingsService implements IFormattingSettingsService { let formattingModel = { cards: [] } - + formattingSettingsModel.cards - .filter(({visible = true}) => visible) + .filter(({ visible = true }) => visible) .forEach((card: Cards) => { let formattingCard: visuals.FormattingCard = { displayName: (this.localizationManager && card.displayNameKey) ? this.localizationManager.getDisplayName(card.displayNameKey) : card.displayName, @@ -80,26 +81,26 @@ export class FormattingSettingsService implements IFormattingSettingsService { card.onPreProcess?.(); const isSimpleCard = card instanceof SimpleCard; - const cardGroupInstances = (isSimpleCard ? - [ card ].filter(({visible = true}) => visible) : - card.groups.filter(({visible = true}) => visible)); + const cardGroupInstances = (isSimpleCard ? + [card].filter(({ visible = true }) => visible) : + card.groups.filter(({ visible = true }) => visible)); cardGroupInstances .forEach((cardGroupInstance: CardGroupEntity) => { const groupUid = cardGroupInstance.name + "-group"; // Build formatting group for each group const formattingGroup: visuals.FormattingGroup = { - displayName: isSimpleCard ? undefined : (this.localizationManager && cardGroupInstance.displayNameKey) - ? this.localizationManager.getDisplayName(cardGroupInstance.displayNameKey) : cardGroupInstance.displayName, - description: isSimpleCard ? undefined : (this.localizationManager && cardGroupInstance.descriptionKey) - ? this.localizationManager.getDisplayName(cardGroupInstance.descriptionKey) : cardGroupInstance.description, - slices: [], - uid: groupUid, - collapsible: cardGroupInstance.collapsible, - delaySaveSlices: cardGroupInstance.delaySaveSlices, - disabled: cardGroupInstance.disabled, - disabledReason: cardGroupInstance.disabledReason, - } + displayName: isSimpleCard ? undefined : (this.localizationManager && cardGroupInstance.displayNameKey) + ? this.localizationManager.getDisplayName(cardGroupInstance.displayNameKey) : cardGroupInstance.displayName, + description: isSimpleCard ? undefined : (this.localizationManager && cardGroupInstance.descriptionKey) + ? this.localizationManager.getDisplayName(cardGroupInstance.descriptionKey) : cardGroupInstance.description, + slices: [], + uid: groupUid, + collapsible: cardGroupInstance.collapsible, + delaySaveSlices: cardGroupInstance.delaySaveSlices, + disabled: cardGroupInstance.disabled, + disabledReason: cardGroupInstance.disabledReason, + } formattingCard.groups.push(formattingGroup); // In case formatting model adds data points or top categories (Like when you modify specific visual category color). @@ -107,6 +108,12 @@ export class FormattingSettingsService implements IFormattingSettingsService { // Solution => Save slice names to modify each slice uid to be unique by adding counter value to the new slice uid const sliceNames: { [name: string]: number } = {}; + // creating slices using same uid as described above is ok at this stage, powerbi identify the specific slice by the selector. + // so the correct thing to do is to provide a unique selector for each datapoint if needed. + // We are keeping sliceNames for backward compatibility. + // This map helps us to check if we need to change the uid + const selectorsMap: Record = {} + // Build formatting container slice for each property if (cardGroupInstance.container) { const container = cardGroupInstance.container; @@ -132,7 +139,7 @@ export class FormattingSettingsService implements IFormattingSettingsService { } // Build formatting slices and add them to current formatting container item - this.buildFormattingSlices({slices: containerItem.slices, objectName, sliceNames, formattingSlices: formattingContainerItem.slices}); + this.buildFormattingSlices({ slices: containerItem.slices, objectName, sliceNames, selectorsMap, formattingSlices: formattingContainerItem.slices }); formattingContainer.containerItems.push(formattingContainerItem); }); @@ -143,10 +150,10 @@ export class FormattingSettingsService implements IFormattingSettingsService { if (cardGroupInstance.topLevelSlice) { let topLevelToggleSlice: visuals.FormattingSlice = cardGroupInstance.topLevelSlice.getFormattingSlice(objectName, this.localizationManager); topLevelToggleSlice.suppressDisplayName = true; - (formattingGroup.displayName==undefined ? formattingCard : formattingGroup).topLevelToggle = (topLevelToggleSlice); + (formattingGroup.displayName == undefined ? formattingCard : formattingGroup).topLevelToggle = (topLevelToggleSlice); } // Build formatting slice for each property - this.buildFormattingSlices({slices: cardGroupInstance.slices, objectName, sliceNames, formattingSlices: formattingGroup.slices as visuals.FormattingSlice[]}); + this.buildFormattingSlices({ slices: cardGroupInstance.slices, objectName, sliceNames, selectorsMap, formattingSlices: formattingGroup.slices as visuals.FormattingSlice[] }); } }); @@ -159,24 +166,35 @@ export class FormattingSettingsService implements IFormattingSettingsService { return formattingModel; } - private buildFormattingSlices({slices, objectName, sliceNames, formattingSlices }: IBuildFormattingSlicesParams) { + private buildFormattingSlices({ slices, objectName, sliceNames, selectorsMap, formattingSlices }: IBuildFormattingSlicesParams) { // Filter slices based on their visibility - slices?.filter(({visible = true}) => visible) + slices?.filter(({ visible = true }) => visible) .forEach((slice: Slice) => { - let formattingSlice: visuals.FormattingSlice = slice?.getFormattingSlice(objectName, this.localizationManager); - - if (formattingSlice) { - // Modify formatting slice uid if needed - if (sliceNames[slice.name] === undefined) { - sliceNames[slice.name] = 0; - } else { - sliceNames[slice.name]++; - formattingSlice.uid = `${formattingSlice.uid}-${sliceNames[slice.name]}`; - } + let formattingSlice: visuals.FormattingSlice = slice?.getFormattingSlice(objectName, this.localizationManager); + + if (formattingSlice) { + // Modify formatting slice uid if needed + const sliceName = slice.name; + const sliceSelector = (slice as SimpleSlice).selector; + if (sliceNames[slice.name] === undefined) { + selectorsMap[sliceName] = {}; + sliceNames[slice.name] = 0; + if (sliceSelector) { + selectorsMap[slice.name][JSON.stringify(sliceSelector)] = true; + } + } else { + sliceNames[slice.name]++; + const selectorExistsInMap = sliceSelector ? selectorsMap[slice.name][JSON.stringify(sliceSelector)] : undefined; + if (sliceSelector && !selectorExistsInMap) { + selectorsMap[slice.name][JSON.stringify(sliceSelector)] = true; + } else { + formattingSlice.uid = `${formattingSlice.uid}-${sliceNames[slice.name]}`; + } + } - formattingSlices.push(formattingSlice); - } - }); + formattingSlices.push(formattingSlice); + } + }); } private getRevertToDefaultDescriptor(card: Cards): visuals.FormattingDescriptor[] { @@ -189,9 +207,9 @@ export class FormattingSettingsService implements IFormattingSettingsService { if (card instanceof CompositeCard && card.topLevelSlice) revertToDefaultDescriptors.push(...card.topLevelSlice?.getRevertToDefaultDescriptor(card.name)); - const cardGroupInstances = (card instanceof SimpleCard ? - [ card ].filter(({visible = true}) => visible) : - card.groups.filter(({visible = true}) => visible)); + const cardGroupInstances = (card instanceof SimpleCard ? + [card].filter(({ visible = true }) => visible) : + card.groups.filter(({ visible = true }) => visible)); cardGroupInstances.forEach((cardGroupInstance: CardGroupEntity) => { cardSlicesDefaultDescriptors = this.getSlicesRevertToDefaultDescriptor(card.name, cardGroupInstance.slices, sliceNames, cardGroupInstance.topLevelSlice); From 22a7e9a843459c9b2b317610cc8af59995e59388 Mon Sep 17 00:00:00 2001 From: Shafeeq Date: Tue, 12 Dec 2023 16:05:46 +0200 Subject: [PATCH 2/3] comments --- src/FormattingSettingsInterfaces.ts | 2 -- src/FormattingSettingsService.ts | 27 +++++++++++++-------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/FormattingSettingsInterfaces.ts b/src/FormattingSettingsInterfaces.ts index 94ba1d2..40416a6 100644 --- a/src/FormattingSettingsInterfaces.ts +++ b/src/FormattingSettingsInterfaces.ts @@ -62,7 +62,5 @@ export interface IFormattingSettingsSlice { export interface IBuildFormattingSlicesParams { slices: Slice[], objectName: string, - sliceNames: { [name: string]: number; }, - selectorsMap: Record, formattingSlices: visuals.FormattingSlice[] } diff --git a/src/FormattingSettingsService.ts b/src/FormattingSettingsService.ts index a20e209..65989e0 100644 --- a/src/FormattingSettingsService.ts +++ b/src/FormattingSettingsService.ts @@ -103,17 +103,6 @@ export class FormattingSettingsService implements IFormattingSettingsService { } formattingCard.groups.push(formattingGroup); - // In case formatting model adds data points or top categories (Like when you modify specific visual category color). - // these categories use same object name and property name from capabilities and the generated uid will be the same for these formatting categories properties - // Solution => Save slice names to modify each slice uid to be unique by adding counter value to the new slice uid - const sliceNames: { [name: string]: number } = {}; - - // creating slices using same uid as described above is ok at this stage, powerbi identify the specific slice by the selector. - // so the correct thing to do is to provide a unique selector for each datapoint if needed. - // We are keeping sliceNames for backward compatibility. - // This map helps us to check if we need to change the uid - const selectorsMap: Record = {} - // Build formatting container slice for each property if (cardGroupInstance.container) { const container = cardGroupInstance.container; @@ -139,7 +128,7 @@ export class FormattingSettingsService implements IFormattingSettingsService { } // Build formatting slices and add them to current formatting container item - this.buildFormattingSlices({ slices: containerItem.slices, objectName, sliceNames, selectorsMap, formattingSlices: formattingContainerItem.slices }); + this.buildFormattingSlices({ slices: containerItem.slices, objectName, formattingSlices: formattingContainerItem.slices }); formattingContainer.containerItems.push(formattingContainerItem); }); @@ -153,7 +142,7 @@ export class FormattingSettingsService implements IFormattingSettingsService { (formattingGroup.displayName == undefined ? formattingCard : formattingGroup).topLevelToggle = (topLevelToggleSlice); } // Build formatting slice for each property - this.buildFormattingSlices({ slices: cardGroupInstance.slices, objectName, sliceNames, selectorsMap, formattingSlices: formattingGroup.slices as visuals.FormattingSlice[] }); + this.buildFormattingSlices({ slices: cardGroupInstance.slices, objectName, formattingSlices: formattingGroup.slices as visuals.FormattingSlice[] }); } }); @@ -166,7 +155,17 @@ export class FormattingSettingsService implements IFormattingSettingsService { return formattingModel; } - private buildFormattingSlices({ slices, objectName, sliceNames, selectorsMap, formattingSlices }: IBuildFormattingSlicesParams) { + private buildFormattingSlices({ slices, objectName, formattingSlices }: IBuildFormattingSlicesParams) { + // In case formatting model adds data points or top categories (Like when you modify specific visual category color). + // these categories use same object name and property name from capabilities and the generated uid will be the same for these formatting categories properties + // Solution => Save slice names to modify each slice uid to be unique by adding counter value to the new slice uid + const sliceNames: { [name: string]: number } = {}; + + // We are also supporting slices with the same name if they differ in the selector field (not the altConstantSelector). + // If the slices with the same name have different selectors we keep the uid as is, else we change it. + // We recommend using different selectors in the selector field for slices with the same name. + const selectorsMap: Record = {} + // Filter slices based on their visibility slices?.filter(({ visible = true }) => visible) .forEach((slice: Slice) => { From 01202f3f94b20344f6ae0f516e19a525ca3d5b81 Mon Sep 17 00:00:00 2001 From: Shafeeq Date: Wed, 24 Jan 2024 19:05:23 +0200 Subject: [PATCH 3/3] comments --- src/FormattingSettingsService.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/FormattingSettingsService.ts b/src/FormattingSettingsService.ts index 65989e0..5ff9669 100644 --- a/src/FormattingSettingsService.ts +++ b/src/FormattingSettingsService.ts @@ -175,19 +175,20 @@ export class FormattingSettingsService implements IFormattingSettingsService { // Modify formatting slice uid if needed const sliceName = slice.name; const sliceSelector = (slice as SimpleSlice).selector; - if (sliceNames[slice.name] === undefined) { + const stringifiedSelector = sliceSelector ? JSON.stringify(sliceSelector) : ''; + if (sliceNames[sliceName] === undefined) { selectorsMap[sliceName] = {}; - sliceNames[slice.name] = 0; + sliceNames[sliceName] = 0; if (sliceSelector) { - selectorsMap[slice.name][JSON.stringify(sliceSelector)] = true; + selectorsMap[sliceName][stringifiedSelector] = true; } } else { - sliceNames[slice.name]++; - const selectorExistsInMap = sliceSelector ? selectorsMap[slice.name][JSON.stringify(sliceSelector)] : undefined; + sliceNames[sliceName]++; + const selectorExistsInMap = stringifiedSelector && selectorsMap[sliceName][stringifiedSelector]; if (sliceSelector && !selectorExistsInMap) { - selectorsMap[slice.name][JSON.stringify(sliceSelector)] = true; + selectorsMap[sliceName][stringifiedSelector] = true; } else { - formattingSlice.uid = `${formattingSlice.uid}-${sliceNames[slice.name]}`; + formattingSlice.uid = `${formattingSlice.uid}-${sliceNames[sliceName]}`; } }