Skip to content

Commit

Permalink
Fix: opening a file that has a visual with tooltip (#101)
Browse files Browse the repository at this point in the history
* WIP

* Remove speckle server urls from webaccess

* Offline support identifier for mixpanel tracks

* Do not select if null

* Remove throttle

* Simplfy logic

* Increase row limit to 150000 for pro users
  • Loading branch information
oguzhankoral authored Jan 15, 2025
1 parent 3bc82f6 commit d5e8cd8
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 134 deletions.
20 changes: 9 additions & 11 deletions src/powerbi-visual/capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"rows": {
"dataReductionAlgorithm": {
"top": {
"count": 30000
"count": 150000
}
},
"select": [
Expand Down Expand Up @@ -67,6 +67,13 @@
}
],
"objects": {
"storedData": {
"properties": {
"fullData": {
"type": { "text": true }
}
}
},
"camera": {
"properties": {
"defaultView": {
Expand Down Expand Up @@ -201,16 +208,7 @@
{
"essential": true,
"name": "WebAccess",
"parameters": [
"https://app.speckle.systems",
"https://speckle.xyz",
"https://*.speckle.xyz",
"https://latest.speckle.systems",
"https://latest.speckle.dev",
"https://*.speckle.dev",
"https://analytics.speckle.systems",
"*"
]
"parameters": ["https://analytics.speckle.systems", "*"]
},
{
"essential": false,
Expand Down
2 changes: 1 addition & 1 deletion src/powerbi-visual/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ let status = computed(() => {
})
onMounted(() => {
console.log("App mounted")
console.log('App mounted')
})
</script>
21 changes: 15 additions & 6 deletions src/powerbi-visual/src/plugins/viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IntersectionQuery,
CameraController
} from '@speckle/viewer'
import { SpeckleDataInput } from '@src/types'
import { createNanoEvents, Emitter } from 'nanoevents'
import { ColorPicker } from 'powerbi-visuals-utils-formattingmodel/lib/FormattingSettingsComponents'
import { toRaw } from 'vue'
Expand Down Expand Up @@ -37,7 +38,7 @@ export interface IViewerEvents {
forceViewerUpdate: () => void
unIsolateObjects: () => void
zoomExtends: () => void
loadObjects: (objects: object[]) => void
loadObjects: (dataInput: SpeckleDataInput) => void
}

export class ViewerHandler {
Expand Down Expand Up @@ -88,7 +89,9 @@ export class ViewerHandler {

public selectObjects = async (objectIds: string[]) => {
console.log('🔗 Handling setSelection inside ViewerHandler:', objectIds)
await this.viewer.selectObjects(objectIds)
if (objectIds) {
await this.viewer.selectObjects(objectIds)
}
}

public colorObjectsByGroup = async (
Expand Down Expand Up @@ -140,11 +143,17 @@ export class ViewerHandler {
}
}

public loadObjects = (objects: object[]) => {
const stringifiedObject = JSON.stringify(objects)
void this.viewer.unloadAll()
public loadObjects = async (dataInput: SpeckleDataInput) => {
const stringifiedObject = JSON.stringify(dataInput.objects)
await this.viewer.unloadAll()
const loader = new SpeckleOfflineLoader(this.viewer.getWorldTree(), stringifiedObject)
void this.viewer.loadObject(loader, true)
await this.viewer.loadObject(loader, true)
if (dataInput.selectedIds.length > 0) {
await this.isolateObjects(dataInput.selectedIds, false)
} else {
await this.isolateObjects(dataInput.objectIds, false)
}
await this.colorObjectsByGroup(dataInput.colorByIds)
this.viewer.zoom() // zoom extends
}

Expand Down
63 changes: 47 additions & 16 deletions src/powerbi-visual/src/store/visualStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type FieldInputState = {

export const useVisualStore = defineStore('visualStore', () => {
const host = shallowRef<powerbi.extensibility.visual.IVisualHost>()
const objectsFromStore = ref<object[]>(undefined)
const isViewerInitialized = ref<boolean>(false)
const isViewerReadyToInitialize = ref<boolean>(false)
const viewerReloadNeeded = ref<boolean>(false)
Expand Down Expand Up @@ -64,26 +65,40 @@ export const useVisualStore = defineStore('visualStore', () => {
}
}

const setObjectsFromStore = (newObjectsFromStore: object[]) => {
objectsFromStore.value = newObjectsFromStore
}

// MAKE TS HAPPY
type SpeckleObject = {
id: string
}

const loadObjectsFromStore = async () => {
lastLoadedRootObjectId.value = (dataInput.value.objects[0] as SpeckleObject).id
console.log(`📦 Loading viewer from cached data with ${lastLoadedRootObjectId.value} id.`)
viewerReloadNeeded.value = false
await viewerEmit.value('loadObjects', dataInput.value)
}

/**
* Sets upcoming data input into store to be able to pass it through viewer by evaluating the data.
* @param newValue new data input that user dragged and dropped to the fields in visual
*/
const setDataInput = (newValue: SpeckleDataInput) => {
const setDataInput = async (newValue: SpeckleDataInput) => {
dataInput.value = newValue

if (dataInput.value.isFromStore) {
await loadObjectsFromStore()
return
}
// here we have to check upcoming data is require viewer to force update! like a new model or some explicit force..
if (viewerReloadNeeded.value || !lastLoadedRootObjectId.value) {
lastLoadedRootObjectId.value = (dataInput.value.objects[0] as SpeckleObject).id
console.log(
`🔄 Forcing viewer re-render for new root object with ${lastLoadedRootObjectId.value} id.`
)
viewerReloadNeeded.value = false
viewerEmit.value('loadObjects', dataInput.value.objects)
await viewerEmit.value('loadObjects', dataInput.value)
} else {
if (dataInput.value.selectedIds.length > 0) {
viewerEmit.value('isolateObjects', dataInput.value.selectedIds)
Expand All @@ -95,28 +110,41 @@ export const useVisualStore = defineStore('visualStore', () => {
}

const setFieldInputState = (newFieldInputState: FieldInputState) => {
fieldInputState.value = newFieldInputState
console.log(fieldInputState.value, 'fieldInputState.value')

if (!fieldInputState.value.viewerData && !fieldInputState.value.objectIds) {
if (!newFieldInputState.viewerData || !newFieldInputState.objectIds) {
setInputStatus('incomplete')
} else {
setInputStatus('valid')
}
if (!isViewerInitialized.value) {
if (
fieldInputState.value.viewerData &&
fieldInputState.value.objectIds &&
!fieldInputState.value.tooltipData &&
!fieldInputState.value.colorBy
) {
viewerReloadNeeded.value = true
}

// Check for the changes on fields that viewer care, if user changes important fields, we have to ask for viewer reload
if (
fieldInputState.value.viewerData &&
fieldInputState.value.objectIds &&
(!newFieldInputState.viewerData || !newFieldInputState.objectIds)
) {
viewerReloadNeeded.value = true
}

// if (!isViewerInitialized.value) {
// if (
// fieldInputState.value.viewerData &&
// fieldInputState.value.objectIds &&
// !fieldInputState.value.tooltipData &&
// !fieldInputState.value.colorBy
// ) {
// viewerReloadNeeded.value = true
// }
// }

fieldInputState.value = newFieldInputState
}

/**
* Sets input status as flags `viewerReloadNeeded` if the new status is not 'valid'
*/
const setInputStatus = (newValue: InputState) => {
console.log('❓ Data input statues changed to:', newValue)

dataInputStatus.value = newValue
if (dataInputStatus.value !== 'valid') {
viewerReloadNeeded.value = true
Expand All @@ -129,12 +157,15 @@ export const useVisualStore = defineStore('visualStore', () => {

return {
host,
objectsFromStore,
isViewerInitialized,
viewerReloadNeeded,
dataInput,
dataInputStatus,
viewerEmit,
loadObjectsFromStore,
setHost,
setObjectsFromStore,
setViewerEmitter,
setDataInput,
setFieldInputState,
Expand Down
1 change: 1 addition & 0 deletions src/powerbi-visual/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export interface SpeckleDataInput {
colorByIds: { objectIds: string[]; slice: fs.ColorPicker; color: string }[]
objectTooltipData: Map<string, IViewerTooltip>
view: powerbi.DataViewMatrix
isFromStore: boolean
}
98 changes: 15 additions & 83 deletions src/powerbi-visual/src/utils/matrixViewUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export function validateMatrixView(options: VisualUpdateOptions): FieldInputStat
if (!hasViewerData) hasViewerData = source.roles['viewerData'] != undefined
if (!hasObjectIds) hasObjectIds = source.roles['objectIds'] != undefined
if (!hasColorFilter) hasColorFilter = source.roles['objectColorBy'] != undefined
})
})

matrixVew.columns.levels.forEach((level) => {
level.sources.forEach((source) => {
if (!hasTooltipData) hasTooltipData = source.roles['tooltipData'] != undefined
})
})
Expand Down Expand Up @@ -202,92 +207,18 @@ export function processMatrixView(
})
}
})

const jsonObjects: object[] = []
// otherwise there is no point to join collected objects
if (visualStore.viewerReloadNeeded) {
for (const objs of Object.values(objects)) {
jsonObjects.push(JSON.parse(objs.join('')))
try {
// otherwise there is no point to join collected objects
if (visualStore.viewerReloadNeeded) {
for (const objs of Object.values(objects)) {
jsonObjects.push(JSON.parse(objs.join('')))
}
}
} catch (error) {
console.error(error)
}

// matrixView.rows.root.children.forEach((streamUrlChild) => {
// const url = streamUrlChild.value

// streamUrlChild.children?.forEach((parentObjectIdChild) => {
// const parentId = parentObjectIdChild.value

// if (!hasColorFilter) {
// processObjectIdLevel(parentObjectIdChild, host, matrixView).forEach((objRes) => {
// objectIds.push(objRes.id)
// onSelectionPair(objRes.id, objRes.selectionId)
// if (objRes.shouldSelect) selectedIds.push(objRes.id)
// if (objRes.color) {
// let group = colorByIds.find((g) => g.color === objRes.color)
// if (!group) {
// group = {
// color: objRes.color,
// objectIds: []
// }
// colorByIds.push(group)
// }
// group.objectIds.push(objRes.id)
// }
// objectTooltipData.set(objRes.id, {
// selectionId: objRes.selectionId,
// data: objRes.data
// })
// })
// } else {
// if (previousPalette) host.colorPalette['colorPalette'] = previousPalette
// parentObjectIdChild.children?.forEach((colorByChild) => {
// const colorSelectionId = host
// .createSelectionIdBuilder()
// .withMatrixNode(colorByChild, matrixView.rows.levels)
// .createSelectionId()

// const color = host.colorPalette.getColor(colorByChild.value as string)
// if (colorByChild.objects) {
// console.log(
// '⚠️COLOR NODE HAS objects',
// colorByChild.objects,
// colorByChild.objects.color?.fill
// )
// }

// const colorSlice = new fs.ColorPicker({
// name: 'selectorFill',
// displayName: colorByChild.value.toString(),
// value: {
// value: color.value
// },
// selector: colorSelectionId.getSelector()
// })

// const colorGroup = {
// color: color.value,
// slice: colorSlice,
// objectIds: []
// }

// processObjectIdLevel(colorByChild, host, matrixView).forEach((objRes) => {
// objectIds.push(objRes.id)
// onSelectionPair(objRes.id, objRes.selectionId)
// if (objRes.shouldSelect) selectedIds.push(objRes.id)
// if (objRes.shouldColor) {
// colorGroup.objectIds.push(objRes.id)
// }
// objectTooltipData.set(objRes.id, {
// selectionId: objRes.selectionId,
// data: objRes.data
// })
// })
// if (colorGroup.objectIds.length > 0) colorByIds.push(colorGroup)
// })
// }
// })
// })

previousPalette = host.colorPalette['colorPalette']

return {
Expand All @@ -296,6 +227,7 @@ export function processMatrixView(
selectedIds,
colorByIds: colorByIds.length > 0 ? colorByIds : null,
objectTooltipData,
view: matrixView
view: matrixView,
isFromStore: false
}
}
7 changes: 6 additions & 1 deletion src/powerbi-visual/src/utils/mixpanel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// TBD NOTE: if we decide to tackle certification on visual and deploy it on microsoft marketplace, we would need to remove this logic
// since we enable webaccess privile for the sake of mixpanel for now.

const TRACK_URL = 'https://analytics.speckle.systems/track?ip=1'
const MIXPANEL_TOKEN = 'acd87c5a50b56df91a795e999812a3a4'
const HOST_APP_NAME = 'powerbi-visual'
const IS_OFFLINE_SUPPORT = true

export enum Event {
Create = 'Create',
Expand Down Expand Up @@ -34,7 +38,8 @@ export class Tracker {
events.map((e) => {
Object.assign(e.properties, {
token: MIXPANEL_TOKEN,
hostApp: HOST_APP_NAME
hostApp: HOST_APP_NAME,
offlineSupport: IS_OFFLINE_SUPPORT
})
return e
})
Expand Down
Loading

0 comments on commit d5e8cd8

Please sign in to comment.