insight-react-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -0
- package/dist/index.d.mts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +286 -0
- package/dist/index.mjs +276 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Insight React SDK
|
|
2
|
+
|
|
3
|
+
A lightweight, extensible **React frontend SDK** for rendering analytical insights such as **trend** and **contributor** views from raw metric data.
|
|
4
|
+
The SDK abstracts data transformation, time handling, and visualization selection to provide a clean, developer friendly API.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Single public API**: `<Insight />`
|
|
10
|
+
- **Backend-agnostic** via resolver functions
|
|
11
|
+
- SDK-owned **time range calculation**
|
|
12
|
+
- Dedicated **data transformation layer**
|
|
13
|
+
- Automatic **chart selection** based on insight type
|
|
14
|
+
- Clear separation of responsibilities:
|
|
15
|
+
- data fetching
|
|
16
|
+
- transformation
|
|
17
|
+
- visualization
|
|
18
|
+
- Built-in loading, error, and empty states
|
|
19
|
+
- Easy to extend with new insight types
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
npm install insight-react-sdk
|
|
27
|
+
```
|
|
28
|
+
## usage
|
|
29
|
+
|
|
30
|
+
### Step 1: Import the Insight component
|
|
31
|
+
```
|
|
32
|
+
import { Insight } from "insight-react-sdk";
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Step 2: Implement a data resolver
|
|
36
|
+
|
|
37
|
+
The resolver is responsible for returning raw metric data.
|
|
38
|
+
The SDK handles everything else.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
const dataResolver = async (metric, grain, fromTime, toTime) => {
|
|
43
|
+
return [
|
|
44
|
+
{ fromtime: "01-01-2025", totime: "07-01-2025", Revenue: 120 },
|
|
45
|
+
{ fromtime: "08-01-2025", totime: "14-01-2025", Revenue: 180 },
|
|
46
|
+
];
|
|
47
|
+
};
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Step 3: Render a Trend Insight
|
|
51
|
+
```
|
|
52
|
+
export default function TrendExample() {
|
|
53
|
+
return (
|
|
54
|
+
<Insight
|
|
55
|
+
type="trend"
|
|
56
|
+
metric="Revenue"
|
|
57
|
+
timeGrain="weekly"
|
|
58
|
+
timeRange={30}
|
|
59
|
+
dataResolver={dataResolver}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Step 4: Render a Contributor Insight
|
|
66
|
+
|
|
67
|
+
For contributor insights, provide a dimension and a dimension values resolver.
|
|
68
|
+
```
|
|
69
|
+
const contributorDataResolver = async (metric, grain, fromTime, toTime) => {
|
|
70
|
+
return [
|
|
71
|
+
{ fromtime: "01-01-2025", totime: "07-01-2025", India: 60, USA: 40 },
|
|
72
|
+
{ fromtime: "08-01-2025", totime: "14-01-2025", India: 30, USA: 70 },
|
|
73
|
+
];
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
```
|
|
77
|
+
const dimensionValuesResolver = async () => {
|
|
78
|
+
return ["India", "USA"];
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
```
|
|
82
|
+
export default function ContributorExample() {
|
|
83
|
+
return (
|
|
84
|
+
<Insight
|
|
85
|
+
type="contributor"
|
|
86
|
+
metric="Revenue"
|
|
87
|
+
dimension="location"
|
|
88
|
+
timeGrain="weekly"
|
|
89
|
+
timeRange={30}
|
|
90
|
+
dataResolver={contributorDataResolver}
|
|
91
|
+
dimensionValuesResolver={dimensionValuesResolver}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
## Insight Types
|
|
97
|
+
|
|
98
|
+
### Trend Insight
|
|
99
|
+
|
|
100
|
+
- Displays how a single metric changes over time
|
|
101
|
+
|
|
102
|
+
- Uses line/area visualization
|
|
103
|
+
|
|
104
|
+
- X-axis represents time
|
|
105
|
+
|
|
106
|
+
- Y-axis represents the metric value
|
|
107
|
+
|
|
108
|
+
### Contributor Insight
|
|
109
|
+
|
|
110
|
+
- Displays how different dimension values contribute to a metric
|
|
111
|
+
|
|
112
|
+
- Uses stacked bar visualization
|
|
113
|
+
|
|
114
|
+
- Each dimension value is rendered as a separate series
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## Time Handling
|
|
118
|
+
|
|
119
|
+
- timeRange is specified in days
|
|
120
|
+
|
|
121
|
+
- The SDK calculates fromTime and toTime internally
|
|
122
|
+
|
|
123
|
+
- timeGrain controls aggregation semantics
|
|
124
|
+
|
|
125
|
+
- Monthly insights display labels as Jan 2025, Feb 2025, etc.
|
|
126
|
+
|
|
127
|
+
- Daily and weekly labels are passed through as provided
|
|
128
|
+
|
|
129
|
+
## Extensibility
|
|
130
|
+
|
|
131
|
+
- New insight types can be added by:
|
|
132
|
+
|
|
133
|
+
- Creating a new transformer
|
|
134
|
+
|
|
135
|
+
- Creating a corresponding chart
|
|
136
|
+
|
|
137
|
+
- Registering both internally
|
|
138
|
+
|
|
139
|
+
- No changes are required in the public API.
|
|
140
|
+
|
|
141
|
+
## Notes
|
|
142
|
+
|
|
143
|
+
- The SDK does not perform data fetching directly
|
|
144
|
+
|
|
145
|
+
- The consuming application controls data sources
|
|
146
|
+
|
|
147
|
+
- No external date or chart configuration libraries are required
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type InsightType = "trend" | "contributor";
|
|
4
|
+
type TimeGrain = "daily" | "weekly" | "monthly";
|
|
5
|
+
interface BaseInsightProps {
|
|
6
|
+
metric: string;
|
|
7
|
+
timeGrain: TimeGrain;
|
|
8
|
+
timeRange: number;
|
|
9
|
+
}
|
|
10
|
+
type DataResolver = (metric: string, grain: TimeGrain, fromTime: Date, toTime: Date) => Promise<Record<string, any>[]>;
|
|
11
|
+
type DimensionValuesResolver = (metric: string, dimension: string) => Promise<string[]>;
|
|
12
|
+
interface InsightProps extends BaseInsightProps {
|
|
13
|
+
type: InsightType;
|
|
14
|
+
dimension?: string;
|
|
15
|
+
dataResolver: DataResolver;
|
|
16
|
+
dimensionValuesResolver?: DimensionValuesResolver;
|
|
17
|
+
}
|
|
18
|
+
interface TimeRange {
|
|
19
|
+
fromTime: Date;
|
|
20
|
+
toTime: Date;
|
|
21
|
+
}
|
|
22
|
+
interface TrendPoint {
|
|
23
|
+
x: string;
|
|
24
|
+
y: number;
|
|
25
|
+
}
|
|
26
|
+
interface ChartProps {
|
|
27
|
+
title: string;
|
|
28
|
+
}
|
|
29
|
+
interface ContributorSeries {
|
|
30
|
+
id: string;
|
|
31
|
+
label: string;
|
|
32
|
+
color: string;
|
|
33
|
+
}
|
|
34
|
+
interface TrendChartProps extends ChartProps {
|
|
35
|
+
data: TrendPoint[];
|
|
36
|
+
}
|
|
37
|
+
interface ContributorChartProps extends ChartProps {
|
|
38
|
+
data: Record<string, number | string>[];
|
|
39
|
+
series: ContributorSeries[];
|
|
40
|
+
}
|
|
41
|
+
type TransformedInsightData = TrendChartProps | ContributorChartProps;
|
|
42
|
+
|
|
43
|
+
declare function Insight(props: InsightProps): react_jsx_runtime.JSX.Element;
|
|
44
|
+
|
|
45
|
+
export { type BaseInsightProps, type ChartProps, type ContributorChartProps, type ContributorSeries, type DataResolver, type DimensionValuesResolver, Insight, type InsightProps, type InsightType, type TimeGrain, type TimeRange, type TransformedInsightData, type TrendChartProps, type TrendPoint };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type InsightType = "trend" | "contributor";
|
|
4
|
+
type TimeGrain = "daily" | "weekly" | "monthly";
|
|
5
|
+
interface BaseInsightProps {
|
|
6
|
+
metric: string;
|
|
7
|
+
timeGrain: TimeGrain;
|
|
8
|
+
timeRange: number;
|
|
9
|
+
}
|
|
10
|
+
type DataResolver = (metric: string, grain: TimeGrain, fromTime: Date, toTime: Date) => Promise<Record<string, any>[]>;
|
|
11
|
+
type DimensionValuesResolver = (metric: string, dimension: string) => Promise<string[]>;
|
|
12
|
+
interface InsightProps extends BaseInsightProps {
|
|
13
|
+
type: InsightType;
|
|
14
|
+
dimension?: string;
|
|
15
|
+
dataResolver: DataResolver;
|
|
16
|
+
dimensionValuesResolver?: DimensionValuesResolver;
|
|
17
|
+
}
|
|
18
|
+
interface TimeRange {
|
|
19
|
+
fromTime: Date;
|
|
20
|
+
toTime: Date;
|
|
21
|
+
}
|
|
22
|
+
interface TrendPoint {
|
|
23
|
+
x: string;
|
|
24
|
+
y: number;
|
|
25
|
+
}
|
|
26
|
+
interface ChartProps {
|
|
27
|
+
title: string;
|
|
28
|
+
}
|
|
29
|
+
interface ContributorSeries {
|
|
30
|
+
id: string;
|
|
31
|
+
label: string;
|
|
32
|
+
color: string;
|
|
33
|
+
}
|
|
34
|
+
interface TrendChartProps extends ChartProps {
|
|
35
|
+
data: TrendPoint[];
|
|
36
|
+
}
|
|
37
|
+
interface ContributorChartProps extends ChartProps {
|
|
38
|
+
data: Record<string, number | string>[];
|
|
39
|
+
series: ContributorSeries[];
|
|
40
|
+
}
|
|
41
|
+
type TransformedInsightData = TrendChartProps | ContributorChartProps;
|
|
42
|
+
|
|
43
|
+
declare function Insight(props: InsightProps): react_jsx_runtime.JSX.Element;
|
|
44
|
+
|
|
45
|
+
export { type BaseInsightProps, type ChartProps, type ContributorChartProps, type ContributorSeries, type DataResolver, type DimensionValuesResolver, Insight, type InsightProps, type InsightType, type TimeGrain, type TimeRange, type TransformedInsightData, type TrendChartProps, type TrendPoint };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Insight: () => Insight
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/core/errors.ts
|
|
28
|
+
var SDKError = class extends Error {
|
|
29
|
+
constructor(message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "SDKError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var ValidationError = class extends SDKError {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "ValidationError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/utils/validation.ts
|
|
42
|
+
function validateInsightProps(props) {
|
|
43
|
+
if (!props.metric) {
|
|
44
|
+
throw new ValidationError("metric is required");
|
|
45
|
+
}
|
|
46
|
+
if (props.type === "contributor" && !props.dimension) {
|
|
47
|
+
throw new ValidationError(
|
|
48
|
+
"dimension is required when type is 'contributor'"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (!props.dataResolver) {
|
|
52
|
+
throw new ValidationError("dataResolver is required");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/hooks/useTimeRange.ts
|
|
57
|
+
var import_react = require("react");
|
|
58
|
+
|
|
59
|
+
// src/utils/time.ts
|
|
60
|
+
var calculateTimeRange = (days) => {
|
|
61
|
+
const toTime = /* @__PURE__ */ new Date();
|
|
62
|
+
const fromTime = /* @__PURE__ */ new Date();
|
|
63
|
+
fromTime.setDate(toTime.getDate() - days);
|
|
64
|
+
return { fromTime, toTime };
|
|
65
|
+
};
|
|
66
|
+
function formatTimeLabel(rawDate, grain) {
|
|
67
|
+
if (grain !== "monthly") {
|
|
68
|
+
return rawDate;
|
|
69
|
+
}
|
|
70
|
+
const [day, month, year] = rawDate.split("-");
|
|
71
|
+
const isoDate = `${year}-${month}-${day}`;
|
|
72
|
+
const date = new Date(isoDate);
|
|
73
|
+
return date.toLocaleString("en-US", {
|
|
74
|
+
month: "short",
|
|
75
|
+
year: "numeric"
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/hooks/useTimeRange.ts
|
|
80
|
+
function useTimeRange(timeRange) {
|
|
81
|
+
return (0, import_react.useMemo)(() => calculateTimeRange(timeRange), [timeRange]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/hooks/useInsightData.ts
|
|
85
|
+
var import_react2 = require("react");
|
|
86
|
+
|
|
87
|
+
// src/transformers/contributor.transformer.ts
|
|
88
|
+
var DEFAULT_COLORS = ["#9acbdc", "#f6b1ac", "#8da2fb", "#1f3b63"];
|
|
89
|
+
var transformContributorData = (raw, ctx) => {
|
|
90
|
+
const data = raw.map((row) => {
|
|
91
|
+
const point = {
|
|
92
|
+
x: formatTimeLabel(row.fromtime, ctx.timeGrain)
|
|
93
|
+
};
|
|
94
|
+
ctx.dimensionValues.forEach((dim) => {
|
|
95
|
+
var _a;
|
|
96
|
+
point[dim] = (_a = row[dim]) != null ? _a : 0;
|
|
97
|
+
});
|
|
98
|
+
return point;
|
|
99
|
+
});
|
|
100
|
+
const series = ctx.dimensionValues.map((dim, index) => ({
|
|
101
|
+
id: dim,
|
|
102
|
+
label: dim,
|
|
103
|
+
color: DEFAULT_COLORS[index % DEFAULT_COLORS.length]
|
|
104
|
+
}));
|
|
105
|
+
return {
|
|
106
|
+
title: `${ctx.metric} by ${ctx.dimension}`,
|
|
107
|
+
data,
|
|
108
|
+
series
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/transformers/trend.transformer.ts
|
|
113
|
+
var transformTrendData = (raw, ctx) => {
|
|
114
|
+
return {
|
|
115
|
+
title: ctx.metric,
|
|
116
|
+
data: raw.map((bucket) => ({
|
|
117
|
+
x: formatTimeLabel(bucket.fromtime, ctx.timeGrain),
|
|
118
|
+
y: bucket[ctx.metric]
|
|
119
|
+
}))
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/registry/transformerRegistry.ts
|
|
124
|
+
var transformerRegistry = {
|
|
125
|
+
trend: transformTrendData,
|
|
126
|
+
contributor: transformContributorData
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/hooks/useInsightData.ts
|
|
130
|
+
function useInsightData(props, fromTime, toTime) {
|
|
131
|
+
const [data, setData] = (0, import_react2.useState)(null);
|
|
132
|
+
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
133
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
134
|
+
(0, import_react2.useEffect)(() => {
|
|
135
|
+
let mounted = true;
|
|
136
|
+
async function run() {
|
|
137
|
+
try {
|
|
138
|
+
setLoading(true);
|
|
139
|
+
setError(null);
|
|
140
|
+
const rawData = await props.dataResolver(
|
|
141
|
+
props.metric,
|
|
142
|
+
props.timeGrain,
|
|
143
|
+
fromTime,
|
|
144
|
+
toTime
|
|
145
|
+
);
|
|
146
|
+
let dimensionValues = [];
|
|
147
|
+
if (props.type === "contributor" && props.dimensionValuesResolver) {
|
|
148
|
+
dimensionValues = await props.dimensionValuesResolver(
|
|
149
|
+
props.metric,
|
|
150
|
+
props.dimension
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const transformer = transformerRegistry[props.type];
|
|
154
|
+
const transformed = transformer(rawData, {
|
|
155
|
+
metric: props.metric,
|
|
156
|
+
dimension: props.dimension,
|
|
157
|
+
dimensionValues,
|
|
158
|
+
timeGrain: props.timeGrain
|
|
159
|
+
});
|
|
160
|
+
if (mounted) setData(transformed);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
if (mounted) setError(e);
|
|
163
|
+
} finally {
|
|
164
|
+
if (mounted) setLoading(false);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
run();
|
|
168
|
+
return () => {
|
|
169
|
+
mounted = false;
|
|
170
|
+
};
|
|
171
|
+
}, [
|
|
172
|
+
props.type,
|
|
173
|
+
props.metric,
|
|
174
|
+
props.dimension,
|
|
175
|
+
props.timeGrain,
|
|
176
|
+
props.dataResolver,
|
|
177
|
+
props.dimensionValuesResolver,
|
|
178
|
+
fromTime,
|
|
179
|
+
toTime
|
|
180
|
+
]);
|
|
181
|
+
return { data, loading, error };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/states/loading.tsx
|
|
185
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
186
|
+
var Loading = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: "Loading insight\u2026" });
|
|
187
|
+
|
|
188
|
+
// src/states/error.tsx
|
|
189
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
190
|
+
var ErrorState = ({ error }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
191
|
+
"Error: ",
|
|
192
|
+
error.message
|
|
193
|
+
] });
|
|
194
|
+
|
|
195
|
+
// src/states/empty.tsx
|
|
196
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
197
|
+
var Empty = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: "No data available" });
|
|
198
|
+
|
|
199
|
+
// src/charts/contributorChart.tsx
|
|
200
|
+
var import_recharts = require("recharts");
|
|
201
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
202
|
+
function ContributorChart({
|
|
203
|
+
title,
|
|
204
|
+
data,
|
|
205
|
+
series
|
|
206
|
+
}) {
|
|
207
|
+
if (!data.length || !series.length) return null;
|
|
208
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { style: { marginBottom: 8 }, children: title }),
|
|
210
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_recharts.ResponsiveContainer, { width: "100%", minHeight: 300, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_recharts.BarChart, { data, children: [
|
|
211
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_recharts.CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
|
|
212
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_recharts.XAxis, { dataKey: "x" }),
|
|
213
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_recharts.YAxis, {}),
|
|
214
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_recharts.Tooltip, {}),
|
|
215
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_recharts.Legend, {}),
|
|
216
|
+
series.map((s) => {
|
|
217
|
+
var _a;
|
|
218
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
219
|
+
import_recharts.Bar,
|
|
220
|
+
{
|
|
221
|
+
dataKey: s.id,
|
|
222
|
+
stackId: "total",
|
|
223
|
+
fill: s.color,
|
|
224
|
+
name: (_a = s.label) != null ? _a : s.id
|
|
225
|
+
},
|
|
226
|
+
s.id
|
|
227
|
+
);
|
|
228
|
+
})
|
|
229
|
+
] }) })
|
|
230
|
+
] });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/charts/trendChart.tsx
|
|
234
|
+
var import_recharts2 = require("recharts");
|
|
235
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
236
|
+
function TrendChart({ title, data }) {
|
|
237
|
+
if (!data.length) return null;
|
|
238
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
|
|
239
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { style: { marginBottom: 8 }, children: title }),
|
|
240
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_recharts2.ResponsiveContainer, { width: "100%", minHeight: 300, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_recharts2.AreaChart, { data, children: [
|
|
241
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("linearGradient", { id: "trendGradient", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
242
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("stop", { offset: "0%", stopColor: "#1e3a8a", stopOpacity: 0.25 }),
|
|
243
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("stop", { offset: "100%", stopColor: "#1e3a8a", stopOpacity: 0 })
|
|
244
|
+
] }) }),
|
|
245
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_recharts2.CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
|
|
246
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_recharts2.XAxis, { dataKey: "x" }),
|
|
247
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_recharts2.YAxis, {}),
|
|
248
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_recharts2.Tooltip, {}),
|
|
249
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
250
|
+
import_recharts2.Area,
|
|
251
|
+
{
|
|
252
|
+
type: "monotone",
|
|
253
|
+
dataKey: "y",
|
|
254
|
+
stroke: "#1e3a8a",
|
|
255
|
+
strokeWidth: 2,
|
|
256
|
+
fill: "url(#trendGradient)",
|
|
257
|
+
dot: false
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
] }) })
|
|
261
|
+
] });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/registry/insightRegistry.ts
|
|
265
|
+
var insightRegistry = {
|
|
266
|
+
trend: TrendChart,
|
|
267
|
+
contributor: ContributorChart
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// src/Insight.tsx
|
|
271
|
+
var import_react3 = require("react");
|
|
272
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
273
|
+
function Insight(props) {
|
|
274
|
+
(0, import_react3.useMemo)(() => validateInsightProps(props), [props]);
|
|
275
|
+
const { fromTime, toTime } = useTimeRange(props.timeRange);
|
|
276
|
+
const { data, loading, error } = useInsightData(props, fromTime, toTime);
|
|
277
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Loading, {});
|
|
278
|
+
if (error) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorState, { error });
|
|
279
|
+
if (!data) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Empty, {});
|
|
280
|
+
const Chart = insightRegistry[props.type];
|
|
281
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Chart, { ...data });
|
|
282
|
+
}
|
|
283
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
284
|
+
0 && (module.exports = {
|
|
285
|
+
Insight
|
|
286
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// src/core/errors.ts
|
|
2
|
+
var SDKError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "SDKError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var ValidationError = class extends SDKError {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "ValidationError";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/utils/validation.ts
|
|
16
|
+
function validateInsightProps(props) {
|
|
17
|
+
if (!props.metric) {
|
|
18
|
+
throw new ValidationError("metric is required");
|
|
19
|
+
}
|
|
20
|
+
if (props.type === "contributor" && !props.dimension) {
|
|
21
|
+
throw new ValidationError(
|
|
22
|
+
"dimension is required when type is 'contributor'"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (!props.dataResolver) {
|
|
26
|
+
throw new ValidationError("dataResolver is required");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/hooks/useTimeRange.ts
|
|
31
|
+
import { useMemo } from "react";
|
|
32
|
+
|
|
33
|
+
// src/utils/time.ts
|
|
34
|
+
var calculateTimeRange = (days) => {
|
|
35
|
+
const toTime = /* @__PURE__ */ new Date();
|
|
36
|
+
const fromTime = /* @__PURE__ */ new Date();
|
|
37
|
+
fromTime.setDate(toTime.getDate() - days);
|
|
38
|
+
return { fromTime, toTime };
|
|
39
|
+
};
|
|
40
|
+
function formatTimeLabel(rawDate, grain) {
|
|
41
|
+
if (grain !== "monthly") {
|
|
42
|
+
return rawDate;
|
|
43
|
+
}
|
|
44
|
+
const [day, month, year] = rawDate.split("-");
|
|
45
|
+
const isoDate = `${year}-${month}-${day}`;
|
|
46
|
+
const date = new Date(isoDate);
|
|
47
|
+
return date.toLocaleString("en-US", {
|
|
48
|
+
month: "short",
|
|
49
|
+
year: "numeric"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/hooks/useTimeRange.ts
|
|
54
|
+
function useTimeRange(timeRange) {
|
|
55
|
+
return useMemo(() => calculateTimeRange(timeRange), [timeRange]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/hooks/useInsightData.ts
|
|
59
|
+
import { useEffect, useState } from "react";
|
|
60
|
+
|
|
61
|
+
// src/transformers/contributor.transformer.ts
|
|
62
|
+
var DEFAULT_COLORS = ["#9acbdc", "#f6b1ac", "#8da2fb", "#1f3b63"];
|
|
63
|
+
var transformContributorData = (raw, ctx) => {
|
|
64
|
+
const data = raw.map((row) => {
|
|
65
|
+
const point = {
|
|
66
|
+
x: formatTimeLabel(row.fromtime, ctx.timeGrain)
|
|
67
|
+
};
|
|
68
|
+
ctx.dimensionValues.forEach((dim) => {
|
|
69
|
+
var _a;
|
|
70
|
+
point[dim] = (_a = row[dim]) != null ? _a : 0;
|
|
71
|
+
});
|
|
72
|
+
return point;
|
|
73
|
+
});
|
|
74
|
+
const series = ctx.dimensionValues.map((dim, index) => ({
|
|
75
|
+
id: dim,
|
|
76
|
+
label: dim,
|
|
77
|
+
color: DEFAULT_COLORS[index % DEFAULT_COLORS.length]
|
|
78
|
+
}));
|
|
79
|
+
return {
|
|
80
|
+
title: `${ctx.metric} by ${ctx.dimension}`,
|
|
81
|
+
data,
|
|
82
|
+
series
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// src/transformers/trend.transformer.ts
|
|
87
|
+
var transformTrendData = (raw, ctx) => {
|
|
88
|
+
return {
|
|
89
|
+
title: ctx.metric,
|
|
90
|
+
data: raw.map((bucket) => ({
|
|
91
|
+
x: formatTimeLabel(bucket.fromtime, ctx.timeGrain),
|
|
92
|
+
y: bucket[ctx.metric]
|
|
93
|
+
}))
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/registry/transformerRegistry.ts
|
|
98
|
+
var transformerRegistry = {
|
|
99
|
+
trend: transformTrendData,
|
|
100
|
+
contributor: transformContributorData
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/hooks/useInsightData.ts
|
|
104
|
+
function useInsightData(props, fromTime, toTime) {
|
|
105
|
+
const [data, setData] = useState(null);
|
|
106
|
+
const [loading, setLoading] = useState(true);
|
|
107
|
+
const [error, setError] = useState(null);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
let mounted = true;
|
|
110
|
+
async function run() {
|
|
111
|
+
try {
|
|
112
|
+
setLoading(true);
|
|
113
|
+
setError(null);
|
|
114
|
+
const rawData = await props.dataResolver(
|
|
115
|
+
props.metric,
|
|
116
|
+
props.timeGrain,
|
|
117
|
+
fromTime,
|
|
118
|
+
toTime
|
|
119
|
+
);
|
|
120
|
+
let dimensionValues = [];
|
|
121
|
+
if (props.type === "contributor" && props.dimensionValuesResolver) {
|
|
122
|
+
dimensionValues = await props.dimensionValuesResolver(
|
|
123
|
+
props.metric,
|
|
124
|
+
props.dimension
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const transformer = transformerRegistry[props.type];
|
|
128
|
+
const transformed = transformer(rawData, {
|
|
129
|
+
metric: props.metric,
|
|
130
|
+
dimension: props.dimension,
|
|
131
|
+
dimensionValues,
|
|
132
|
+
timeGrain: props.timeGrain
|
|
133
|
+
});
|
|
134
|
+
if (mounted) setData(transformed);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
if (mounted) setError(e);
|
|
137
|
+
} finally {
|
|
138
|
+
if (mounted) setLoading(false);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
run();
|
|
142
|
+
return () => {
|
|
143
|
+
mounted = false;
|
|
144
|
+
};
|
|
145
|
+
}, [
|
|
146
|
+
props.type,
|
|
147
|
+
props.metric,
|
|
148
|
+
props.dimension,
|
|
149
|
+
props.timeGrain,
|
|
150
|
+
props.dataResolver,
|
|
151
|
+
props.dimensionValuesResolver,
|
|
152
|
+
fromTime,
|
|
153
|
+
toTime
|
|
154
|
+
]);
|
|
155
|
+
return { data, loading, error };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/states/loading.tsx
|
|
159
|
+
import { jsx } from "react/jsx-runtime";
|
|
160
|
+
var Loading = () => /* @__PURE__ */ jsx("div", { children: "Loading insight\u2026" });
|
|
161
|
+
|
|
162
|
+
// src/states/error.tsx
|
|
163
|
+
import { jsxs } from "react/jsx-runtime";
|
|
164
|
+
var ErrorState = ({ error }) => /* @__PURE__ */ jsxs("div", { children: [
|
|
165
|
+
"Error: ",
|
|
166
|
+
error.message
|
|
167
|
+
] });
|
|
168
|
+
|
|
169
|
+
// src/states/empty.tsx
|
|
170
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
171
|
+
var Empty = () => /* @__PURE__ */ jsx2("div", { children: "No data available" });
|
|
172
|
+
|
|
173
|
+
// src/charts/contributorChart.tsx
|
|
174
|
+
import {
|
|
175
|
+
BarChart,
|
|
176
|
+
Bar,
|
|
177
|
+
XAxis,
|
|
178
|
+
YAxis,
|
|
179
|
+
Tooltip,
|
|
180
|
+
ResponsiveContainer,
|
|
181
|
+
CartesianGrid,
|
|
182
|
+
Legend
|
|
183
|
+
} from "recharts";
|
|
184
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
185
|
+
function ContributorChart({
|
|
186
|
+
title,
|
|
187
|
+
data,
|
|
188
|
+
series
|
|
189
|
+
}) {
|
|
190
|
+
if (!data.length || !series.length) return null;
|
|
191
|
+
return /* @__PURE__ */ jsxs2("div", { children: [
|
|
192
|
+
/* @__PURE__ */ jsx3("h3", { style: { marginBottom: 8 }, children: title }),
|
|
193
|
+
/* @__PURE__ */ jsx3(ResponsiveContainer, { width: "100%", minHeight: 300, children: /* @__PURE__ */ jsxs2(BarChart, { data, children: [
|
|
194
|
+
/* @__PURE__ */ jsx3(CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
|
|
195
|
+
/* @__PURE__ */ jsx3(XAxis, { dataKey: "x" }),
|
|
196
|
+
/* @__PURE__ */ jsx3(YAxis, {}),
|
|
197
|
+
/* @__PURE__ */ jsx3(Tooltip, {}),
|
|
198
|
+
/* @__PURE__ */ jsx3(Legend, {}),
|
|
199
|
+
series.map((s) => {
|
|
200
|
+
var _a;
|
|
201
|
+
return /* @__PURE__ */ jsx3(
|
|
202
|
+
Bar,
|
|
203
|
+
{
|
|
204
|
+
dataKey: s.id,
|
|
205
|
+
stackId: "total",
|
|
206
|
+
fill: s.color,
|
|
207
|
+
name: (_a = s.label) != null ? _a : s.id
|
|
208
|
+
},
|
|
209
|
+
s.id
|
|
210
|
+
);
|
|
211
|
+
})
|
|
212
|
+
] }) })
|
|
213
|
+
] });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/charts/trendChart.tsx
|
|
217
|
+
import {
|
|
218
|
+
AreaChart,
|
|
219
|
+
Area,
|
|
220
|
+
XAxis as XAxis2,
|
|
221
|
+
YAxis as YAxis2,
|
|
222
|
+
Tooltip as Tooltip2,
|
|
223
|
+
ResponsiveContainer as ResponsiveContainer2,
|
|
224
|
+
CartesianGrid as CartesianGrid2
|
|
225
|
+
} from "recharts";
|
|
226
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
227
|
+
function TrendChart({ title, data }) {
|
|
228
|
+
if (!data.length) return null;
|
|
229
|
+
return /* @__PURE__ */ jsxs3("div", { children: [
|
|
230
|
+
/* @__PURE__ */ jsx4("h3", { style: { marginBottom: 8 }, children: title }),
|
|
231
|
+
/* @__PURE__ */ jsx4(ResponsiveContainer2, { width: "100%", minHeight: 300, children: /* @__PURE__ */ jsxs3(AreaChart, { data, children: [
|
|
232
|
+
/* @__PURE__ */ jsx4("defs", { children: /* @__PURE__ */ jsxs3("linearGradient", { id: "trendGradient", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
233
|
+
/* @__PURE__ */ jsx4("stop", { offset: "0%", stopColor: "#1e3a8a", stopOpacity: 0.25 }),
|
|
234
|
+
/* @__PURE__ */ jsx4("stop", { offset: "100%", stopColor: "#1e3a8a", stopOpacity: 0 })
|
|
235
|
+
] }) }),
|
|
236
|
+
/* @__PURE__ */ jsx4(CartesianGrid2, { strokeDasharray: "3 3", vertical: false }),
|
|
237
|
+
/* @__PURE__ */ jsx4(XAxis2, { dataKey: "x" }),
|
|
238
|
+
/* @__PURE__ */ jsx4(YAxis2, {}),
|
|
239
|
+
/* @__PURE__ */ jsx4(Tooltip2, {}),
|
|
240
|
+
/* @__PURE__ */ jsx4(
|
|
241
|
+
Area,
|
|
242
|
+
{
|
|
243
|
+
type: "monotone",
|
|
244
|
+
dataKey: "y",
|
|
245
|
+
stroke: "#1e3a8a",
|
|
246
|
+
strokeWidth: 2,
|
|
247
|
+
fill: "url(#trendGradient)",
|
|
248
|
+
dot: false
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
] }) })
|
|
252
|
+
] });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/registry/insightRegistry.ts
|
|
256
|
+
var insightRegistry = {
|
|
257
|
+
trend: TrendChart,
|
|
258
|
+
contributor: ContributorChart
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/Insight.tsx
|
|
262
|
+
import { useMemo as useMemo2 } from "react";
|
|
263
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
264
|
+
function Insight(props) {
|
|
265
|
+
useMemo2(() => validateInsightProps(props), [props]);
|
|
266
|
+
const { fromTime, toTime } = useTimeRange(props.timeRange);
|
|
267
|
+
const { data, loading, error } = useInsightData(props, fromTime, toTime);
|
|
268
|
+
if (loading) return /* @__PURE__ */ jsx5(Loading, {});
|
|
269
|
+
if (error) return /* @__PURE__ */ jsx5(ErrorState, { error });
|
|
270
|
+
if (!data) return /* @__PURE__ */ jsx5(Empty, {});
|
|
271
|
+
const Chart = insightRegistry[props.type];
|
|
272
|
+
return /* @__PURE__ */ jsx5(Chart, { ...data });
|
|
273
|
+
}
|
|
274
|
+
export {
|
|
275
|
+
Insight
|
|
276
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "insight-react-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React SDK for rendering insights",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"react",
|
|
23
|
+
"sdk",
|
|
24
|
+
"charts",
|
|
25
|
+
"analytics",
|
|
26
|
+
"insights",
|
|
27
|
+
"frontend"
|
|
28
|
+
],
|
|
29
|
+
"author": "Anvesh Ramelli",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": ">=18",
|
|
33
|
+
"react-dom": ">=18",
|
|
34
|
+
"recharts": ">=2.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/react": "^19.2.7",
|
|
38
|
+
"@types/react-dom": "^19.2.3",
|
|
39
|
+
"react": "^19.2.3",
|
|
40
|
+
"react-dom": "^19.2.3",
|
|
41
|
+
"recharts": "^3.6.0",
|
|
42
|
+
"tsup": "^8.5.1",
|
|
43
|
+
"typescript": "^5.9.3"
|
|
44
|
+
}
|
|
45
|
+
}
|