Skip to content

Commit

Permalink
Dimensions and Metrics Explorer UX update
Browse files Browse the repository at this point in the history
Use the autocomplete component to search metrics/dimensions.
The main list of all dimensions/metrics remains unchanged when selecting fields for compatibility check.
The list of options available in Autocomplete components is updated based on fields' compatibility.
Display field categories in accordion with Expand all / Collapse all options.
Add option to display compatible only/incompatible only/all fields.
Render field description using Markdown.
  • Loading branch information
ikuleshov committed Sep 26, 2024
1 parent ebc919e commit 8a6b0e5
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 270 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,23 @@
"react-helmet": "^6.1.0",
"react-icons": "^4.8.0",
"react-json-view": "^1.21.3",
"react-loader-spinner": "^6.1.6",
"react-loader-spinner": "^5.3.4",
"react-markdown": "^9.0.1",
"react-redux": "^8.0.5",
"react-syntax-highlighter": "^15.5.0",
"redux": "^4.2.1",
"remark-gfm": "^4.0.0",
"tsconfig-paths-webpack-plugin": "^4.0.1",
"use-debounce": "^9.0.4",
"use-query-params": "^0.4.3"
},
"devDependencies": {
"@reach/router": "^1.3.4",
"@testing-library/jest-dom": "^6.1.2",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^13.1.8",
"@types/gapi": "^0.0.39",
"@types/gapi.auth2": "^0.0.54",
"@reach/router": "^1.3.4",
"@types/gapi.client.analytics": "^3.0.7",
"@types/gapi.client.analyticsadmin": "^1.0.0",
"@types/gapi.client.analyticsdata": "^1.0.2",
Expand Down
188 changes: 123 additions & 65 deletions src/components/ga4/DimensionsMetricsExplorer/Compatible.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { navigate } from "gatsby"
import * as React from "react"
import QueryExplorerLink from "../QueryExplorer/BasicReport/QueryExplorerLink"
import { CompatibleHook } from "./useCompatibility"
import Autocomplete from '@mui/material/Autocomplete';
import {Dimension, Metric} from '@/components/ga4/DimensionsMetricsExplorer/useDimensionsAndMetrics';
import TextField from '@mui/material/TextField';

const PREFIX = 'Compatible';

Expand All @@ -23,9 +26,9 @@ const classes = {
};

const StyledPaper = styled(Paper)((
{
theme
}
{
theme
}
) => ({
[`&.${classes.compatible}`]: {
padding: theme.spacing(2),
Expand Down Expand Up @@ -57,88 +60,143 @@ const StyledPaper = styled(Paper)((
}));

const WithProperty: React.FC<
CompatibleHook & { property: PropertySummary | undefined }
CompatibleHook & { property: PropertySummary | undefined }
> = ({
dimensions,
metrics,
removeMetric,
removeDimension,
property,
hasFieldSelected,
}) => {
dimensions,
metrics,
addDimension,
removeMetric,
removeDimension, setDimensions, setMetrics,
property,
hasFieldSelected, incompatibleDimensions, incompatibleMetrics,
allDimensions,
allMetrics,
}) => {


if (property === undefined) {
return null
}

return (
<>
<Typography>
As you choose dimensions & metrics (by clicking the checkbox next to
their name), they will be added here. Incompatible dimensions & metrics
will be grayed out.
</Typography>
<div className={classes.chipGrid}>
<Typography className={classes.chipLabel}>Dimensions:</Typography>
<div className={classes.chips}>
{dimensions !== undefined && dimensions.length > 0
? dimensions.map(d => (
<Chip
variant="outlined"
key={d.apiName}
label={d.apiName}
onClick={() => navigate(`#${d.apiName}`)}
onDelete={() => removeDimension(d)}
/>
))
: "No dimensions selected."}
</div>
<Typography className={classes.chipLabel}>Metrics:</Typography>
<div className={classes.chips}>
{metrics !== undefined && metrics.length > 0
? metrics?.map(m => (
<Chip
variant="outlined"
key={m.apiName}
label={m.apiName}
onDelete={() => removeMetric(m)}
/>
))
: "No metrics selected."}
</div>
</div>
{hasFieldSelected && (
<>
<Typography>
Use these fields in the{" "}
<QueryExplorerLink dimensions={dimensions} metrics={metrics} />
As you choose dimensions & metrics, they will be added here.
Incompatible dimensions & metrics will be grayed out.
</Typography>
)}
</>
<div className={classes.chipGrid}>
<Typography className={classes.chipLabel}>Dimensions:</Typography>
<div className={classes.chips}>
<Autocomplete<Dimension, true>
fullWidth
autoComplete
multiple
isOptionEqualToValue={(a, b) => a.apiName === b.apiName}
onChange={(event, value) => setDimensions(value)}
value={dimensions || []}
options={allDimensions}
getOptionDisabled={(option) =>
incompatibleDimensions?.find(d => d.apiName === option.apiName) !== undefined
}
getOptionLabel={dimension => `${dimension.apiName}: ${dimension.uiName}` || ""}
renderInput={params => (
<TextField
{...params}
size="small"
variant="outlined"
helperText={
<>
Select dimensions.
</>
}
/>
)}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => {
return (
<Chip
key={option.apiName}
label={option.apiName}
onClick={() => navigate(`#${option.apiName}`)}
onDelete={() => removeDimension(option)}
/>
);
})
}
/>
</div>
<Typography className={classes.chipLabel}>Metrics:</Typography>
<div className={classes.chips}>
<Autocomplete<Metric, true>
fullWidth
autoComplete
multiple
isOptionEqualToValue={(a, b) => a.apiName === b.apiName}
onChange={(event, value) => setMetrics(value)}
value={metrics || []}
options={allMetrics}
getOptionDisabled={(option) =>
incompatibleMetrics?.find(d => d.apiName === option.apiName) !== undefined
}
getOptionLabel={metric => `${metric.apiName}: ${metric.uiName}` || ""}
renderInput={params => (
<TextField
{...params}
size="small"
variant="outlined"
helperText={
<>
Select metrics.
</>
}
/>
)}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => {
return (
<Chip
key={option.apiName}
label={option.apiName}
onClick={() => navigate(`#${option.apiName}`)}
onDelete={() => removeMetric(option)}
/>
);
})
}
/>
</div>
</div>
{hasFieldSelected && (
<Typography>
Use these fields in the{" "}
<QueryExplorerLink dimensions={dimensions} metrics={metrics} />
</Typography>
)}
</>
)
}

