Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { QuantityFormatPanel } from "@itwin/quantity-formatting-react";
import { IModelApp } from "@itwin/core-frontend";
import type { FormatDefinition } from "@itwin/core-quantity";
// __PUBLISH_EXTRACT_END__
// __PUBLISH_EXTRACT_START__ QuantityFormat.TelemetryExampleImports
import { TelemetryContextProvider } from "@itwin/quantity-formatting-react";
// __PUBLISH_EXTRACT_END__
import { QuantityFormattingTestUtils } from "../../utils/QuantityFormattingTestUtils.js";

describe("Quantity formatting", () => {
Expand Down Expand Up @@ -50,5 +53,48 @@ describe("Quantity formatting", () => {
expect(screen.getByText("labels.type")).to.exist;
});
});

describe("Telemetry", () => {
before(async function () {
await QuantityFormattingTestUtils.initialize();
});

after(async function () {
await QuantityFormattingTestUtils.terminate();
});

it("renders with telemetry tracking", async function () {
// __PUBLISH_EXTRACT_START__ QuantityFormat.TelemetryExample
const formatDefinition: FormatDefinition = {
precision: 4,
type: "Decimal",
composite: {
units: [{ name: "Units.M", label: "m" }],
},
};

const handleFormatChange = (_newFormat: FormatDefinition) => {
// Handle format change
};

const handleFeatureUsed = (featureId: string) => {
// Send to your analytics service
console.log(`Feature used: ${featureId}`);
};

render(
<TelemetryContextProvider onFeatureUsed={handleFeatureUsed}>
<QuantityFormatPanel
formatDefinition={formatDefinition}
unitsProvider={IModelApp.quantityFormatter.unitsProvider}
onFormatChange={handleFormatChange}
/>
</TelemetryContextProvider>,
);
// __PUBLISH_EXTRACT_END__

expect(screen.getByText("labels.type")).to.exist;
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
;
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import React from "react";
import React, { useCallback } from "react";
import { Button, Flex, Text } from "@itwin/itwinui-react";
import { FormatSetPanel, FormatSetSelector } from "@itwin/quantity-formatting-react";
import { FormatSetPanel, FormatSetSelector, TelemetryContextProvider } from "@itwin/quantity-formatting-react";

import type { FormatSet } from "@itwin/ecschema-metadata";
import type { UsageTrackedFeatures } from "@itwin/quantity-formatting-react";
import type { FormatManager } from "./FormatManager";

interface FormatSetsTabPanelProps {
Expand Down Expand Up @@ -86,52 +88,58 @@ export const FormatSetsTabPanel: React.FC<FormatSetsTabPanelProps> = ({ formatMa
}
}, [selectedFormatSet]);

const handleFeatureUsed = useCallback((feature: UsageTrackedFeatures) => {
console.log(`[QuantityFormatting] Feature used: ${feature}`);
}, []);

return (
<Flex flexDirection="row" gap="l" className="format-tab-panel">
<Flex.Item className="quantity-format-selector-item">
<Flex flexDirection="column" gap="m" alignItems="none">
<div style={{ height: "36rem" }}>
<FormatSetSelector
formatSets={formatSets}
selectedFormatSetKey={selectedFormatSet?.name}
activeFormatSetKey={activeFormatSetKey}
onFormatSetChange={handleFormatSetChange}
/>
</div>
</Flex>
</Flex.Item>

<Flex.Item className="quantity-format-panel-item">
<Flex flexDirection="column" gap="xs" alignItems="stretch">
{clonedSelectedFormatSet ? (
<FormatSetPanel formatSet={clonedSelectedFormatSet} editable={true} onFormatSetChange={handleSelectedFormatSetChange} />
) : (
<Flex className="quantityFormat--formatSetPanel-emptyState">
<Text variant="leading" isMuted>
Select a format set to view details
</Text>
</Flex>
)}
<Flex.Item alignSelf="flex-end">
<Flex gap="xs">
<Button size="small" styleType="default" onClick={handleClearFormatSet} disabled={!saveEnabled}>
Clear
</Button>
<Button size="small" styleType="high-visibility" onClick={handleSaveFormatSet} disabled={!saveEnabled}>
Save
</Button>
<Button
size="small"
styleType="default"
onClick={handleApplyFormatSet}
disabled={activeFormatSetKey === selectedFormatSet?.name || !selectedFormatSet}
>
Set as Active
</Button>
</Flex>
</Flex.Item>
</Flex>
</Flex.Item>
</Flex>
<TelemetryContextProvider onFeatureUsed={handleFeatureUsed}>
<Flex flexDirection="row" gap="l" className="format-tab-panel">
<Flex.Item className="quantity-format-selector-item">
<Flex flexDirection="column" gap="m" alignItems="none">
<div style={{ height: "36rem" }}>
<FormatSetSelector
formatSets={formatSets}
selectedFormatSetKey={selectedFormatSet?.name}
activeFormatSetKey={activeFormatSetKey}
onFormatSetChange={handleFormatSetChange}
/>
</div>
</Flex>
</Flex.Item>

<Flex.Item className="quantity-format-panel-item">
<Flex flexDirection="column" gap="xs" alignItems="stretch">
{clonedSelectedFormatSet ? (
<FormatSetPanel formatSet={clonedSelectedFormatSet} editable={true} onFormatSetChange={handleSelectedFormatSetChange} />
) : (
<Flex className="quantityFormat--formatSetPanel-emptyState">
<Text variant="leading" isMuted>
Select a format set to view details
</Text>
</Flex>
)}
<Flex.Item alignSelf="flex-end">
<Flex gap="xs">
<Button size="small" styleType="default" onClick={handleClearFormatSet} disabled={!saveEnabled}>
Clear
</Button>
<Button size="small" styleType="high-visibility" onClick={handleSaveFormatSet} disabled={!saveEnabled}>
Save
</Button>
<Button
size="small"
styleType="default"
onClick={handleApplyFormatSet}
disabled={activeFormatSetKey === selectedFormatSet?.name || !selectedFormatSet}
>
Set as Active
</Button>
</Flex>
</Flex.Item>
</Flex>
</Flex.Item>
</Flex>
</TelemetryContextProvider>
);
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
;
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import React from "react";
import React, { useCallback } from "react";
import { Flex, Text } from "@itwin/itwinui-react";
import { FormatSelector, QuantityFormatPanel } from "@itwin/quantity-formatting-react";
import { FormatSelector, QuantityFormatPanel, TelemetryContextProvider } from "@itwin/quantity-formatting-react";

import type { FormatDefinition, UnitsProvider } from "@itwin/core-quantity";
import type { FormatSet } from "@itwin/ecschema-metadata";
import type { UsageTrackedFeatures } from "@itwin/quantity-formatting-react";

interface FormatTabPanelProps {
activeFormatSet: FormatSet | undefined;
Expand All @@ -28,35 +30,41 @@ export const FormatTabPanel: React.FC<FormatTabPanelProps> = ({
onListItemChange,
onFormatChange,
}) => {
const handleFeatureUsed = useCallback((feature: UsageTrackedFeatures) => {
console.log(`[QuantityFormatting] Feature used: ${feature}`);
}, []);

return (
<Flex flexDirection="row" gap="l" className="format-tab-panel">
<Flex.Item className="quantity-format-selector-item">
<FormatSelector activeFormatSet={activeFormatSet} activeFormatDefinitionKey={activeFormatDefinitionKey} onListItemChange={onListItemChange} />
</Flex.Item>
<TelemetryContextProvider onFeatureUsed={handleFeatureUsed}>
<Flex flexDirection="row" gap="l" className="format-tab-panel">
<Flex.Item className="quantity-format-selector-item">
<FormatSelector activeFormatSet={activeFormatSet} activeFormatDefinitionKey={activeFormatDefinitionKey} onListItemChange={onListItemChange} />
</Flex.Item>

<Flex.Item className="quantity-format-panel-item">
{formatDefinition ? (
<>
{(formatDefinition.label || formatDefinition.description) && (
<Flex flexDirection="column" alignItems="flex-start" gap="xs" className="format-definition-header">
{formatDefinition.label && <Text variant="subheading">{formatDefinition.label}</Text>}
{formatDefinition.description && (
<Text variant="body" isMuted>
{formatDefinition.description}
</Text>
)}
</Flex>
)}
<QuantityFormatPanel formatDefinition={formatDefinition} unitsProvider={unitsProvider} onFormatChange={onFormatChange} />
</>
) : (
<Flex flexDirection="column" justifyContent="center" alignItems="center" className="quantity-format-empty-state">
<Text variant="leading" isMuted>
Select a format in the list to edit
</Text>
</Flex>
)}
</Flex.Item>
</Flex>
<Flex.Item className="quantity-format-panel-item">
{formatDefinition ? (
<>
{(formatDefinition.label || formatDefinition.description) && (
<Flex flexDirection="column" alignItems="flex-start" gap="xs" className="format-definition-header">
{formatDefinition.label && <Text variant="subheading">{formatDefinition.label}</Text>}
{formatDefinition.description && (
<Text variant="body" isMuted>
{formatDefinition.description}
</Text>
)}
</Flex>
)}
<QuantityFormatPanel formatDefinition={formatDefinition} unitsProvider={unitsProvider} onFormatChange={onFormatChange} />
</>
) : (
<Flex flexDirection="column" justifyContent="center" alignItems="center" className="quantity-format-empty-state">
<Text variant="leading" isMuted>
Select a format in the list to edit
</Text>
</Flex>
)}
</Flex.Item>
</Flex>
</TelemetryContextProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add TelemetryContextProvider to quantity formatting react",
"packageName": "@itwin/quantity-formatting-react",
"email": "50554904+hl662@users.noreply.github.com",
"dependentChangeType": "patch"
}
68 changes: 68 additions & 0 deletions packages/itwin/quantity-formatting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,74 @@ render(<FormatSetPanel formatSet={formatSet} editable={false} />);

</details>

## Telemetry

The quantity formatting components support telemetry tracking for usage analytics. You can hook into feature usage events by wrapping your components with the `TelemetryContextProvider`.

### Basic Setup

<details>
<summary>Telemetry example code</summary>

<!-- [[include: [QuantityFormat.TelemetryExampleImports, QuantityFormat.QuantityFormatPanelExampleImports, QuantityFormat.TelemetryExample], tsx]] -->
<!-- BEGIN EXTRACTION -->

```tsx
import { TelemetryContextProvider } from "@itwin/quantity-formatting-react";

import { QuantityFormatPanel } from "@itwin/quantity-formatting-react";
import { IModelApp } from "@itwin/core-frontend";
import type { FormatDefinition } from "@itwin/core-quantity";

const formatDefinition: FormatDefinition = {
precision: 4,
type: "Decimal",
composite: {
units: [{ name: "Units.M", label: "m" }],
},
};

const handleFormatChange = (_newFormat: FormatDefinition) => {
// Handle format change
};

const handleFeatureUsed = (featureId: string) => {
// Send to your analytics service
console.log(`Feature used: ${featureId}`);
};

render(
<TelemetryContextProvider onFeatureUsed={handleFeatureUsed}>
<QuantityFormatPanel formatDefinition={formatDefinition} unitsProvider={IModelApp.quantityFormatter.unitsProvider} onFormatChange={handleFormatChange} />
</TelemetryContextProvider>,
);
```

<!-- END EXTRACTION -->

</details>

### Tracked Features

The following features are tracked:

| Feature ID | Description |
| --------------------------- | ---------------------------------- |
| `format-apply` | User applies format changes |
| `format-clear` | User clears/resets format changes |
| `advanced-options-expand` | User expands advanced options |
| `advanced-options-collapse` | User collapses advanced options |
| `format-set-select` | User selects a format set |
| `format-select` | User selects a format |
| `format-set-search` | User initiates a format set search |
| `format-search` | User initiates a format search |
| `unit-system-change` | User changes the unit system |
| `format-type-change` | User changes the format type |
| `unit-change` | User changes the unit |
| `precision-change` | User changes precision |

Additional internal feature tracking includes toggles and changes for decimal separators, thousands separators, sign options, and various format traits.

## Complete Example

A comprehensive example showing how to use FormatSelector together with QuantityFormatPanel can be found in this repository's test-viewer, found in [QuantityFormatButton.tsx](https://github.com/iTwin/viewer-components-react/blob/master/apps/test-viewer/src/components/QuantityFormatButton.tsx). The [common workflow](#common-worfklow) in the section above walks through the component in pictures.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as React from "react";
import { FormatType, parseFormatType } from "@itwin/core-quantity";
import { Divider, ExpandableBlock, Flex, Surface, Text } from "@itwin/itwinui-react";
import { useTranslation } from "../../useTranslation.js";
import { useTelemetryContext } from "../../hooks/UseTelemetryContext.js";
import { AzimuthPrimaryChildren, AzimuthSecondaryChildren } from "./panels/Azimuth.js";
import { BearingPrimaryChildren, BearingSecondaryChildren } from "./panels/Bearing.js";
import { DecimalPrimaryChildren, DecimalSecondaryChildren } from "./panels/Decimal.js";
Expand Down Expand Up @@ -35,6 +36,13 @@ export function FormatPanel(props: FormatPanelProps) {
const { formatProps, unitsProvider, onFormatChange, persistenceUnit } = props;
const [isExpanded, setIsExpanded] = React.useState(false);
const { translate } = useTranslation();
const { onFeatureUsed } = useTelemetryContext();

const handleToggleExpanded = React.useCallback(() => {
const newExpanded = !isExpanded;
onFeatureUsed(newExpanded ? "advanced-options-expand" : "advanced-options-collapse");
setIsExpanded(newExpanded);
}, [isExpanded, onFeatureUsed]);

const [primaryChildren, secondaryChildren] = React.useMemo(() => {
const panelProps: PanelProps = {
Expand Down Expand Up @@ -112,7 +120,7 @@ export function FormatPanel(props: FormatPanelProps) {
size="small"
styleType="borderless"
isExpanded={isExpanded}
onToggle={() => setIsExpanded(!isExpanded)}
onToggle={handleToggleExpanded}
>
<div className="quantityFormat--formatPanel-secondaryChildren">
{secondaryChildren}
Expand Down
Loading
Loading