diff --git a/react/javascript/tile-sdk/src/components/Inspector/components/EventTester/EventTester.js b/react/javascript/tile-sdk/src/components/Inspector/components/EventTester/EventTester.js
index 5f4bce5..a71b5e3 100644
--- a/react/javascript/tile-sdk/src/components/Inspector/components/EventTester/EventTester.js
+++ b/react/javascript/tile-sdk/src/components/Inspector/components/EventTester/EventTester.js
@@ -23,7 +23,13 @@
SOFTWARE.
*/
-import React, { useCallback, useContext, useState, useRef } from 'react'
+import React, {
+ useCallback,
+ useContext,
+ useState,
+ useRef,
+ useMemo,
+} from 'react'
import {
Space,
Accordion2,
@@ -32,18 +38,75 @@ import {
Grid,
ButtonOutline,
FieldToggleSwitch,
+ Paragraph,
} from '@looker/components'
import { ExtensionContext40 } from '@looker/extension-sdk-react'
+import {
+ getDrillLinks,
+ getCrossfilterSelection,
+ CrossfilterSelection,
+} from '../../../../utils/utils'
export const EventTester = () => {
const {
extensionSDK,
tileSDK,
- tileHostData: { dashboardFilters },
+ tileHostData: {
+ dashboardFilters,
+ isExploring,
+ isDashboardEditing,
+ isDashboardCrossFilteringEnabled,
+ },
visualizationData,
+ visualizationSDK,
} = useContext(ExtensionContext40)
const [runDashboard, setRunDashboard] = useState(false)
const openDrillMenuButtonRef = useRef()
+ const toggleCrossFilterButtonRef = useRef()
+
+ const currentCrossFiltersSelection = useMemo(() => {
+ if (isDashboardCrossFilteringEnabled && visualizationSDK) {
+ const queryResponse = visualizationSDK.queryResponse
+ if (queryResponse) {
+ let row
+ let pivot
+ if (queryResponse?.data.length > 0) {
+ row = queryResponse?.data[0]
+ }
+ if (queryResponse?.pivot?.length > 0) {
+ pivot = queryResponse?.pivot[0]
+ }
+ return getCrossfilterSelection(row, pivot)
+ }
+ }
+ return undefined
+ }, [isDashboardCrossFilteringEnabled, visualizationSDK, visualizationData])
+
+ const currentCrossFiltersSelectionDesc = useMemo(() => {
+ if (!isExploring) {
+ switch (currentCrossFiltersSelection) {
+ case CrossfilterSelection.NONE: {
+ return 'None'
+ }
+ case CrossfilterSelection.SELECTED: {
+ return 'Selected'
+ }
+ case CrossfilterSelection.UNSELECTED: {
+ return 'Unselected'
+ }
+ default: {
+ return isDashboardCrossFilteringEnabled
+ ? 'Unknown'
+ : 'Cross filtering disabled'
+ }
+ }
+ }
+ return 'Not supported when exploring'
+ }, [
+ currentCrossFiltersSelection,
+ isDashboardCrossFilteringEnabled,
+ isExploring,
+ ])
const addErrorsClick = useCallback(() => {
tileSDK.addErrors({
@@ -56,6 +119,17 @@ export const EventTester = () => {
tileSDK.clearErrors()
}, [tileSDK])
+ const buildEvent = useCallback((buttonRef) => {
+ let event = { pageX: 0, pageY: 0 }
+ if (buttonRef.current) {
+ const { bottom, left } = buttonRef.current.getBoundingClientRect()
+ // Add 95px to the x coordinate to shift the menu
+ // under the button.
+ event = { pageX: left + 95, pageY: bottom }
+ }
+ return event
+ }, [])
+
const triggerClick = useCallback(
(event) => {
// Taken from custom visualizations 2
@@ -80,34 +154,43 @@ export const EventTester = () => {
const toggleCrossFilterClick = useCallback(
(event) => {
- // TODO pivot and row data needs to be populated
- tileSDK.toggleCrossFilter({ pivot: {}, row: {} }, event)
+ if (isDashboardCrossFilteringEnabled && visualizationSDK) {
+ const queryResponse = visualizationSDK.queryResponse
+ if (queryResponse) {
+ let row
+ let pivot
+ if (queryResponse?.data.length > 0) {
+ row = queryResponse?.data[0]
+ }
+ if (queryResponse?.pivot?.length > 0) {
+ pivot = queryResponse?.pivot[0]
+ }
+ tileSDK.toggleCrossFilter(
+ { pivot, row },
+ buildEvent(toggleCrossFilterButtonRef)
+ )
+ }
+ }
},
- [tileSDK]
+ [
+ tileSDK,
+ isDashboardCrossFilteringEnabled,
+ visualizationSDK,
+ visualizationData,
+ ]
)
const openDrillMenuClick = useCallback(
(_event) => {
- let event = { pageX: 0, pageY: 0 }
- let links = []
- const data = visualizationData?.queryResponse?.data
- if (data && data.length > 0) {
- const row = data[0]
- const column = Array.from(Object.keys(row)).find(
- (column) => row[column].links?.length > 0
- )
- if (column) {
- links = [...row[column].links]
- }
- }
- if (openDrillMenuButtonRef.current) {
- const { bottom, left } =
- openDrillMenuButtonRef.current.getBoundingClientRect()
- // Add 95px to the x coordinate to shift the menu
- // under the button.
- event = { pageX: left + 95, pageY: bottom }
+ const links = getDrillLinks(visualizationData?.queryResponse?.data, 0, 0)
+ if (links.length === 0) {
+ tileSDK.addErrors({
+ title: 'Drilling Error',
+ message: 'No drill links found',
+ })
+ } else {
+ tileSDK.openDrillMenu({ links }, buildEvent(openDrillMenuButtonRef))
}
- tileSDK.openDrillMenu({ links }, event)
},
[tileSDK, visualizationData]
)
@@ -159,7 +242,25 @@ export const EventTester = () => {
Test clear errors
-
+
+ Test run dashboard
+
+
+ Test stop dashboard
+
+
Test trigger
{
>
Test open drill menu
-
+
Test toggle cross filter
-
- Test run dashboard
-
-
- Test stop dashboard
-
-
- Test update filters
-
- setRunDashboard(event.target.checked)}
- on={runDashboard}
- >
+
+ Cross filter selection: {currentCrossFiltersSelectionDesc}
+
-
+
+ Test update filters
+
+ setRunDashboard(event.target.checked)}
+ on={runDashboard}
+ >
+
Test open schedule dialog
diff --git a/react/javascript/tile-sdk/src/utils/utils.js b/react/javascript/tile-sdk/src/utils/utils.js
new file mode 100644
index 0000000..9260bb8
--- /dev/null
+++ b/react/javascript/tile-sdk/src/utils/utils.js
@@ -0,0 +1,90 @@
+/*
+
+ MIT License
+
+ Copyright (c) 2023 Looker Data Sciences, Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+ */
+
+// TODO move these methods into the extension SDK
+
+/**
+ * Get drill links for a row and a column. Column can be a numeric
+ * index OR the name of the column.
+ */
+export const getDrillLinks = (data, row, column) => {
+ let links = []
+ if (data && data.length && row < data.length) {
+ const selectedRow = data[row]
+ const columnKeys = Array.from(Object.keys(selectedRow))
+ let selectedColumnKey
+ if (typeof column === 'number') {
+ selectedColumnKey = columnKeys[column]
+ } else if (columnKeys.includes(column)) {
+ selectedColumnKey = column
+ }
+ if (selectedColumnKey) {
+ const selectedColumn = selectedRow[selectedColumnKey]
+ if (selectedColumn?.links?.length && selectedColumn?.links?.length > 0) {
+ links = [...selectedColumn.links]
+ }
+ }
+ }
+ return links
+}
+
+export const CrossfilterSelection = Object.seal({
+ NONE: 0,
+ SELECTED: 1,
+ UNSELECTED: 2,
+})
+
+/**
+ * Checks if crossfilters are selected for a Row + Pivot
+ * by inspecting the crossfilterSelection property of the cells
+ * Will return true if every cell is selected (ignoring undefined selections)
+ * Call only when crossfilters are present in the element
+ */
+export const getCrossfilterSelection = (row, pivot) => {
+ // get row crossfilterSelection values
+ const rowCells = Object.values(row || {}).map(
+ (item) => item.crossfilterSelection
+ )
+ // get pivot crossfilterSelection values
+ const pivotCells = Object.values(pivot?.metadata || {}).map(
+ (item) => item.crossfilterSelection
+ )
+ // merge both lists and remove undefined values
+ const cells = [...rowCells, ...pivotCells].filter((value) => !!value)
+
+ // if there are non undefined any selection values
+ if (cells.length) {
+ // if every cell is selected
+ return cells.every((value) => value === CrossfilterSelection.SELECTED)
+ ? // return selected
+ CrossfilterSelection.SELECTED
+ : // if at least one is unselected, return unselected
+ CrossfilterSelection.UNSELECTED
+ }
+
+ // if all cells are undefined, return none
+ return CrossfilterSelection.NONE
+}