const Compatible: React.FC<
CompatibleHook & { property: PropertySummary | undefined }
CompatibleHook & { allDimensions: Dimension[], allMetrics: Metric[], property: PropertySummary | undefined }
> = props => {

const { reset, property, hasFieldSelected } = props

return (
<StyledPaper className={classes.compatible}>
<Typography variant="h3">
Compatible Fields
<IconButton disabled={!hasFieldSelected} onClick={reset}>
<Replay />
</IconButton>
</Typography>
<WithProperty {...props} property={property} />
{property === undefined && (
<Typography>
Pick a property above to enable this functionality.
<StyledPaper className={classes.compatible}>
<Typography variant="h3">
Compatible Fields
<IconButton disabled={!hasFieldSelected} onClick={reset}>
<Replay />
</IconButton>
</Typography>
)}
</StyledPaper>
<WithProperty {...props} property={property} />
{property === undefined && (
<Typography>
Pick a property above to enable this functionality.
</Typography>
)}
</StyledPaper>
);
}

Expand Down
87 changes: 36 additions & 51 deletions src/components/ga4/DimensionsMetricsExplorer/Field.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import * as React from "react"

import { styled } from '@mui/material/styles';
import {styled} from '@mui/material/styles';

import IconLink from "@mui/icons-material/Link"
import Typography from "@mui/material/Typography"

import InlineCode from "@/components/InlineCode"
import { CopyIconButton } from "@/components/CopyButton"
import {CopyIconButton} from "@/components/CopyButton"
import ExternalLink from "@/components/ExternalLink"
import { Dimension, Metric } from "./useDimensionsAndMetrics"
import { QueryParam } from "."
import { AccountSummary, PropertySummary } from "@/types/ga4/StreamPicker"
import {Dimension, Metric} from "./useDimensionsAndMetrics"
import {QueryParam} from "."
import {AccountSummary, PropertySummary} from "@/types/ga4/StreamPicker"
import LabeledCheckbox from "@/components/LabeledCheckbox"
import { CompatibleHook } from "./useCompatibility"
import {CompatibleHook} from "./useCompatibility"
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'

const PREFIX = 'Field';

Expand Down Expand Up @@ -128,7 +130,6 @@ interface FieldProps extends CompatibleHook {

const Field: React.FC<FieldProps> = props => {


const {
field,
account,
Expand Down Expand Up @@ -158,27 +159,6 @@ const Field: React.FC<FieldProps> = props => {
return `${baseURL}${search}#${apiName}`
}, [field, apiName, account, property])

const withLinks = React.useMemo(() => {
let remainingText = description
let elements: (JSX.Element | string)[] = []
let mightHaveLinks = true
while (mightHaveLinks) {
const result = linkifyText(remainingText, elements)
remainingText = result[0]
elements = result[1]
if (remainingText === "") {
mightHaveLinks = false
}
}
return (
<>
{elements.map((e, idx) => (
<React.Fragment key={idx}>{e}</React.Fragment>
))}
</>
)
}, [description])

const isCompatible = React.useMemo(() => {
return (
incompatibleDimensions?.find(d => d.apiName === field.value.apiName) ===
Expand Down Expand Up @@ -208,29 +188,34 @@ const Field: React.FC<FieldProps> = props => {
}, [checked, addDimension, addMetric, removeDimension, removeMetric, field])

return (
<Root id={apiName} key={apiName}>
<Typography variant="h4" className={classes.heading}>
{property === undefined ? (
uiName
) : (
<LabeledCheckbox
className={classes.headingUIName}
checked={checked}
onChange={onChange}
disabled={!isCompatible}
>
{uiName}
</LabeledCheckbox>
)}
<InlineCode className={classes.apiName}>{apiName}</InlineCode>
<CopyIconButton
icon={<IconLink color="primary" />}
toCopy={link}
tooltipText={`Copy link to ${apiName}`}
/>
</Typography>
<Typography>{withLinks}</Typography>
</Root>
<>
{
<Root id={apiName} key={apiName}>
<Typography variant="h4" className={classes.heading}>
{property === undefined ? (
uiName
) : (
<LabeledCheckbox
className={classes.headingUIName}
checked={checked}
onChange={onChange}
disabled={!isCompatible}
>
{uiName}
</LabeledCheckbox>
)}
<InlineCode className={classes.apiName}>{apiName}</InlineCode>
<CopyIconButton
icon={<IconLink color="primary"/>}
toCopy={link}
tooltipText={`Copy link to ${apiName}`}
/>
</Typography>
<Typography><Markdown
remarkPlugins={[remarkGfm]}>{description}</Markdown></Typography>
</Root>
}
</>
);
}

Expand Down
Loading

0 comments on commit 8a6b0e5

Please sign in to comment.