@flagsmith/backstage-plugin 0.1.0-pr.7.a55a280 → 0.1.0-pr.7.de3318e

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.
Files changed (48) hide show
  1. package/dist/api/FlagsmithClient.esm.js +28 -2
  2. package/dist/api/FlagsmithClient.esm.js.map +1 -1
  3. package/dist/components/FlagsTab/EnvironmentTable.esm.js +83 -45
  4. package/dist/components/FlagsTab/EnvironmentTable.esm.js.map +1 -1
  5. package/dist/components/FlagsTab/ExpandableRow.esm.js +188 -101
  6. package/dist/components/FlagsTab/ExpandableRow.esm.js.map +1 -1
  7. package/dist/components/FlagsTab/FeatureAnalyticsSection.esm.js +178 -0
  8. package/dist/components/FlagsTab/FeatureAnalyticsSection.esm.js.map +1 -0
  9. package/dist/components/FlagsTab/FeatureDetailsGrid.esm.js +111 -35
  10. package/dist/components/FlagsTab/FeatureDetailsGrid.esm.js.map +1 -1
  11. package/dist/components/FlagsTab/SegmentOverridesSection.esm.js +1 -0
  12. package/dist/components/FlagsTab/SegmentOverridesSection.esm.js.map +1 -1
  13. package/dist/components/FlagsTab/index.esm.js +48 -16
  14. package/dist/components/FlagsTab/index.esm.js.map +1 -1
  15. package/dist/components/FlagsmithOverviewCard/FeatureFlagRow.esm.js +1 -0
  16. package/dist/components/FlagsmithOverviewCard/FeatureFlagRow.esm.js.map +1 -1
  17. package/dist/components/FlagsmithOverviewCard/FlagStatsRow.esm.js +1 -0
  18. package/dist/components/FlagsmithOverviewCard/FlagStatsRow.esm.js.map +1 -1
  19. package/dist/components/FlagsmithOverviewCard/index.esm.js +4 -5
  20. package/dist/components/FlagsmithOverviewCard/index.esm.js.map +1 -1
  21. package/dist/components/FlagsmithUsageCard/UsageChart.esm.js +40 -3
  22. package/dist/components/FlagsmithUsageCard/UsageChart.esm.js.map +1 -1
  23. package/dist/components/FlagsmithUsageCard/index.esm.js +11 -13
  24. package/dist/components/FlagsmithUsageCard/index.esm.js.map +1 -1
  25. package/dist/components/shared/ChartTooltip.esm.js +33 -0
  26. package/dist/components/shared/ChartTooltip.esm.js.map +1 -0
  27. package/dist/components/shared/ErrorState.esm.js +13 -0
  28. package/dist/components/shared/ErrorState.esm.js.map +1 -0
  29. package/dist/components/shared/FlagsmithLink.esm.js +12 -4
  30. package/dist/components/shared/FlagsmithLink.esm.js.map +1 -1
  31. package/dist/constants/index.esm.js +38 -0
  32. package/dist/constants/index.esm.js.map +1 -0
  33. package/dist/hooks/useFlagsmithProject.esm.js +7 -1
  34. package/dist/hooks/useFlagsmithProject.esm.js.map +1 -1
  35. package/dist/hooks/useFlagsmithUsage.esm.js +6 -3
  36. package/dist/hooks/useFlagsmithUsage.esm.js.map +1 -1
  37. package/dist/index.d.ts +9 -2
  38. package/dist/index.esm.js +3 -0
  39. package/dist/index.esm.js.map +1 -1
  40. package/dist/theme/sharedStyles.esm.js +19 -0
  41. package/dist/theme/sharedStyles.esm.js.map +1 -0
  42. package/dist/utils/dateFormatters.esm.js +15 -0
  43. package/dist/utils/dateFormatters.esm.js.map +1 -0
  44. package/dist/utils/flagTypeHelpers.esm.js +42 -0
  45. package/dist/utils/flagTypeHelpers.esm.js.map +1 -0
  46. package/package.json +1 -1
  47. package/dist/components/FlagsmithUsageCard/UsageTooltip.esm.js +0 -52
  48. package/dist/components/FlagsmithUsageCard/UsageTooltip.esm.js.map +0 -1
@@ -1,9 +1,46 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { Box, Typography } from '@material-ui/core';
3
3
  import { ResponsiveContainer, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, Bar } from 'recharts';
4
4
  import { flagsmithColors } from '../../theme/flagsmithTheme.esm.js';
5
- import { UsageTooltip } from './UsageTooltip.esm.js';
5
+ import '../shared/FlagStatusIndicator.esm.js';
6
+ import '../shared/SearchInput.esm.js';
7
+ import '../shared/FlagsmithLink.esm.js';
8
+ import '@material-ui/icons/ChevronLeft';
9
+ import '@material-ui/icons/ChevronRight';
10
+ import { ChartTooltip, ChartTooltipText } from '../shared/ChartTooltip.esm.js';
6
11
 
12
+ const UsageChartTooltip = ({ active, payload }) => /* @__PURE__ */ jsx(ChartTooltip, { active, payload, children: (data) => {
13
+ const usageData = data[0].payload;
14
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
15
+ /* @__PURE__ */ jsx(ChartTooltipText, { variant: "subtitle2", fontWeight: 600, children: new Date(usageData.day).toLocaleDateString("en-US", {
16
+ month: "short",
17
+ day: "numeric",
18
+ year: "numeric"
19
+ }) }),
20
+ /* @__PURE__ */ jsxs(Box, { mt: 1, children: [
21
+ /* @__PURE__ */ jsxs(ChartTooltipText, { children: [
22
+ /* @__PURE__ */ jsx("strong", { children: "Flags:" }),
23
+ " ",
24
+ usageData.flags ?? 0
25
+ ] }),
26
+ /* @__PURE__ */ jsxs(ChartTooltipText, { children: [
27
+ /* @__PURE__ */ jsx("strong", { children: "Identities:" }),
28
+ " ",
29
+ usageData.identities
30
+ ] }),
31
+ /* @__PURE__ */ jsxs(ChartTooltipText, { children: [
32
+ /* @__PURE__ */ jsx("strong", { children: "Traits:" }),
33
+ " ",
34
+ usageData.traits
35
+ ] }),
36
+ /* @__PURE__ */ jsxs(ChartTooltipText, { children: [
37
+ /* @__PURE__ */ jsx("strong", { children: "Environment Document:" }),
38
+ " ",
39
+ usageData.environment_document
40
+ ] })
41
+ ] })
42
+ ] });
43
+ } });
7
44
  const UsageChart = ({ data }) => {
8
45
  if (data.length === 0) {
9
46
  return /* @__PURE__ */ jsx(Box, { display: "flex", justifyContent: "center", alignItems: "center", height: 300, children: /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: "No usage data available" }) });
@@ -29,7 +66,7 @@ const UsageChart = ({ data }) => {
29
66
  }
30
67
  ),
31
68
  /* @__PURE__ */ jsx(YAxis, {}),
32
- /* @__PURE__ */ jsx(Tooltip, { content: /* @__PURE__ */ jsx(UsageTooltip, {}) }),
69
+ /* @__PURE__ */ jsx(Tooltip, { content: /* @__PURE__ */ jsx(UsageChartTooltip, {}) }),
33
70
  /* @__PURE__ */ jsx(
34
71
  Bar,
35
72
  {
@@ -1 +1 @@
1
- {"version":3,"file":"UsageChart.esm.js","sources":["../../../src/components/FlagsmithUsageCard/UsageChart.tsx"],"sourcesContent":["import { Box, Typography } from '@material-ui/core';\nimport {\n BarChart,\n Bar,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ResponsiveContainer,\n} from 'recharts';\nimport { FlagsmithUsageData } from '../../api/FlagsmithClient';\nimport { flagsmithColors } from '../../theme/flagsmithTheme';\nimport { UsageTooltip } from './UsageTooltip';\n\ninterface UsageChartProps {\n data: FlagsmithUsageData[];\n}\n\nexport const UsageChart = ({ data }: UsageChartProps) => {\n if (data.length === 0) {\n return (\n <Box display=\"flex\" justifyContent=\"center\" alignItems=\"center\" height={300}>\n <Typography color=\"textSecondary\">\n No usage data available\n </Typography>\n </Box>\n );\n }\n\n return (\n <ResponsiveContainer width=\"100%\" height={300}>\n <BarChart\n data={data}\n margin={{ top: 5, right: 30, left: 20, bottom: 5 }}\n >\n <CartesianGrid strokeDasharray=\"3 3\" />\n <XAxis\n dataKey=\"day\"\n tickFormatter={value => {\n const date = new Date(value);\n return `${date.getMonth() + 1}/${date.getDate()}`;\n }}\n angle={-45}\n textAnchor=\"end\"\n height={80}\n />\n <YAxis />\n <Tooltip content={<UsageTooltip />} />\n <Bar\n dataKey=\"flags\"\n fill={flagsmithColors.primary}\n radius={[2, 2, 0, 0]}\n />\n </BarChart>\n </ResponsiveContainer>\n );\n};\n"],"names":[],"mappings":";;;;;;AAkBO,MAAM,UAAA,GAAa,CAAC,EAAE,IAAA,EAAK,KAAuB;AACvD,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,IAAA,uBACE,GAAA,CAAC,GAAA,EAAA,EAAI,OAAA,EAAQ,MAAA,EAAO,gBAAe,QAAA,EAAS,UAAA,EAAW,QAAA,EAAS,MAAA,EAAQ,KACtE,QAAA,kBAAA,GAAA,CAAC,UAAA,EAAA,EAAW,KAAA,EAAM,eAAA,EAAgB,qCAElC,CAAA,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,uBACE,GAAA,CAAC,mBAAA,EAAA,EAAoB,KAAA,EAAM,MAAA,EAAO,QAAQ,GAAA,EACxC,QAAA,kBAAA,IAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,IAAA;AAAA,MACA,MAAA,EAAQ,EAAE,GAAA,EAAK,CAAA,EAAG,OAAO,EAAA,EAAI,IAAA,EAAM,EAAA,EAAI,MAAA,EAAQ,CAAA,EAAE;AAAA,MAEjD,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,aAAA,EAAA,EAAc,iBAAgB,KAAA,EAAM,CAAA;AAAA,wBACrC,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,OAAA,EAAQ,KAAA;AAAA,YACR,eAAe,CAAA,KAAA,KAAS;AACtB,cAAA,MAAM,IAAA,GAAO,IAAI,IAAA,CAAK,KAAK,CAAA;AAC3B,cAAA,OAAO,CAAA,EAAG,KAAK,QAAA,EAAS,GAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,SAAS,CAAA,CAAA;AAAA,YACjD,CAAA;AAAA,YACA,KAAA,EAAO,GAAA;AAAA,YACP,UAAA,EAAW,KAAA;AAAA,YACX,MAAA,EAAQ;AAAA;AAAA,SACV;AAAA,4BACC,KAAA,EAAA,EAAM,CAAA;AAAA,wBACP,GAAA,CAAC,OAAA,EAAA,EAAQ,OAAA,kBAAS,GAAA,CAAC,gBAAa,CAAA,EAAI,CAAA;AAAA,wBACpC,GAAA;AAAA,UAAC,GAAA;AAAA,UAAA;AAAA,YACC,OAAA,EAAQ,OAAA;AAAA,YACR,MAAM,eAAA,CAAgB,OAAA;AAAA,YACtB,MAAA,EAAQ,CAAC,CAAA,EAAG,CAAA,EAAG,GAAG,CAAC;AAAA;AAAA;AACrB;AAAA;AAAA,GACF,EACF,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"UsageChart.esm.js","sources":["../../../src/components/FlagsmithUsageCard/UsageChart.tsx"],"sourcesContent":["import { Box, Typography } from '@material-ui/core';\nimport {\n BarChart,\n Bar,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ResponsiveContainer,\n} from 'recharts';\nimport { FlagsmithUsageData } from '../../api/FlagsmithClient';\nimport { flagsmithColors } from '../../theme/flagsmithTheme';\nimport { ChartTooltip, ChartTooltipText } from '../shared';\n\ninterface UsageChartProps {\n data: FlagsmithUsageData[];\n}\n\n/**\n * Custom tooltip for usage chart displaying flags, identities, traits, and environment document\n */\nconst UsageChartTooltip = ({ active, payload }: any) => (\n <ChartTooltip active={active} payload={payload}>\n {(data) => {\n const usageData = data[0].payload as FlagsmithUsageData;\n return (\n <>\n <ChartTooltipText variant=\"subtitle2\" fontWeight={600}>\n {new Date(usageData.day).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n })}\n </ChartTooltipText>\n <Box mt={1}>\n <ChartTooltipText>\n <strong>Flags:</strong> {usageData.flags ?? 0}\n </ChartTooltipText>\n <ChartTooltipText>\n <strong>Identities:</strong> {usageData.identities}\n </ChartTooltipText>\n <ChartTooltipText>\n <strong>Traits:</strong> {usageData.traits}\n </ChartTooltipText>\n <ChartTooltipText>\n <strong>Environment Document:</strong> {usageData.environment_document}\n </ChartTooltipText>\n </Box>\n </>\n );\n }}\n </ChartTooltip>\n);\n\nexport const UsageChart = ({ data }: UsageChartProps) => {\n if (data.length === 0) {\n return (\n <Box display=\"flex\" justifyContent=\"center\" alignItems=\"center\" height={300}>\n <Typography color=\"textSecondary\">\n No usage data available\n </Typography>\n </Box>\n );\n }\n\n return (\n <ResponsiveContainer width=\"100%\" height={300}>\n <BarChart\n data={data}\n margin={{ top: 5, right: 30, left: 20, bottom: 5 }}\n >\n <CartesianGrid strokeDasharray=\"3 3\" />\n <XAxis\n dataKey=\"day\"\n tickFormatter={value => {\n const date = new Date(value);\n return `${date.getMonth() + 1}/${date.getDate()}`;\n }}\n angle={-45}\n textAnchor=\"end\"\n height={80}\n />\n <YAxis />\n <Tooltip content={<UsageChartTooltip />} />\n <Bar\n dataKey=\"flags\"\n fill={flagsmithColors.primary}\n radius={[2, 2, 0, 0]}\n />\n </BarChart>\n </ResponsiveContainer>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;AAqBA,MAAM,iBAAA,GAAoB,CAAC,EAAE,MAAA,EAAQ,OAAA,EAAQ,qBAC3C,GAAA,CAAC,YAAA,EAAA,EAAa,MAAA,EAAgB,OAAA,EAC3B,QAAA,EAAA,CAAC,IAAA,KAAS;AACT,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,CAAC,CAAA,CAAE,OAAA;AAC1B,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,gBAAA,EAAA,EAAiB,OAAA,EAAQ,WAAA,EAAY,UAAA,EAAY,GAAA,EAC/C,QAAA,EAAA,IAAI,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA,CAAE,kBAAA,CAAmB,OAAA,EAAS;AAAA,MACnD,KAAA,EAAO,OAAA;AAAA,MACP,GAAA,EAAK,SAAA;AAAA,MACL,IAAA,EAAM;AAAA,KACP,CAAA,EACH,CAAA;AAAA,oBACA,IAAA,CAAC,GAAA,EAAA,EAAI,EAAA,EAAI,CAAA,EACP,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,gBAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,YAAO,QAAA,EAAA,QAAA,EAAM,CAAA;AAAA,QAAS,GAAA;AAAA,QAAE,UAAU,KAAA,IAAS;AAAA,OAAA,EAC9C,CAAA;AAAA,2BACC,gBAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,YAAO,QAAA,EAAA,aAAA,EAAW,CAAA;AAAA,QAAS,GAAA;AAAA,QAAE,SAAA,CAAU;AAAA,OAAA,EAC1C,CAAA;AAAA,2BACC,gBAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,YAAO,QAAA,EAAA,SAAA,EAAO,CAAA;AAAA,QAAS,GAAA;AAAA,QAAE,SAAA,CAAU;AAAA,OAAA,EACtC,CAAA;AAAA,2BACC,gBAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,YAAO,QAAA,EAAA,uBAAA,EAAqB,CAAA;AAAA,QAAS,GAAA;AAAA,QAAE,SAAA,CAAU;AAAA,OAAA,EACpD;AAAA,KAAA,EACF;AAAA,GAAA,EACF,CAAA;AAEJ,CAAA,EACF,CAAA;AAGK,MAAM,UAAA,GAAa,CAAC,EAAE,IAAA,EAAK,KAAuB;AACvD,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,IAAA,uBACE,GAAA,CAAC,GAAA,EAAA,EAAI,OAAA,EAAQ,MAAA,EAAO,gBAAe,QAAA,EAAS,UAAA,EAAW,QAAA,EAAS,MAAA,EAAQ,KACtE,QAAA,kBAAA,GAAA,CAAC,UAAA,EAAA,EAAW,KAAA,EAAM,eAAA,EAAgB,qCAElC,CAAA,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,uBACE,GAAA,CAAC,mBAAA,EAAA,EAAoB,KAAA,EAAM,MAAA,EAAO,QAAQ,GAAA,EACxC,QAAA,kBAAA,IAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,IAAA;AAAA,MACA,MAAA,EAAQ,EAAE,GAAA,EAAK,CAAA,EAAG,OAAO,EAAA,EAAI,IAAA,EAAM,EAAA,EAAI,MAAA,EAAQ,CAAA,EAAE;AAAA,MAEjD,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,aAAA,EAAA,EAAc,iBAAgB,KAAA,EAAM,CAAA;AAAA,wBACrC,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,OAAA,EAAQ,KAAA;AAAA,YACR,eAAe,CAAA,KAAA,KAAS;AACtB,cAAA,MAAM,IAAA,GAAO,IAAI,IAAA,CAAK,KAAK,CAAA;AAC3B,cAAA,OAAO,CAAA,EAAG,KAAK,QAAA,EAAS,GAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,SAAS,CAAA,CAAA;AAAA,YACjD,CAAA;AAAA,YACA,KAAA,EAAO,GAAA;AAAA,YACP,UAAA,EAAW,KAAA;AAAA,YACX,MAAA,EAAQ;AAAA;AAAA,SACV;AAAA,4BACC,KAAA,EAAA,EAAM,CAAA;AAAA,wBACP,GAAA,CAAC,OAAA,EAAA,EAAQ,OAAA,kBAAS,GAAA,CAAC,qBAAkB,CAAA,EAAI,CAAA;AAAA,wBACzC,GAAA;AAAA,UAAC,GAAA;AAAA,UAAA;AAAA,YACC,OAAA,EAAQ,OAAA;AAAA,YACR,MAAM,eAAA,CAAgB,OAAA;AAAA,YACtB,MAAA,EAAQ,CAAC,CAAA,EAAG,CAAA,EAAG,GAAG,CAAC;AAAA;AAAA;AACrB;AAAA;AAAA,GACF,EACF,CAAA;AAEJ;;;;"}
@@ -1,5 +1,5 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { Box, Typography } from '@material-ui/core';
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { Box } from '@material-ui/core';
3
3
  import { makeStyles } from '@material-ui/core/styles';
4
4
  import { InfoCard } from '@backstage/core-components';
5
5
  import { useEntity } from '@backstage/plugin-catalog-react';
@@ -9,6 +9,8 @@ import { FlagsmithLink } from '../shared/FlagsmithLink.esm.js';
9
9
  import '@material-ui/icons/ChevronLeft';
10
10
  import '@material-ui/icons/ChevronRight';
11
11
  import { LoadingState } from '../shared/LoadingState.esm.js';
12
+ import { ErrorState } from '../shared/ErrorState.esm.js';
13
+ import '../shared/ChartTooltip.esm.js';
12
14
  import { FLAGSMITH_DASHBOARD_URL } from '../../theme/flagsmithTheme.esm.js';
13
15
  import 'react';
14
16
  import '@backstage/core-plugin-api';
@@ -36,17 +38,13 @@ const FlagsmithUsageCard = () => {
36
38
  return /* @__PURE__ */ jsx(InfoCard, { title: "Flags Usage Data (30 Days)", children: /* @__PURE__ */ jsx(LoadingState, { message: "Loading usage data...", size: 24 }) });
37
39
  }
38
40
  if (error) {
39
- return /* @__PURE__ */ jsx(InfoCard, { title: "Flags Usage Data (30 Days)", children: /* @__PURE__ */ jsxs(Box, { p: 2, children: [
40
- /* @__PURE__ */ jsxs(Typography, { color: "error", children: [
41
- "Error: ",
42
- error
43
- ] }),
44
- !orgId && /* @__PURE__ */ jsxs(Typography, { variant: "body2", style: { marginTop: 8 }, children: [
45
- "Add a ",
46
- /* @__PURE__ */ jsx("code", { children: "flagsmith.com/organization-id" }),
47
- " annotation to this entity."
48
- ] })
49
- ] }) });
41
+ return /* @__PURE__ */ jsx(InfoCard, { title: "Flags Usage Data (30 Days)", children: /* @__PURE__ */ jsx(
42
+ ErrorState,
43
+ {
44
+ message: error,
45
+ hint: !orgId ? "Add a flagsmith.com/organization-id annotation to this entity." : void 0
46
+ }
47
+ ) });
50
48
  }
51
49
  const subheader = project?.name ? `${project.name} - ${totalFlags.toLocaleString()} total flag calls` : void 0;
52
50
  return /* @__PURE__ */ jsx(
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../../../src/components/FlagsmithUsageCard/index.tsx"],"sourcesContent":["import { Typography, Box } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport { InfoCard } from '@backstage/core-components';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { FlagsmithLink, LoadingState } from '../shared';\nimport { FLAGSMITH_DASHBOARD_URL } from '../../theme/flagsmithTheme';\nimport { useFlagsmithUsage } from '../../hooks';\nimport { UsageChart } from './UsageChart';\n\nconst useStyles = makeStyles(theme => ({\n headerActions: {\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n },\n}));\n\nexport const FlagsmithUsageCard = () => {\n const classes = useStyles();\n const { entity } = useEntity();\n\n const projectId = entity.metadata.annotations?.['flagsmith.com/project-id'];\n const orgId = entity.metadata.annotations?.['flagsmith.com/org-id'];\n\n const { project, usageData, totalFlags, loading, error } = useFlagsmithUsage(\n projectId,\n orgId,\n );\n\n const usageUrl = `${FLAGSMITH_DASHBOARD_URL}/organisation/${orgId}/usage`;\n\n if (loading) {\n return (\n <InfoCard title=\"Flags Usage Data (30 Days)\">\n <LoadingState message=\"Loading usage data...\" size={24} />\n </InfoCard>\n );\n }\n\n if (error) {\n return (\n <InfoCard title=\"Flags Usage Data (30 Days)\">\n <Box p={2}>\n <Typography color=\"error\">Error: {error}</Typography>\n {!orgId && (\n <Typography variant=\"body2\" style={{ marginTop: 8 }}>\n Add a <code>flagsmith.com/organization-id</code> annotation to this\n entity.\n </Typography>\n )}\n </Box>\n </InfoCard>\n );\n }\n\n const subheader = project?.name\n ? `${project.name} - ${totalFlags.toLocaleString()} total flag calls`\n : undefined;\n\n return (\n <InfoCard\n title=\"Flags Usage Data (30 Days)\"\n subheader={subheader}\n action={\n orgId && (\n <Box className={classes.headerActions}>\n <FlagsmithLink href={usageUrl} iconOnly tooltip=\"View Usage Analytics\" />\n </Box>\n )\n }\n >\n <Box p={2}>\n <UsageChart data={usageData} />\n </Box>\n </InfoCard>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AASA,MAAM,SAAA,GAAY,WAAW,CAAA,KAAA,MAAU;AAAA,EACrC,aAAA,EAAe;AAAA,IACb,OAAA,EAAS,MAAA;AAAA,IACT,UAAA,EAAY,QAAA;AAAA,IACZ,GAAA,EAAK,KAAA,CAAM,OAAA,CAAQ,CAAC;AAAA;AAExB,CAAA,CAAE,CAAA;AAEK,MAAM,qBAAqB,MAAM;AACtC,EAAA,MAAM,UAAU,SAAA,EAAU;AAC1B,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,SAAA,EAAU;AAE7B,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,0BAA0B,CAAA;AAC1E,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,sBAAsB,CAAA;AAElE,EAAA,MAAM,EAAE,OAAA,EAAS,SAAA,EAAW,UAAA,EAAY,OAAA,EAAS,OAAM,GAAI,iBAAA;AAAA,IACzD,SAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,MAAM,QAAA,GAAW,CAAA,EAAG,uBAAuB,CAAA,cAAA,EAAiB,KAAK,CAAA,MAAA,CAAA;AAEjE,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,uBACE,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAM,4BAAA,EACd,QAAA,kBAAA,GAAA,CAAC,gBAAa,OAAA,EAAQ,uBAAA,EAAwB,IAAA,EAAM,EAAA,EAAI,CAAA,EAC1D,CAAA;AAAA,EAEJ;AAEA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,2BACG,QAAA,EAAA,EAAS,KAAA,EAAM,8BACd,QAAA,kBAAA,IAAA,CAAC,GAAA,EAAA,EAAI,GAAG,CAAA,EACN,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,UAAA,EAAA,EAAW,OAAM,OAAA,EAAQ,QAAA,EAAA;AAAA,QAAA,SAAA;AAAA,QAAQ;AAAA,OAAA,EAAM,CAAA;AAAA,MACvC,CAAC,KAAA,oBACA,IAAA,CAAC,UAAA,EAAA,EAAW,OAAA,EAAQ,SAAQ,KAAA,EAAO,EAAE,SAAA,EAAW,CAAA,EAAE,EAAG,QAAA,EAAA;AAAA,QAAA,QAAA;AAAA,wBAC7C,GAAA,CAAC,UAAK,QAAA,EAAA,+BAAA,EAA6B,CAAA;AAAA,QAAO;AAAA,OAAA,EAElD;AAAA,KAAA,EAEJ,CAAA,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,MAAM,SAAA,GAAY,OAAA,EAAS,IAAA,GACvB,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA,GAAA,EAAM,UAAA,CAAW,cAAA,EAAgB,CAAA,iBAAA,CAAA,GAChD,MAAA;AAEJ,EAAA,uBACE,GAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAM,4BAAA;AAAA,MACN,SAAA;AAAA,MACA,MAAA,EACE,KAAA,oBACE,GAAA,CAAC,GAAA,EAAA,EAAI,WAAW,OAAA,CAAQ,aAAA,EACtB,QAAA,kBAAA,GAAA,CAAC,aAAA,EAAA,EAAc,MAAM,QAAA,EAAU,QAAA,EAAQ,IAAA,EAAC,OAAA,EAAQ,wBAAuB,CAAA,EACzE,CAAA;AAAA,MAIJ,QAAA,kBAAA,GAAA,CAAC,OAAI,CAAA,EAAG,CAAA,EACN,8BAAC,UAAA,EAAA,EAAW,IAAA,EAAM,WAAW,CAAA,EAC/B;AAAA;AAAA,GACF;AAEJ;;;;"}
1
+ {"version":3,"file":"index.esm.js","sources":["../../../src/components/FlagsmithUsageCard/index.tsx"],"sourcesContent":["import { Box } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport { InfoCard } from '@backstage/core-components';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { FlagsmithLink, LoadingState, ErrorState } from '../shared';\nimport { FLAGSMITH_DASHBOARD_URL } from '../../theme/flagsmithTheme';\nimport { useFlagsmithUsage } from '../../hooks';\nimport { UsageChart } from './UsageChart';\n\nconst useStyles = makeStyles(theme => ({\n headerActions: {\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n },\n}));\n\nexport const FlagsmithUsageCard = () => {\n const classes = useStyles();\n const { entity } = useEntity();\n\n const projectId = entity.metadata.annotations?.['flagsmith.com/project-id'];\n const orgId = entity.metadata.annotations?.['flagsmith.com/org-id'];\n\n const { project, usageData, totalFlags, loading, error } = useFlagsmithUsage(\n projectId,\n orgId,\n );\n\n const usageUrl = `${FLAGSMITH_DASHBOARD_URL}/organisation/${orgId}/usage`;\n\n if (loading) {\n return (\n <InfoCard title=\"Flags Usage Data (30 Days)\">\n <LoadingState message=\"Loading usage data...\" size={24} />\n </InfoCard>\n );\n }\n\n if (error) {\n return (\n <InfoCard title=\"Flags Usage Data (30 Days)\">\n <ErrorState\n message={error}\n hint={!orgId ? 'Add a flagsmith.com/organization-id annotation to this entity.' : undefined}\n />\n </InfoCard>\n );\n }\n\n const subheader = project?.name\n ? `${project.name} - ${totalFlags.toLocaleString()} total flag calls`\n : undefined;\n\n return (\n <InfoCard\n title=\"Flags Usage Data (30 Days)\"\n subheader={subheader}\n action={\n orgId && (\n <Box className={classes.headerActions}>\n <FlagsmithLink href={usageUrl} iconOnly tooltip=\"View Usage Analytics\" />\n </Box>\n )\n }\n >\n <Box p={2}>\n <UsageChart data={usageData} />\n </Box>\n </InfoCard>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AASA,MAAM,SAAA,GAAY,WAAW,CAAA,KAAA,MAAU;AAAA,EACrC,aAAA,EAAe;AAAA,IACb,OAAA,EAAS,MAAA;AAAA,IACT,UAAA,EAAY,QAAA;AAAA,IACZ,GAAA,EAAK,KAAA,CAAM,OAAA,CAAQ,CAAC;AAAA;AAExB,CAAA,CAAE,CAAA;AAEK,MAAM,qBAAqB,MAAM;AACtC,EAAA,MAAM,UAAU,SAAA,EAAU;AAC1B,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,SAAA,EAAU;AAE7B,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,0BAA0B,CAAA;AAC1E,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,sBAAsB,CAAA;AAElE,EAAA,MAAM,EAAE,OAAA,EAAS,SAAA,EAAW,UAAA,EAAY,OAAA,EAAS,OAAM,GAAI,iBAAA;AAAA,IACzD,SAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,MAAM,QAAA,GAAW,CAAA,EAAG,uBAAuB,CAAA,cAAA,EAAiB,KAAK,CAAA,MAAA,CAAA;AAEjE,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,uBACE,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAM,4BAAA,EACd,QAAA,kBAAA,GAAA,CAAC,gBAAa,OAAA,EAAQ,uBAAA,EAAwB,IAAA,EAAM,EAAA,EAAI,CAAA,EAC1D,CAAA;AAAA,EAEJ;AAEA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,uBACE,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAM,4BAAA,EACd,QAAA,kBAAA,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,OAAA,EAAS,KAAA;AAAA,QACT,IAAA,EAAM,CAAC,KAAA,GAAQ,gEAAA,GAAmE;AAAA;AAAA,KACpF,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,MAAM,SAAA,GAAY,OAAA,EAAS,IAAA,GACvB,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA,GAAA,EAAM,UAAA,CAAW,cAAA,EAAgB,CAAA,iBAAA,CAAA,GAChD,MAAA;AAEJ,EAAA,uBACE,GAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAM,4BAAA;AAAA,MACN,SAAA;AAAA,MACA,MAAA,EACE,KAAA,oBACE,GAAA,CAAC,GAAA,EAAA,EAAI,WAAW,OAAA,CAAQ,aAAA,EACtB,QAAA,kBAAA,GAAA,CAAC,aAAA,EAAA,EAAc,MAAM,QAAA,EAAU,QAAA,EAAQ,IAAA,EAAC,OAAA,EAAQ,wBAAuB,CAAA,EACzE,CAAA;AAAA,MAIJ,QAAA,kBAAA,GAAA,CAAC,OAAI,CAAA,EAAG,CAAA,EACN,8BAAC,UAAA,EAAA,EAAW,IAAA,EAAM,WAAW,CAAA,EAC/B;AAAA;AAAA,GACF;AAEJ;;;;"}
@@ -0,0 +1,33 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { Box, Typography } from '@material-ui/core';
3
+ import { makeStyles } from '@material-ui/core/styles';
4
+
5
+ const useStyles = makeStyles((theme) => ({
6
+ tooltipBox: {
7
+ backgroundColor: theme.palette.grey[800],
8
+ border: "none",
9
+ borderRadius: theme.shape.borderRadius,
10
+ boxShadow: "0 2px 8px rgba(0,0,0,0.15)"
11
+ },
12
+ tooltipText: {
13
+ color: theme.palette.common.white
14
+ }
15
+ }));
16
+ const ChartTooltip = ({ active, payload, label, children }) => {
17
+ const classes = useStyles();
18
+ if (!active || !payload?.length) {
19
+ return null;
20
+ }
21
+ return /* @__PURE__ */ jsx(Box, { p: 1.5, className: classes.tooltipBox, children: children(payload, label) });
22
+ };
23
+ const ChartTooltipText = ({
24
+ variant = "body2",
25
+ fontWeight,
26
+ children
27
+ }) => {
28
+ const classes = useStyles();
29
+ return /* @__PURE__ */ jsx(Typography, { variant, className: classes.tooltipText, style: { fontWeight }, children });
30
+ };
31
+
32
+ export { ChartTooltip, ChartTooltipText };
33
+ //# sourceMappingURL=ChartTooltip.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChartTooltip.esm.js","sources":["../../../src/components/shared/ChartTooltip.tsx"],"sourcesContent":["import { Box, Typography } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport { ReactNode } from 'react';\n\nconst useStyles = makeStyles((theme) => ({\n tooltipBox: {\n backgroundColor: theme.palette.grey[800],\n border: 'none',\n borderRadius: theme.shape.borderRadius,\n boxShadow: '0 2px 8px rgba(0,0,0,0.15)',\n },\n tooltipText: {\n color: theme.palette.common.white,\n },\n}));\n\ninterface ChartTooltipProps {\n active?: boolean;\n payload?: any[];\n label?: string;\n children: (payload: any[], label?: string) => ReactNode;\n}\n\n/**\n * Generic Recharts custom tooltip component\n * Uses theme colors for consistent theming across light and dark modes\n * Accepts a render function to customize content per use case\n *\n * @example\n * <Tooltip content={\n * <ChartTooltip>\n * {(payload, label) => (\n * <>\n * <ChartTooltipText variant=\"subtitle2\">{label}</ChartTooltipText>\n * <ChartTooltipText>Value: {payload[0].value}</ChartTooltipText>\n * </>\n * )}\n * </ChartTooltip>\n * } />\n */\nexport const ChartTooltip = ({ active, payload, label, children }: ChartTooltipProps) => {\n const classes = useStyles();\n\n if (!active || !payload?.length) {\n return null;\n }\n\n return (\n <Box p={1.5} className={classes.tooltipBox}>\n {children(payload, label)}\n </Box>\n );\n};\n\ninterface ChartTooltipTextProps {\n variant?: 'subtitle2' | 'body2';\n fontWeight?: number;\n children: ReactNode;\n}\n\n/**\n * Text component for chart tooltips with theme-aware white color\n */\nexport const ChartTooltipText = ({\n variant = 'body2',\n fontWeight,\n children,\n}: ChartTooltipTextProps) => {\n const classes = useStyles();\n\n return (\n <Typography variant={variant} className={classes.tooltipText} style={{ fontWeight }}>\n {children}\n </Typography>\n );\n};\n"],"names":[],"mappings":";;;;AAIA,MAAM,SAAA,GAAY,UAAA,CAAW,CAAC,KAAA,MAAW;AAAA,EACvC,UAAA,EAAY;AAAA,IACV,eAAA,EAAiB,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,GAAG,CAAA;AAAA,IACvC,MAAA,EAAQ,MAAA;AAAA,IACR,YAAA,EAAc,MAAM,KAAA,CAAM,YAAA;AAAA,IAC1B,SAAA,EAAW;AAAA,GACb;AAAA,EACA,WAAA,EAAa;AAAA,IACX,KAAA,EAAO,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO;AAAA;AAEhC,CAAA,CAAE,CAAA;AA0BK,MAAM,eAAe,CAAC,EAAE,QAAQ,OAAA,EAAS,KAAA,EAAO,UAAS,KAAyB;AACvF,EAAA,MAAM,UAAU,SAAA,EAAU;AAE1B,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,OAAA,EAAS,MAAA,EAAQ;AAC/B,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,uBACE,GAAA,CAAC,GAAA,EAAA,EAAI,CAAA,EAAG,GAAA,EAAK,SAAA,EAAW,QAAQ,UAAA,EAC7B,QAAA,EAAA,QAAA,CAAS,OAAA,EAAS,KAAK,CAAA,EAC1B,CAAA;AAEJ;AAWO,MAAM,mBAAmB,CAAC;AAAA,EAC/B,OAAA,GAAU,OAAA;AAAA,EACV,UAAA;AAAA,EACA;AACF,CAAA,KAA6B;AAC3B,EAAA,MAAM,UAAU,SAAA,EAAU;AAE1B,EAAA,uBACE,GAAA,CAAC,UAAA,EAAA,EAAW,OAAA,EAAkB,SAAA,EAAW,OAAA,CAAQ,aAAa,KAAA,EAAO,EAAE,UAAA,EAAW,EAC/E,QAAA,EACH,CAAA;AAEJ;;;;"}
@@ -0,0 +1,13 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, Typography } from '@material-ui/core';
3
+
4
+ const ErrorState = ({ message, hint }) => /* @__PURE__ */ jsxs(Box, { p: 2, children: [
5
+ /* @__PURE__ */ jsxs(Typography, { color: "error", children: [
6
+ "Error: ",
7
+ message
8
+ ] }),
9
+ hint && /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "textSecondary", style: { marginTop: 8 }, children: hint })
10
+ ] });
11
+
12
+ export { ErrorState };
13
+ //# sourceMappingURL=ErrorState.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorState.esm.js","sources":["../../../src/components/shared/ErrorState.tsx"],"sourcesContent":["import { Box, Typography } from '@material-ui/core';\n\ninterface ErrorStateProps {\n message: string;\n hint?: string;\n}\n\nexport const ErrorState = ({ message, hint }: ErrorStateProps) => (\n <Box p={2}>\n <Typography color=\"error\">Error: {message}</Typography>\n {hint && (\n <Typography variant=\"body2\" color=\"textSecondary\" style={{ marginTop: 8 }}>\n {hint}\n </Typography>\n )}\n </Box>\n);\n"],"names":[],"mappings":";;;AAOO,MAAM,UAAA,GAAa,CAAC,EAAE,OAAA,EAAS,MAAK,qBACzC,IAAA,CAAC,GAAA,EAAA,EAAI,CAAA,EAAG,CAAA,EACN,QAAA,EAAA;AAAA,kBAAA,IAAA,CAAC,UAAA,EAAA,EAAW,OAAM,OAAA,EAAQ,QAAA,EAAA;AAAA,IAAA,SAAA;AAAA,IAAQ;AAAA,GAAA,EAAQ,CAAA;AAAA,EACzC,IAAA,oBACC,GAAA,CAAC,UAAA,EAAA,EAAW,OAAA,EAAQ,OAAA,EAAQ,KAAA,EAAM,eAAA,EAAgB,KAAA,EAAO,EAAE,SAAA,EAAW,CAAA,EAAE,EACrE,QAAA,EAAA,IAAA,EACH;AAAA,CAAA,EAEJ;;;;"}
@@ -4,7 +4,7 @@ import { makeStyles } from '@material-ui/core/styles';
4
4
  import LaunchIcon from '@material-ui/icons/Launch';
5
5
  import { flagsmithColors } from '../../theme/flagsmithTheme.esm.js';
6
6
 
7
- const useStyles = makeStyles(() => ({
7
+ const useStyles = makeStyles((theme) => ({
8
8
  link: {
9
9
  display: "inline-flex",
10
10
  alignItems: "center",
@@ -23,17 +23,23 @@ const useStyles = makeStyles(() => ({
23
23
  iconButton: {
24
24
  padding: 4,
25
25
  color: flagsmithColors.primary
26
+ },
27
+ tooltip: {
28
+ backgroundColor: theme.palette.grey[700],
29
+ color: theme.palette.common.white,
30
+ fontSize: "0.75rem"
26
31
  }
27
32
  }));
28
33
  const FlagsmithLink = ({
29
34
  href,
30
35
  children,
31
36
  tooltip = "Open in Flagsmith",
32
- iconOnly = false
37
+ iconOnly = false,
38
+ onClick
33
39
  }) => {
34
40
  const classes = useStyles();
35
41
  if (iconOnly) {
36
- return /* @__PURE__ */ jsx(Tooltip, { title: tooltip, children: /* @__PURE__ */ jsx(
42
+ return /* @__PURE__ */ jsx(Tooltip, { title: tooltip, classes: { tooltip: classes.tooltip }, children: /* @__PURE__ */ jsx(
37
43
  IconButton,
38
44
  {
39
45
  className: classes.iconButton,
@@ -42,11 +48,12 @@ const FlagsmithLink = ({
42
48
  rel: "noopener noreferrer",
43
49
  size: "small",
44
50
  "aria-label": tooltip,
51
+ onClick,
45
52
  children: /* @__PURE__ */ jsx(LaunchIcon, { fontSize: "small", "aria-hidden": "true" })
46
53
  }
47
54
  ) });
48
55
  }
49
- return /* @__PURE__ */ jsx(Tooltip, { title: tooltip, children: /* @__PURE__ */ jsxs(
56
+ return /* @__PURE__ */ jsx(Tooltip, { title: tooltip, classes: { tooltip: classes.tooltip }, children: /* @__PURE__ */ jsxs(
50
57
  Link,
51
58
  {
52
59
  className: classes.link,
@@ -54,6 +61,7 @@ const FlagsmithLink = ({
54
61
  target: "_blank",
55
62
  rel: "noopener noreferrer",
56
63
  "aria-label": `${tooltip} (opens in new tab)`,
64
+ onClick,
57
65
  children: [
58
66
  children,
59
67
  /* @__PURE__ */ jsx(LaunchIcon, { className: classes.icon, "aria-hidden": "true" })
@@ -1 +1 @@
1
- {"version":3,"file":"FlagsmithLink.esm.js","sources":["../../../src/components/shared/FlagsmithLink.tsx"],"sourcesContent":["import { Link, Tooltip, IconButton } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport LaunchIcon from '@material-ui/icons/Launch';\nimport { flagsmithColors } from '../../theme/flagsmithTheme';\n\nconst useStyles = makeStyles(() => ({\n link: {\n display: 'inline-flex',\n alignItems: 'center',\n gap: 4,\n color: 'inherit',\n textDecoration: 'none',\n '&:hover': {\n color: flagsmithColors.primary,\n textDecoration: 'underline',\n },\n },\n icon: {\n fontSize: '0.875rem',\n opacity: 0.7,\n },\n iconButton: {\n padding: 4,\n color: flagsmithColors.primary,\n },\n}));\n\ninterface FlagsmithLinkProps {\n href: string;\n children?: React.ReactNode;\n tooltip?: string;\n iconOnly?: boolean;\n}\n\n/**\n * External link to Flagsmith dashboard\n * Opens in a new tab with appropriate security attributes\n */\nexport const FlagsmithLink = ({\n href,\n children,\n tooltip = 'Open in Flagsmith',\n iconOnly = false,\n}: FlagsmithLinkProps) => {\n const classes = useStyles();\n\n if (iconOnly) {\n return (\n <Tooltip title={tooltip}>\n <IconButton\n className={classes.iconButton}\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n size=\"small\"\n aria-label={tooltip}\n >\n <LaunchIcon fontSize=\"small\" aria-hidden=\"true\" />\n </IconButton>\n </Tooltip>\n );\n }\n\n return (\n <Tooltip title={tooltip}>\n <Link\n className={classes.link}\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`${tooltip} (opens in new tab)`}\n >\n {children}\n <LaunchIcon className={classes.icon} aria-hidden=\"true\" />\n </Link>\n </Tooltip>\n );\n};\n"],"names":[],"mappings":";;;;;;AAKA,MAAM,SAAA,GAAY,WAAW,OAAO;AAAA,EAClC,IAAA,EAAM;AAAA,IACJ,OAAA,EAAS,aAAA;AAAA,IACT,UAAA,EAAY,QAAA;AAAA,IACZ,GAAA,EAAK,CAAA;AAAA,IACL,KAAA,EAAO,SAAA;AAAA,IACP,cAAA,EAAgB,MAAA;AAAA,IAChB,SAAA,EAAW;AAAA,MACT,OAAO,eAAA,CAAgB,OAAA;AAAA,MACvB,cAAA,EAAgB;AAAA;AAClB,GACF;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,QAAA,EAAU,UAAA;AAAA,IACV,OAAA,EAAS;AAAA,GACX;AAAA,EACA,UAAA,EAAY;AAAA,IACV,OAAA,EAAS,CAAA;AAAA,IACT,OAAO,eAAA,CAAgB;AAAA;AAE3B,CAAA,CAAE,CAAA;AAaK,MAAM,gBAAgB,CAAC;AAAA,EAC5B,IAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA,GAAU,mBAAA;AAAA,EACV,QAAA,GAAW;AACb,CAAA,KAA0B;AACxB,EAAA,MAAM,UAAU,SAAA,EAAU;AAE1B,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,uBACE,GAAA,CAAC,OAAA,EAAA,EAAQ,KAAA,EAAO,OAAA,EACd,QAAA,kBAAA,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,WAAW,OAAA,CAAQ,UAAA;AAAA,QACnB,IAAA;AAAA,QACA,MAAA,EAAO,QAAA;AAAA,QACP,GAAA,EAAI,qBAAA;AAAA,QACJ,IAAA,EAAK,OAAA;AAAA,QACL,YAAA,EAAY,OAAA;AAAA,QAEZ,QAAA,kBAAA,GAAA,CAAC,UAAA,EAAA,EAAW,QAAA,EAAS,OAAA,EAAQ,eAAY,MAAA,EAAO;AAAA;AAAA,KAClD,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,uBACE,GAAA,CAAC,OAAA,EAAA,EAAQ,KAAA,EAAO,OAAA,EACd,QAAA,kBAAA,IAAA;AAAA,IAAC,IAAA;AAAA,IAAA;AAAA,MACC,WAAW,OAAA,CAAQ,IAAA;AAAA,MACnB,IAAA;AAAA,MACA,MAAA,EAAO,QAAA;AAAA,MACP,GAAA,EAAI,qBAAA;AAAA,MACJ,YAAA,EAAY,GAAG,OAAO,CAAA,mBAAA,CAAA;AAAA,MAErB,QAAA,EAAA;AAAA,QAAA,QAAA;AAAA,4BACA,UAAA,EAAA,EAAW,SAAA,EAAW,OAAA,CAAQ,IAAA,EAAM,eAAY,MAAA,EAAO;AAAA;AAAA;AAAA,GAC1D,EACF,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"FlagsmithLink.esm.js","sources":["../../../src/components/shared/FlagsmithLink.tsx"],"sourcesContent":["import { Link, Tooltip, IconButton } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport LaunchIcon from '@material-ui/icons/Launch';\nimport { flagsmithColors } from '../../theme/flagsmithTheme';\n\nconst useStyles = makeStyles(theme => ({\n link: {\n display: 'inline-flex',\n alignItems: 'center',\n gap: 4,\n color: 'inherit',\n textDecoration: 'none',\n '&:hover': {\n color: flagsmithColors.primary,\n textDecoration: 'underline',\n },\n },\n icon: {\n fontSize: '0.875rem',\n opacity: 0.7,\n },\n iconButton: {\n padding: 4,\n color: flagsmithColors.primary,\n },\n tooltip: {\n backgroundColor: theme.palette.grey[700],\n color: theme.palette.common.white,\n fontSize: '0.75rem',\n },\n}));\n\ninterface FlagsmithLinkProps {\n href: string;\n children?: React.ReactNode;\n tooltip?: string;\n iconOnly?: boolean;\n onClick?: (event: React.MouseEvent) => void;\n}\n\n/**\n * External link to Flagsmith dashboard\n * Opens in a new tab with appropriate security attributes\n */\nexport const FlagsmithLink = ({\n href,\n children,\n tooltip = 'Open in Flagsmith',\n iconOnly = false,\n onClick,\n}: FlagsmithLinkProps) => {\n const classes = useStyles();\n\n if (iconOnly) {\n return (\n <Tooltip title={tooltip} classes={{ tooltip: classes.tooltip }}>\n <IconButton\n className={classes.iconButton}\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n size=\"small\"\n aria-label={tooltip}\n onClick={onClick}\n >\n <LaunchIcon fontSize=\"small\" aria-hidden=\"true\" />\n </IconButton>\n </Tooltip>\n );\n }\n\n return (\n <Tooltip title={tooltip} classes={{ tooltip: classes.tooltip }}>\n <Link\n className={classes.link}\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`${tooltip} (opens in new tab)`}\n onClick={onClick}\n >\n {children}\n <LaunchIcon className={classes.icon} aria-hidden=\"true\" />\n </Link>\n </Tooltip>\n );\n};\n"],"names":[],"mappings":";;;;;;AAKA,MAAM,SAAA,GAAY,WAAW,CAAA,KAAA,MAAU;AAAA,EACrC,IAAA,EAAM;AAAA,IACJ,OAAA,EAAS,aAAA;AAAA,IACT,UAAA,EAAY,QAAA;AAAA,IACZ,GAAA,EAAK,CAAA;AAAA,IACL,KAAA,EAAO,SAAA;AAAA,IACP,cAAA,EAAgB,MAAA;AAAA,IAChB,SAAA,EAAW;AAAA,MACT,OAAO,eAAA,CAAgB,OAAA;AAAA,MACvB,cAAA,EAAgB;AAAA;AAClB,GACF;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,QAAA,EAAU,UAAA;AAAA,IACV,OAAA,EAAS;AAAA,GACX;AAAA,EACA,UAAA,EAAY;AAAA,IACV,OAAA,EAAS,CAAA;AAAA,IACT,OAAO,eAAA,CAAgB;AAAA,GACzB;AAAA,EACA,OAAA,EAAS;AAAA,IACP,eAAA,EAAiB,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,GAAG,CAAA;AAAA,IACvC,KAAA,EAAO,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,KAAA;AAAA,IAC5B,QAAA,EAAU;AAAA;AAEd,CAAA,CAAE,CAAA;AAcK,MAAM,gBAAgB,CAAC;AAAA,EAC5B,IAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA,GAAU,mBAAA;AAAA,EACV,QAAA,GAAW,KAAA;AAAA,EACX;AACF,CAAA,KAA0B;AACxB,EAAA,MAAM,UAAU,SAAA,EAAU;AAE1B,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,uBACE,GAAA,CAAC,WAAQ,KAAA,EAAO,OAAA,EAAS,SAAS,EAAE,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ,EAC3D,QAAA,kBAAA,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,WAAW,OAAA,CAAQ,UAAA;AAAA,QACnB,IAAA;AAAA,QACA,MAAA,EAAO,QAAA;AAAA,QACP,GAAA,EAAI,qBAAA;AAAA,QACJ,IAAA,EAAK,OAAA;AAAA,QACL,YAAA,EAAY,OAAA;AAAA,QACZ,OAAA;AAAA,QAEA,QAAA,kBAAA,GAAA,CAAC,UAAA,EAAA,EAAW,QAAA,EAAS,OAAA,EAAQ,eAAY,MAAA,EAAO;AAAA;AAAA,KAClD,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,uBACE,GAAA,CAAC,WAAQ,KAAA,EAAO,OAAA,EAAS,SAAS,EAAE,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ,EAC3D,QAAA,kBAAA,IAAA;AAAA,IAAC,IAAA;AAAA,IAAA;AAAA,MACC,WAAW,OAAA,CAAQ,IAAA;AAAA,MACnB,IAAA;AAAA,MACA,MAAA,EAAO,QAAA;AAAA,MACP,GAAA,EAAI,qBAAA;AAAA,MACJ,YAAA,EAAY,GAAG,OAAO,CAAA,mBAAA,CAAA;AAAA,MACtB,OAAA;AAAA,MAEC,QAAA,EAAA;AAAA,QAAA,QAAA;AAAA,4BACA,UAAA,EAAA,EAAW,SAAA,EAAW,OAAA,CAAQ,IAAA,EAAM,eAAY,MAAA,EAAO;AAAA;AAAA;AAAA,GAC1D,EACF,CAAA;AAEJ;;;;"}
@@ -0,0 +1,38 @@
1
+ const FEATURE_TYPES = {
2
+ CONFIG: "CONFIG"};
3
+ const MAX_DISPLAY_TAGS = 3;
4
+ const MAX_TABLE_ENVIRONMENTS = 6;
5
+ const MAX_DETAIL_ENVIRONMENTS = 10;
6
+ const DESCRIPTION_TRUNCATE_LENGTH = 60;
7
+ const PAGINATION_OPTIONS = [10, 25, 50, 100];
8
+ const DEFAULT_ROWS_PER_PAGE = 50;
9
+ const CHART_CONFIG = {
10
+ HEIGHT: 250,
11
+ MARGIN: { top: 5, right: 30, left: 0, bottom: 5 }
12
+ };
13
+ const ENV_COLORS = {
14
+ development: "#4caf50",
15
+ dev: "#4caf50",
16
+ staging: "#ff9800",
17
+ stage: "#ff9800",
18
+ production: "#f44336",
19
+ prod: "#f44336"
20
+ };
21
+ const DEFAULT_ENV_COLORS = [
22
+ "#2196f3",
23
+ "#9c27b0",
24
+ "#00bcd4",
25
+ "#795548",
26
+ "#607d8b",
27
+ "#e91e63"
28
+ ];
29
+ const getEnvColor = (envName, index) => {
30
+ const lowerName = envName.toLowerCase();
31
+ for (const [key, color] of Object.entries(ENV_COLORS)) {
32
+ if (lowerName.includes(key)) return color;
33
+ }
34
+ return DEFAULT_ENV_COLORS[index % DEFAULT_ENV_COLORS.length] || "#2196f3";
35
+ };
36
+
37
+ export { CHART_CONFIG, DEFAULT_ENV_COLORS, DEFAULT_ROWS_PER_PAGE, DESCRIPTION_TRUNCATE_LENGTH, ENV_COLORS, FEATURE_TYPES, MAX_DETAIL_ENVIRONMENTS, MAX_DISPLAY_TAGS, MAX_TABLE_ENVIRONMENTS, PAGINATION_OPTIONS, getEnvColor };
38
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":["../../src/constants/index.ts"],"sourcesContent":["/**\n * Constants for FlagsTab and related components\n */\n\n/** Feature types from Flagsmith API */\nexport const FEATURE_TYPES = {\n CONFIG: 'CONFIG',\n FLAG: 'FLAG',\n} as const;\n\n/** Maximum number of tags to display inline before showing \"+N more\" */\nexport const MAX_DISPLAY_TAGS = 3;\n\n/** Maximum number of environments to show in the main table columns */\nexport const MAX_TABLE_ENVIRONMENTS = 6;\n\n/** Maximum number of environments to show in the detailed environment table */\nexport const MAX_DETAIL_ENVIRONMENTS = 10;\n\n/** Maximum characters for description truncation */\nexport const DESCRIPTION_TRUNCATE_LENGTH = 60;\n\n/** Pagination options for the flags table */\nexport const PAGINATION_OPTIONS = [10, 25, 50, 100];\n\n/** Default rows per page */\nexport const DEFAULT_ROWS_PER_PAGE = 50;\n\n/** Chart dimensions */\nexport const CHART_CONFIG = {\n HEIGHT: 250,\n MARGIN: { top: 5, right: 30, left: 0, bottom: 5 },\n} as const;\n\n/** Environment colors for analytics chart */\nexport const ENV_COLORS: Record<string, string> = {\n development: '#4caf50',\n dev: '#4caf50',\n staging: '#ff9800',\n stage: '#ff9800',\n production: '#f44336',\n prod: '#f44336',\n};\n\n/** Fallback colors for environments not matching predefined names */\nexport const DEFAULT_ENV_COLORS = [\n '#2196f3',\n '#9c27b0',\n '#00bcd4',\n '#795548',\n '#607d8b',\n '#e91e63',\n] as const;\n\n/**\n * Get color for an environment based on its name\n */\nexport const getEnvColor = (envName: string, index: number): string => {\n const lowerName = envName.toLowerCase();\n for (const [key, color] of Object.entries(ENV_COLORS)) {\n if (lowerName.includes(key)) return color;\n }\n return DEFAULT_ENV_COLORS[index % DEFAULT_ENV_COLORS.length] || '#2196f3';\n};\n"],"names":[],"mappings":"AAKO,MAAM,aAAA,GAAgB;AAAA,EAC3B,MAAA,EAAQ,QAEV;AAGO,MAAM,gBAAA,GAAmB;AAGzB,MAAM,sBAAA,GAAyB;AAG/B,MAAM,uBAAA,GAA0B;AAGhC,MAAM,2BAAA,GAA8B;AAGpC,MAAM,kBAAA,GAAqB,CAAC,EAAA,EAAI,EAAA,EAAI,IAAI,GAAG;AAG3C,MAAM,qBAAA,GAAwB;AAG9B,MAAM,YAAA,GAAe;AAAA,EAC1B,MAAA,EAAQ,GAAA;AAAA,EACR,MAAA,EAAQ,EAAE,GAAA,EAAK,CAAA,EAAG,OAAO,EAAA,EAAI,IAAA,EAAM,CAAA,EAAG,MAAA,EAAQ,CAAA;AAChD;AAGO,MAAM,UAAA,GAAqC;AAAA,EAChD,WAAA,EAAa,SAAA;AAAA,EACb,GAAA,EAAK,SAAA;AAAA,EACL,OAAA,EAAS,SAAA;AAAA,EACT,KAAA,EAAO,SAAA;AAAA,EACP,UAAA,EAAY,SAAA;AAAA,EACZ,IAAA,EAAM;AACR;AAGO,MAAM,kBAAA,GAAqB;AAAA,EAChC,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF;AAKO,MAAM,WAAA,GAAc,CAAC,OAAA,EAAiB,KAAA,KAA0B;AACrE,EAAA,MAAM,SAAA,GAAY,QAAQ,WAAA,EAAY;AACtC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,UAAU,CAAA,EAAG;AACrD,IAAA,IAAI,SAAA,CAAU,QAAA,CAAS,GAAG,CAAA,EAAG,OAAO,KAAA;AAAA,EACtC;AACA,EAAA,OAAO,kBAAA,CAAmB,KAAA,GAAQ,kBAAA,CAAmB,MAAM,CAAA,IAAK,SAAA;AAClE;;;;"}
@@ -36,7 +36,13 @@ function useFlagsmithProject(projectId) {
36
36
  };
37
37
  fetchData();
38
38
  }, [projectId, client]);
39
- return { project, environments, features, loading, error, client };
39
+ const envIds = environments.map((e) => e.id).join(",");
40
+ const memoizedEnvironments = useMemo(
41
+ () => environments,
42
+ // eslint-disable-next-line react-hooks/exhaustive-deps
43
+ [envIds]
44
+ );
45
+ return { project, environments: memoizedEnvironments, features, loading, error, client };
40
46
  }
41
47
 
42
48
  export { useFlagsmithProject };
@@ -1 +1 @@
1
- {"version":3,"file":"useFlagsmithProject.esm.js","sources":["../../src/hooks/useFlagsmithProject.ts"],"sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport { useApi, discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';\nimport {\n FlagsmithClient,\n FlagsmithProject,\n FlagsmithEnvironment,\n FlagsmithFeature,\n} from '../api/FlagsmithClient';\n\nexport interface UseFlagsmithProjectResult {\n project: FlagsmithProject | null;\n environments: FlagsmithEnvironment[];\n features: FlagsmithFeature[];\n loading: boolean;\n error: string | null;\n client: FlagsmithClient;\n}\n\nexport function useFlagsmithProject(\n projectId: string | undefined,\n): UseFlagsmithProjectResult {\n const discoveryApi = useApi(discoveryApiRef);\n const fetchApi = useApi(fetchApiRef);\n\n const client = useMemo(\n () => new FlagsmithClient(discoveryApi, fetchApi),\n [discoveryApi, fetchApi],\n );\n\n const [project, setProject] = useState<FlagsmithProject | null>(null);\n const [environments, setEnvironments] = useState<FlagsmithEnvironment[]>([]);\n const [features, setFeatures] = useState<FlagsmithFeature[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!projectId) {\n setError('No Flagsmith project ID found in entity annotations');\n setLoading(false);\n return;\n }\n\n const fetchData = async () => {\n try {\n const projectData = await client.getProject(parseInt(projectId, 10));\n setProject(projectData);\n\n const envs = await client.getProjectEnvironments(parseInt(projectId, 10));\n setEnvironments(envs || []);\n\n const projectFeatures = await client.getProjectFeatures(projectId);\n setFeatures(projectFeatures || []);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Unknown error');\n } finally {\n setLoading(false);\n }\n };\n\n fetchData();\n }, [projectId, client]);\n\n return { project, environments, features, loading, error, client };\n}\n"],"names":[],"mappings":";;;;AAkBO,SAAS,oBACd,SAAA,EAC2B;AAC3B,EAAA,MAAM,YAAA,GAAe,OAAO,eAAe,CAAA;AAC3C,EAAA,MAAM,QAAA,GAAW,OAAO,WAAW,CAAA;AAEnC,EAAA,MAAM,MAAA,GAAS,OAAA;AAAA,IACb,MAAM,IAAI,eAAA,CAAgB,YAAA,EAAc,QAAQ,CAAA;AAAA,IAChD,CAAC,cAAc,QAAQ;AAAA,GACzB;AAEA,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAkC,IAAI,CAAA;AACpE,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAI,QAAA,CAAiC,EAAE,CAAA;AAC3E,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,QAAA,CAA6B,EAAE,CAAA;AAC/D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AAEtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,QAAA,CAAS,qDAAqD,CAAA;AAC9D,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,YAAY,YAAY;AAC5B,MAAA,IAAI;AACF,QAAA,MAAM,cAAc,MAAM,MAAA,CAAO,WAAW,QAAA,CAAS,SAAA,EAAW,EAAE,CAAC,CAAA;AACnE,QAAA,UAAA,CAAW,WAAW,CAAA;AAEtB,QAAA,MAAM,OAAO,MAAM,MAAA,CAAO,uBAAuB,QAAA,CAAS,SAAA,EAAW,EAAE,CAAC,CAAA;AACxE,QAAA,eAAA,CAAgB,IAAA,IAAQ,EAAE,CAAA;AAE1B,QAAA,MAAM,eAAA,GAAkB,MAAM,MAAA,CAAO,kBAAA,CAAmB,SAAS,CAAA;AACjE,QAAA,WAAA,CAAY,eAAA,IAAmB,EAAE,CAAA;AAAA,MACnC,SAAS,GAAA,EAAK;AACZ,QAAA,QAAA,CAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,eAAe,CAAA;AAAA,MAC/D,CAAA,SAAE;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAEA,IAAA,SAAA,EAAU;AAAA,EACZ,CAAA,EAAG,CAAC,SAAA,EAAW,MAAM,CAAC,CAAA;AAEtB,EAAA,OAAO,EAAE,OAAA,EAAS,YAAA,EAAc,QAAA,EAAU,OAAA,EAAS,OAAO,MAAA,EAAO;AACnE;;;;"}
1
+ {"version":3,"file":"useFlagsmithProject.esm.js","sources":["../../src/hooks/useFlagsmithProject.ts"],"sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport { useApi, discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';\nimport {\n FlagsmithClient,\n FlagsmithProject,\n FlagsmithEnvironment,\n FlagsmithFeature,\n} from '../api/FlagsmithClient';\n\nexport interface UseFlagsmithProjectResult {\n project: FlagsmithProject | null;\n environments: FlagsmithEnvironment[];\n features: FlagsmithFeature[];\n loading: boolean;\n error: string | null;\n client: FlagsmithClient;\n}\n\nexport function useFlagsmithProject(\n projectId: string | undefined,\n): UseFlagsmithProjectResult {\n const discoveryApi = useApi(discoveryApiRef);\n const fetchApi = useApi(fetchApiRef);\n\n const client = useMemo(\n () => new FlagsmithClient(discoveryApi, fetchApi),\n [discoveryApi, fetchApi],\n );\n\n const [project, setProject] = useState<FlagsmithProject | null>(null);\n const [environments, setEnvironments] = useState<FlagsmithEnvironment[]>([]);\n const [features, setFeatures] = useState<FlagsmithFeature[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!projectId) {\n setError('No Flagsmith project ID found in entity annotations');\n setLoading(false);\n return;\n }\n\n const fetchData = async () => {\n try {\n const projectData = await client.getProject(parseInt(projectId, 10));\n setProject(projectData);\n\n const envs = await client.getProjectEnvironments(parseInt(projectId, 10));\n setEnvironments(envs || []);\n\n const projectFeatures = await client.getProjectFeatures(projectId);\n setFeatures(projectFeatures || []);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Unknown error');\n } finally {\n setLoading(false);\n }\n };\n\n fetchData();\n }, [projectId, client]);\n\n // Memoize environments to prevent unnecessary re-renders in child components\n // Only create new reference when environment IDs actually change\n const envIds = environments.map(e => e.id).join(',');\n const memoizedEnvironments = useMemo(\n () => environments,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [envIds],\n );\n\n return { project, environments: memoizedEnvironments, features, loading, error, client };\n}\n"],"names":[],"mappings":";;;;AAkBO,SAAS,oBACd,SAAA,EAC2B;AAC3B,EAAA,MAAM,YAAA,GAAe,OAAO,eAAe,CAAA;AAC3C,EAAA,MAAM,QAAA,GAAW,OAAO,WAAW,CAAA;AAEnC,EAAA,MAAM,MAAA,GAAS,OAAA;AAAA,IACb,MAAM,IAAI,eAAA,CAAgB,YAAA,EAAc,QAAQ,CAAA;AAAA,IAChD,CAAC,cAAc,QAAQ;AAAA,GACzB;AAEA,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAkC,IAAI,CAAA;AACpE,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAI,QAAA,CAAiC,EAAE,CAAA;AAC3E,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,QAAA,CAA6B,EAAE,CAAA;AAC/D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AAEtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,QAAA,CAAS,qDAAqD,CAAA;AAC9D,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,YAAY,YAAY;AAC5B,MAAA,IAAI;AACF,QAAA,MAAM,cAAc,MAAM,MAAA,CAAO,WAAW,QAAA,CAAS,SAAA,EAAW,EAAE,CAAC,CAAA;AACnE,QAAA,UAAA,CAAW,WAAW,CAAA;AAEtB,QAAA,MAAM,OAAO,MAAM,MAAA,CAAO,uBAAuB,QAAA,CAAS,SAAA,EAAW,EAAE,CAAC,CAAA;AACxE,QAAA,eAAA,CAAgB,IAAA,IAAQ,EAAE,CAAA;AAE1B,QAAA,MAAM,eAAA,GAAkB,MAAM,MAAA,CAAO,kBAAA,CAAmB,SAAS,CAAA;AACjE,QAAA,WAAA,CAAY,eAAA,IAAmB,EAAE,CAAA;AAAA,MACnC,SAAS,GAAA,EAAK;AACZ,QAAA,QAAA,CAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,eAAe,CAAA;AAAA,MAC/D,CAAA,SAAE;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAEA,IAAA,SAAA,EAAU;AAAA,EACZ,CAAA,EAAG,CAAC,SAAA,EAAW,MAAM,CAAC,CAAA;AAItB,EAAA,MAAM,MAAA,GAAS,aAAa,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,EAAE,CAAA,CAAE,KAAK,GAAG,CAAA;AACnD,EAAA,MAAM,oBAAA,GAAuB,OAAA;AAAA,IAC3B,MAAM,YAAA;AAAA;AAAA,IAEN,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,OAAO,EAAE,OAAA,EAAS,YAAA,EAAc,sBAAsB,QAAA,EAAU,OAAA,EAAS,OAAO,MAAA,EAAO;AACzF;;;;"}
@@ -1,10 +1,14 @@
1
- import { useState, useEffect } from 'react';
1
+ import { useMemo, useState, useEffect } from 'react';
2
2
  import { useApi, discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
3
3
  import { FlagsmithClient } from '../api/FlagsmithClient.esm.js';
4
4
 
5
5
  function useFlagsmithUsage(projectId, orgId) {
6
6
  const discoveryApi = useApi(discoveryApiRef);
7
7
  const fetchApi = useApi(fetchApiRef);
8
+ const client = useMemo(
9
+ () => new FlagsmithClient(discoveryApi, fetchApi),
10
+ [discoveryApi, fetchApi]
11
+ );
8
12
  const [project, setProject] = useState(null);
9
13
  const [usageData, setUsageData] = useState([]);
10
14
  const [loading, setLoading] = useState(true);
@@ -17,7 +21,6 @@ function useFlagsmithUsage(projectId, orgId) {
17
21
  }
18
22
  const fetchData = async () => {
19
23
  try {
20
- const client = new FlagsmithClient(discoveryApi, fetchApi);
21
24
  const projectData = await client.getProject(parseInt(projectId, 10));
22
25
  setProject(projectData);
23
26
  const usage = await client.getUsageData(
@@ -32,7 +35,7 @@ function useFlagsmithUsage(projectId, orgId) {
32
35
  }
33
36
  };
34
37
  fetchData();
35
- }, [projectId, orgId, discoveryApi, fetchApi]);
38
+ }, [projectId, orgId, client]);
36
39
  const totalFlags = usageData.reduce((sum, day) => sum + (day.flags ?? 0), 0);
37
40
  return { project, usageData, totalFlags, loading, error };
38
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useFlagsmithUsage.esm.js","sources":["../../src/hooks/useFlagsmithUsage.ts"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { useApi, discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';\nimport {\n FlagsmithClient,\n FlagsmithProject,\n FlagsmithUsageData,\n} from '../api/FlagsmithClient';\n\nexport interface UseFlagsmithUsageResult {\n project: FlagsmithProject | null;\n usageData: FlagsmithUsageData[];\n totalFlags: number;\n loading: boolean;\n error: string | null;\n}\n\nexport function useFlagsmithUsage(\n projectId: string | undefined,\n orgId: string | undefined,\n): UseFlagsmithUsageResult {\n const discoveryApi = useApi(discoveryApiRef);\n const fetchApi = useApi(fetchApiRef);\n\n const [project, setProject] = useState<FlagsmithProject | null>(null);\n const [usageData, setUsageData] = useState<FlagsmithUsageData[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!projectId || !orgId) {\n setError('Missing Flagsmith project ID or organization ID in entity annotations');\n setLoading(false);\n return;\n }\n\n const fetchData = async () => {\n try {\n const client = new FlagsmithClient(discoveryApi, fetchApi);\n\n const projectData = await client.getProject(parseInt(projectId, 10));\n setProject(projectData);\n\n const usage = await client.getUsageData(\n parseInt(orgId, 10),\n parseInt(projectId, 10),\n );\n setUsageData(usage);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Unknown error');\n } finally {\n setLoading(false);\n }\n };\n\n fetchData();\n }, [projectId, orgId, discoveryApi, fetchApi]);\n\n const totalFlags = usageData.reduce((sum, day) => sum + (day.flags ?? 0), 0);\n\n return { project, usageData, totalFlags, loading, error };\n}\n"],"names":[],"mappings":";;;;AAgBO,SAAS,iBAAA,CACd,WACA,KAAA,EACyB;AACzB,EAAA,MAAM,YAAA,GAAe,OAAO,eAAe,CAAA;AAC3C,EAAA,MAAM,QAAA,GAAW,OAAO,WAAW,CAAA;AAEnC,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAkC,IAAI,CAAA;AACpE,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,QAAA,CAA+B,EAAE,CAAA;AACnE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AAEtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,SAAA,IAAa,CAAC,KAAA,EAAO;AACxB,MAAA,QAAA,CAAS,uEAAuE,CAAA;AAChF,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,YAAY,YAAY;AAC5B,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,YAAA,EAAc,QAAQ,CAAA;AAEzD,QAAA,MAAM,cAAc,MAAM,MAAA,CAAO,WAAW,QAAA,CAAS,SAAA,EAAW,EAAE,CAAC,CAAA;AACnE,QAAA,UAAA,CAAW,WAAW,CAAA;AAEtB,QAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,YAAA;AAAA,UACzB,QAAA,CAAS,OAAO,EAAE,CAAA;AAAA,UAClB,QAAA,CAAS,WAAW,EAAE;AAAA,SACxB;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB,SAAS,GAAA,EAAK;AACZ,QAAA,QAAA,CAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,eAAe,CAAA;AAAA,MAC/D,CAAA,SAAE;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAEA,IAAA,SAAA,EAAU;AAAA,EACZ,GAAG,CAAC,SAAA,EAAW,KAAA,EAAO,YAAA,EAAc,QAAQ,CAAC,CAAA;AAE7C,EAAA,MAAM,UAAA,GAAa,SAAA,CAAU,MAAA,CAAO,CAAC,GAAA,EAAK,QAAQ,GAAA,IAAO,GAAA,CAAI,KAAA,IAAS,CAAA,CAAA,EAAI,CAAC,CAAA;AAE3E,EAAA,OAAO,EAAE,OAAA,EAAS,SAAA,EAAW,UAAA,EAAY,SAAS,KAAA,EAAM;AAC1D;;;;"}
1
+ {"version":3,"file":"useFlagsmithUsage.esm.js","sources":["../../src/hooks/useFlagsmithUsage.ts"],"sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport { useApi, discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';\nimport {\n FlagsmithClient,\n FlagsmithProject,\n FlagsmithUsageData,\n} from '../api/FlagsmithClient';\n\nexport interface UseFlagsmithUsageResult {\n project: FlagsmithProject | null;\n usageData: FlagsmithUsageData[];\n totalFlags: number;\n loading: boolean;\n error: string | null;\n}\n\nexport function useFlagsmithUsage(\n projectId: string | undefined,\n orgId: string | undefined,\n): UseFlagsmithUsageResult {\n const discoveryApi = useApi(discoveryApiRef);\n const fetchApi = useApi(fetchApiRef);\n\n // Memoize client to prevent recreation on every render\n const client = useMemo(\n () => new FlagsmithClient(discoveryApi, fetchApi),\n [discoveryApi, fetchApi],\n );\n\n const [project, setProject] = useState<FlagsmithProject | null>(null);\n const [usageData, setUsageData] = useState<FlagsmithUsageData[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!projectId || !orgId) {\n setError('Missing Flagsmith project ID or organization ID in entity annotations');\n setLoading(false);\n return;\n }\n\n const fetchData = async () => {\n try {\n const projectData = await client.getProject(parseInt(projectId, 10));\n setProject(projectData);\n\n const usage = await client.getUsageData(\n parseInt(orgId, 10),\n parseInt(projectId, 10),\n );\n setUsageData(usage);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Unknown error');\n } finally {\n setLoading(false);\n }\n };\n\n fetchData();\n }, [projectId, orgId, client]);\n\n const totalFlags = usageData.reduce((sum, day) => sum + (day.flags ?? 0), 0);\n\n return { project, usageData, totalFlags, loading, error };\n}\n"],"names":[],"mappings":";;;;AAgBO,SAAS,iBAAA,CACd,WACA,KAAA,EACyB;AACzB,EAAA,MAAM,YAAA,GAAe,OAAO,eAAe,CAAA;AAC3C,EAAA,MAAM,QAAA,GAAW,OAAO,WAAW,CAAA;AAGnC,EAAA,MAAM,MAAA,GAAS,OAAA;AAAA,IACb,MAAM,IAAI,eAAA,CAAgB,YAAA,EAAc,QAAQ,CAAA;AAAA,IAChD,CAAC,cAAc,QAAQ;AAAA,GACzB;AAEA,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAkC,IAAI,CAAA;AACpE,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,QAAA,CAA+B,EAAE,CAAA;AACnE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AAEtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,SAAA,IAAa,CAAC,KAAA,EAAO;AACxB,MAAA,QAAA,CAAS,uEAAuE,CAAA;AAChF,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,YAAY,YAAY;AAC5B,MAAA,IAAI;AACF,QAAA,MAAM,cAAc,MAAM,MAAA,CAAO,WAAW,QAAA,CAAS,SAAA,EAAW,EAAE,CAAC,CAAA;AACnE,QAAA,UAAA,CAAW,WAAW,CAAA;AAEtB,QAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,YAAA;AAAA,UACzB,QAAA,CAAS,OAAO,EAAE,CAAA;AAAA,UAClB,QAAA,CAAS,WAAW,EAAE;AAAA,SACxB;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB,SAAS,GAAA,EAAK;AACZ,QAAA,QAAA,CAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,eAAe,CAAA;AAAA,MAC/D,CAAA,SAAE;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAEA,IAAA,SAAA,EAAU;AAAA,EACZ,CAAA,EAAG,CAAC,SAAA,EAAW,KAAA,EAAO,MAAM,CAAC,CAAA;AAE7B,EAAA,MAAM,UAAA,GAAa,SAAA,CAAU,MAAA,CAAO,CAAC,GAAA,EAAK,QAAQ,GAAA,IAAO,GAAA,CAAI,KAAA,IAAS,CAAA,CAAA,EAAI,CAAC,CAAA;AAE3E,EAAA,OAAO,EAAE,OAAA,EAAS,SAAA,EAAW,UAAA,EAAY,SAAS,KAAA,EAAM;AAC1D;;;;"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,13 @@ import * as _backstage_catalog_model from '@backstage/catalog-model';
2
2
  import * as _backstage_frontend_plugin_api from '@backstage/frontend-plugin-api';
3
3
  import * as react from 'react';
4
4
  import * as _backstage_plugin_catalog_react_alpha from '@backstage/plugin-catalog-react/alpha';
5
+ import * as react_jsx_runtime from 'react/jsx-runtime';
6
+
7
+ declare const FlagsTab: () => react_jsx_runtime.JSX.Element;
8
+
9
+ declare const FlagsmithOverviewCard: () => react_jsx_runtime.JSX.Element;
10
+
11
+ declare const FlagsmithUsageCard: () => react_jsx_runtime.JSX.Element;
5
12
 
6
13
  /**
7
14
  * Flagsmith plugin for Backstage's new frontend system.
@@ -93,7 +100,7 @@ declare const flagsmithPlugin: _backstage_frontend_plugin_api.OverridableFronten
93
100
  defaultTitle?: [Error: `Use the 'title' param instead`];
94
101
  title: string;
95
102
  defaultGroup?: [Error: `Use the 'group' param instead`];
96
- group?: ("overview" | "documentation" | "development" | "deployment" | "operation" | "observability") | (string & {});
103
+ group?: ("development" | "overview" | "documentation" | "deployment" | "operation" | "observability") | (string & {});
97
104
  loader: () => Promise<JSX.Element>;
98
105
  routeRef?: _backstage_frontend_plugin_api.RouteRef;
99
106
  filter?: string | _backstage_plugin_catalog_react_alpha.EntityPredicate | ((entity: _backstage_catalog_model.Entity) => boolean);
@@ -101,4 +108,4 @@ declare const flagsmithPlugin: _backstage_frontend_plugin_api.OverridableFronten
101
108
  }>;
102
109
  }>;
103
110
 
104
- export { flagsmithPlugin as default };
111
+ export { FlagsTab, FlagsmithOverviewCard, FlagsmithUsageCard, flagsmithPlugin as default };
package/dist/index.esm.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { createElement } from 'react';
2
2
  import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
3
3
  import { EntityContentBlueprint, EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha';
4
+ export { FlagsTab } from './components/FlagsTab/index.esm.js';
5
+ export { FlagsmithOverviewCard } from './components/FlagsmithOverviewCard/index.esm.js';
6
+ export { FlagsmithUsageCard } from './components/FlagsmithUsageCard/index.esm.js';
4
7
 
5
8
  const flagsTabContent = EntityContentBlueprint.make({
6
9
  name: "flags",
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/index.ts"],"sourcesContent":["import { createElement } from 'react';\nimport { createFrontendPlugin } from '@backstage/frontend-plugin-api';\nimport {\n EntityContentBlueprint,\n EntityCardBlueprint,\n} from '@backstage/plugin-catalog-react/alpha';\n\n/**\n * Entity content (tab) for FlagsTab - displays feature flags for an entity\n * Requires annotation: flagsmith.com/project-id\n */\nconst flagsTabContent = EntityContentBlueprint.make({\n name: 'flags',\n params: {\n path: '/flagsmith',\n title: 'Feature Flags',\n filter: 'has:annotation:flagsmith.com/project-id',\n loader: () =>\n import('./components/FlagsTab').then(m => createElement(m.FlagsTab)),\n },\n});\n\n/**\n * Entity card for FlagsmithOverviewCard - shows flag overview in entity page\n * Requires annotation: flagsmith.com/project-id\n */\nconst overviewCard = EntityCardBlueprint.make({\n name: 'overview',\n params: {\n filter: 'has:annotation:flagsmith.com/project-id',\n loader: () =>\n import('./components/FlagsmithOverviewCard').then(m =>\n createElement(m.FlagsmithOverviewCard),\n ),\n },\n});\n\n/**\n * Entity card for FlagsmithUsageCard - shows 30-day usage analytics\n * Requires annotations: flagsmith.com/project-id, flagsmith.com/org-id\n */\nconst usageCard = EntityCardBlueprint.make({\n name: 'usage',\n params: {\n filter: 'has:annotation:flagsmith.com/project-id,flagsmith.com/org-id',\n loader: () =>\n import('./components/FlagsmithUsageCard').then(m =>\n createElement(m.FlagsmithUsageCard),\n ),\n },\n});\n\n/**\n * Flagsmith plugin for Backstage's new frontend system.\n *\n * This plugin provides:\n * - Entity content tab showing feature flags\n * - Overview card for entity pages\n * - Usage analytics card\n */\nconst flagsmithPlugin = createFrontendPlugin({\n pluginId: 'flagsmith',\n extensions: [flagsTabContent, overviewCard, usageCard],\n});\n\nexport default flagsmithPlugin;\n"],"names":[],"mappings":";;;;AAWA,MAAM,eAAA,GAAkB,uBAAuB,IAAA,CAAK;AAAA,EAClD,IAAA,EAAM,OAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,YAAA;AAAA,IACN,KAAA,EAAO,eAAA;AAAA,IACP,MAAA,EAAQ,yCAAA;AAAA,IACR,MAAA,EAAQ,MACN,OAAO,oCAAuB,CAAA,CAAE,KAAK,CAAA,CAAA,KAAK,aAAA,CAAc,CAAA,CAAE,QAAQ,CAAC;AAAA;AAEzE,CAAC,CAAA;AAMD,MAAM,YAAA,GAAe,oBAAoB,IAAA,CAAK;AAAA,EAC5C,IAAA,EAAM,UAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,MAAA,EAAQ,yCAAA;AAAA,IACR,MAAA,EAAQ,MACN,OAAO,iDAAoC,CAAA,CAAE,IAAA;AAAA,MAAK,CAAA,CAAA,KAChD,aAAA,CAAc,CAAA,CAAE,qBAAqB;AAAA;AACvC;AAEN,CAAC,CAAA;AAMD,MAAM,SAAA,GAAY,oBAAoB,IAAA,CAAK;AAAA,EACzC,IAAA,EAAM,OAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,MAAA,EAAQ,8DAAA;AAAA,IACR,MAAA,EAAQ,MACN,OAAO,8CAAiC,CAAA,CAAE,IAAA;AAAA,MAAK,CAAA,CAAA,KAC7C,aAAA,CAAc,CAAA,CAAE,kBAAkB;AAAA;AACpC;AAEN,CAAC,CAAA;AAUD,MAAM,kBAAkB,oBAAA,CAAqB;AAAA,EAC3C,QAAA,EAAU,WAAA;AAAA,EACV,UAAA,EAAY,CAAC,eAAA,EAAiB,YAAA,EAAc,SAAS;AACvD,CAAC;;;;"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/index.ts"],"sourcesContent":["import { createElement } from 'react';\nimport { createFrontendPlugin } from '@backstage/frontend-plugin-api';\nimport {\n EntityContentBlueprint,\n EntityCardBlueprint,\n} from '@backstage/plugin-catalog-react/alpha';\n\n/**\n * Entity content (tab) for FlagsTab - displays feature flags for an entity\n * Requires annotation: flagsmith.com/project-id\n */\nconst flagsTabContent = EntityContentBlueprint.make({\n name: 'flags',\n params: {\n path: '/flagsmith',\n title: 'Feature Flags',\n filter: 'has:annotation:flagsmith.com/project-id',\n loader: () =>\n import('./components/FlagsTab').then(m => createElement(m.FlagsTab)),\n },\n});\n\n/**\n * Entity card for FlagsmithOverviewCard - shows flag overview in entity page\n * Requires annotation: flagsmith.com/project-id\n */\nconst overviewCard = EntityCardBlueprint.make({\n name: 'overview',\n params: {\n filter: 'has:annotation:flagsmith.com/project-id',\n loader: () =>\n import('./components/FlagsmithOverviewCard').then(m =>\n createElement(m.FlagsmithOverviewCard),\n ),\n },\n});\n\n/**\n * Entity card for FlagsmithUsageCard - shows 30-day usage analytics\n * Requires annotations: flagsmith.com/project-id, flagsmith.com/org-id\n */\nconst usageCard = EntityCardBlueprint.make({\n name: 'usage',\n params: {\n filter: 'has:annotation:flagsmith.com/project-id,flagsmith.com/org-id',\n loader: () =>\n import('./components/FlagsmithUsageCard').then(m =>\n createElement(m.FlagsmithUsageCard),\n ),\n },\n});\n\n/**\n * Flagsmith plugin for Backstage's new frontend system.\n *\n * This plugin provides:\n * - Entity content tab showing feature flags\n * - Overview card for entity pages\n * - Usage analytics card\n */\nconst flagsmithPlugin = createFrontendPlugin({\n pluginId: 'flagsmith',\n extensions: [flagsTabContent, overviewCard, usageCard],\n});\n\nexport default flagsmithPlugin;\n\n// Legacy frontend system exports\nexport { FlagsTab } from './components/FlagsTab';\nexport { FlagsmithOverviewCard } from './components/FlagsmithOverviewCard';\nexport { FlagsmithUsageCard } from './components/FlagsmithUsageCard';\n"],"names":[],"mappings":";;;;;;;AAWA,MAAM,eAAA,GAAkB,uBAAuB,IAAA,CAAK;AAAA,EAClD,IAAA,EAAM,OAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,YAAA;AAAA,IACN,KAAA,EAAO,eAAA;AAAA,IACP,MAAA,EAAQ,yCAAA;AAAA,IACR,MAAA,EAAQ,MACN,OAAO,oCAAuB,CAAA,CAAE,KAAK,CAAA,CAAA,KAAK,aAAA,CAAc,CAAA,CAAE,QAAQ,CAAC;AAAA;AAEzE,CAAC,CAAA;AAMD,MAAM,YAAA,GAAe,oBAAoB,IAAA,CAAK;AAAA,EAC5C,IAAA,EAAM,UAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,MAAA,EAAQ,yCAAA;AAAA,IACR,MAAA,EAAQ,MACN,OAAO,iDAAoC,CAAA,CAAE,IAAA;AAAA,MAAK,CAAA,CAAA,KAChD,aAAA,CAAc,CAAA,CAAE,qBAAqB;AAAA;AACvC;AAEN,CAAC,CAAA;AAMD,MAAM,SAAA,GAAY,oBAAoB,IAAA,CAAK;AAAA,EACzC,IAAA,EAAM,OAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,MAAA,EAAQ,8DAAA;AAAA,IACR,MAAA,EAAQ,MACN,OAAO,8CAAiC,CAAA,CAAE,IAAA;AAAA,MAAK,CAAA,CAAA,KAC7C,aAAA,CAAc,CAAA,CAAE,kBAAkB;AAAA;AACpC;AAEN,CAAC,CAAA;AAUD,MAAM,kBAAkB,oBAAA,CAAqB;AAAA,EAC3C,QAAA,EAAU,WAAA;AAAA,EACV,UAAA,EAAY,CAAC,eAAA,EAAiB,YAAA,EAAc,SAAS;AACvD,CAAC;;;;"}
@@ -0,0 +1,19 @@
1
+ import { flagsmithColors } from './flagsmithTheme.esm.js';
2
+
3
+ const switchOnStyle = {
4
+ "& .MuiSwitch-switchBase.Mui-checked": {
5
+ color: flagsmithColors.primary
6
+ },
7
+ "& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track": {
8
+ backgroundColor: flagsmithColors.primary
9
+ }
10
+ };
11
+ const detailCardStyle = (theme) => ({
12
+ padding: theme.spacing(1.5),
13
+ marginBottom: theme.spacing(1),
14
+ border: `1px solid ${theme.palette.divider}`,
15
+ borderRadius: theme.shape.borderRadius
16
+ });
17
+
18
+ export { detailCardStyle, switchOnStyle };
19
+ //# sourceMappingURL=sharedStyles.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharedStyles.esm.js","sources":["../../src/theme/sharedStyles.ts"],"sourcesContent":["import { Theme } from '@material-ui/core/styles';\nimport { flagsmithColors } from './flagsmithTheme';\n\n/**\n * Shared style for colored Switch components\n */\nexport const switchOnStyle = {\n '& .MuiSwitch-switchBase.Mui-checked': {\n color: flagsmithColors.primary,\n },\n '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {\n backgroundColor: flagsmithColors.primary,\n },\n};\n\n/**\n * Shared styles for small chips (tags, badges)\n */\nexport const smallChipStyle = (theme: Theme) => ({\n fontSize: '0.7rem',\n height: 20,\n marginRight: theme.spacing(0.5),\n});\n\n/**\n * Shared styles for detail cards\n */\nexport const detailCardStyle = (theme: Theme) => ({\n padding: theme.spacing(1.5),\n marginBottom: theme.spacing(1),\n border: `1px solid ${theme.palette.divider}`,\n borderRadius: theme.shape.borderRadius,\n});\n"],"names":[],"mappings":";;AAMO,MAAM,aAAA,GAAgB;AAAA,EAC3B,qCAAA,EAAuC;AAAA,IACrC,OAAO,eAAA,CAAgB;AAAA,GACzB;AAAA,EACA,wDAAA,EAA0D;AAAA,IACxD,iBAAiB,eAAA,CAAgB;AAAA;AAErC;AAcO,MAAM,eAAA,GAAkB,CAAC,KAAA,MAAkB;AAAA,EAChD,OAAA,EAAS,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAAA,EAC1B,YAAA,EAAc,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA;AAAA,EAC7B,MAAA,EAAQ,CAAA,UAAA,EAAa,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,CAAA;AAAA,EAC1C,YAAA,EAAc,MAAM,KAAA,CAAM;AAC5B,CAAA;;;;"}
@@ -0,0 +1,15 @@
1
+ const formatDate = (date) => {
2
+ return new Date(date).toLocaleDateString();
3
+ };
4
+ const formatDateTime = (date) => {
5
+ return new Date(date).toLocaleString();
6
+ };
7
+ const formatShortDate = (date) => {
8
+ return new Date(date).toLocaleDateString("en-US", {
9
+ month: "short",
10
+ day: "numeric"
11
+ });
12
+ };
13
+
14
+ export { formatDate, formatDateTime, formatShortDate };
15
+ //# sourceMappingURL=dateFormatters.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dateFormatters.esm.js","sources":["../../src/utils/dateFormatters.ts"],"sourcesContent":["/**\n * Consistent date formatting utilities\n */\n\n/**\n * Format a date as a short date string (e.g., \"1/15/2024\")\n */\nexport const formatDate = (date: string | Date): string => {\n return new Date(date).toLocaleDateString();\n};\n\n/**\n * Format a date with time (e.g., \"1/15/2024, 10:30 AM\")\n */\nexport const formatDateTime = (date: string | Date): string => {\n return new Date(date).toLocaleString();\n};\n\n/**\n * Format a date as short month and day (e.g., \"Jan 15\")\n */\nexport const formatShortDate = (date: string | Date): string => {\n return new Date(date).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n });\n};\n\n/**\n * Check if a date is in the future\n */\nexport const isFutureDate = (date: string | Date): boolean => {\n return new Date(date) > new Date();\n};\n"],"names":[],"mappings":"AAOO,MAAM,UAAA,GAAa,CAAC,IAAA,KAAgC;AACzD,EAAA,OAAO,IAAI,IAAA,CAAK,IAAI,CAAA,CAAE,kBAAA,EAAmB;AAC3C;AAKO,MAAM,cAAA,GAAiB,CAAC,IAAA,KAAgC;AAC7D,EAAA,OAAO,IAAI,IAAA,CAAK,IAAI,CAAA,CAAE,cAAA,EAAe;AACvC;AAKO,MAAM,eAAA,GAAkB,CAAC,IAAA,KAAgC;AAC9D,EAAA,OAAO,IAAI,IAAA,CAAK,IAAI,CAAA,CAAE,mBAAmB,OAAA,EAAS;AAAA,IAChD,KAAA,EAAO,OAAA;AAAA,IACP,GAAA,EAAK;AAAA,GACN,CAAA;AACH;;;;"}
@@ -0,0 +1,42 @@
1
+ import { FEATURE_TYPES } from '../constants/index.esm.js';
2
+
3
+ const isDefined = (value) => {
4
+ return value !== null && value !== void 0;
5
+ };
6
+ const isMultivariateFeature = (feature) => {
7
+ return Boolean(
8
+ feature.multivariate_options && feature.multivariate_options.length > 0
9
+ );
10
+ };
11
+ const getFlagType = (feature) => {
12
+ if (isMultivariateFeature(feature)) return "Multivariate";
13
+ if (feature.type === FEATURE_TYPES.CONFIG) return "Remote Config";
14
+ return "Standard";
15
+ };
16
+ const getValueType = (feature) => {
17
+ if (isMultivariateFeature(feature) && feature.multivariate_options) {
18
+ const firstOption = feature.multivariate_options[0];
19
+ if (isDefined(firstOption.string_value)) return "String";
20
+ if (isDefined(firstOption.integer_value)) return "Number";
21
+ if (isDefined(firstOption.boolean_value)) return "Boolean";
22
+ }
23
+ if (feature.type === FEATURE_TYPES.CONFIG && isDefined(feature.initial_value)) {
24
+ const value = feature.initial_value;
25
+ if (value === "true" || value === "false") return "Boolean";
26
+ if (!isNaN(Number(value))) return "Number";
27
+ return "String";
28
+ }
29
+ return "Boolean";
30
+ };
31
+ const truncateText = (text, maxLength) => {
32
+ if (text.length <= maxLength) return text;
33
+ return `${text.substring(0, maxLength)}...`;
34
+ };
35
+ const getErrorMessage = (error, fallback) => {
36
+ if (error instanceof Error) return error.message;
37
+ if (typeof error === "string") return error;
38
+ return fallback;
39
+ };
40
+
41
+ export { getErrorMessage, getFlagType, getValueType, isDefined, isMultivariateFeature, truncateText };
42
+ //# sourceMappingURL=flagTypeHelpers.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flagTypeHelpers.esm.js","sources":["../../src/utils/flagTypeHelpers.ts"],"sourcesContent":["import { FlagsmithFeature } from '../api/FlagsmithClient';\nimport { FEATURE_TYPES } from '../constants';\n\nexport type FlagType = 'Multivariate' | 'Remote Config' | 'Standard';\nexport type ValueType = 'Boolean' | 'String' | 'Number';\n\nexport interface FlagTypeInfo {\n flagType: FlagType;\n valueType: ValueType;\n isMultivariate: boolean;\n}\n\n/**\n * Check if a value is defined (not null or undefined)\n */\nexport const isDefined = <T>(value: T | null | undefined): value is T => {\n return value !== null && value !== undefined;\n};\n\n/**\n * Check if a feature is multivariate\n */\nexport const isMultivariateFeature = (feature: FlagsmithFeature): boolean => {\n return Boolean(\n feature.multivariate_options && feature.multivariate_options.length > 0\n );\n};\n\n/**\n * Determine the flag type for display\n */\nexport const getFlagType = (feature: FlagsmithFeature): FlagType => {\n if (isMultivariateFeature(feature)) return 'Multivariate';\n if (feature.type === FEATURE_TYPES.CONFIG) return 'Remote Config';\n return 'Standard';\n};\n\n/**\n * Determine the value type based on feature configuration\n */\nexport const getValueType = (feature: FlagsmithFeature): ValueType => {\n // Check multivariate options first\n if (isMultivariateFeature(feature) && feature.multivariate_options) {\n const firstOption = feature.multivariate_options[0];\n if (isDefined(firstOption.string_value)) return 'String';\n if (isDefined(firstOption.integer_value)) return 'Number';\n if (isDefined(firstOption.boolean_value)) return 'Boolean';\n }\n\n // Check CONFIG type with initial_value\n if (feature.type === FEATURE_TYPES.CONFIG && isDefined(feature.initial_value)) {\n const value = feature.initial_value;\n if (value === 'true' || value === 'false') return 'Boolean';\n if (!isNaN(Number(value))) return 'Number';\n return 'String';\n }\n\n // Default for FLAG type\n return 'Boolean';\n};\n\n/**\n * Get complete flag type information\n */\nexport const getFlagTypeInfo = (feature: FlagsmithFeature): FlagTypeInfo => ({\n flagType: getFlagType(feature),\n valueType: getValueType(feature),\n isMultivariate: isMultivariateFeature(feature),\n});\n\n/**\n * Truncate text to a maximum length with ellipsis\n */\nexport const truncateText = (text: string, maxLength: number): string => {\n if (text.length <= maxLength) return text;\n return `${text.substring(0, maxLength)}...`;\n};\n\n/**\n * Get error message from unknown error\n */\nexport const getErrorMessage = (error: unknown, fallback: string): string => {\n if (error instanceof Error) return error.message;\n if (typeof error === 'string') return error;\n return fallback;\n};\n"],"names":[],"mappings":";;AAeO,MAAM,SAAA,GAAY,CAAI,KAAA,KAA4C;AACvE,EAAA,OAAO,KAAA,KAAU,QAAQ,KAAA,KAAU,MAAA;AACrC;AAKO,MAAM,qBAAA,GAAwB,CAAC,OAAA,KAAuC;AAC3E,EAAA,OAAO,OAAA;AAAA,IACL,OAAA,CAAQ,oBAAA,IAAwB,OAAA,CAAQ,oBAAA,CAAqB,MAAA,GAAS;AAAA,GACxE;AACF;AAKO,MAAM,WAAA,GAAc,CAAC,OAAA,KAAwC;AAClE,EAAA,IAAI,qBAAA,CAAsB,OAAO,CAAA,EAAG,OAAO,cAAA;AAC3C,EAAA,IAAI,OAAA,CAAQ,IAAA,KAAS,aAAA,CAAc,MAAA,EAAQ,OAAO,eAAA;AAClD,EAAA,OAAO,UAAA;AACT;AAKO,MAAM,YAAA,GAAe,CAAC,OAAA,KAAyC;AAEpE,EAAA,IAAI,qBAAA,CAAsB,OAAO,CAAA,IAAK,OAAA,CAAQ,oBAAA,EAAsB;AAClE,IAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,oBAAA,CAAqB,CAAC,CAAA;AAClD,IAAA,IAAI,SAAA,CAAU,WAAA,CAAY,YAAY,CAAA,EAAG,OAAO,QAAA;AAChD,IAAA,IAAI,SAAA,CAAU,WAAA,CAAY,aAAa,CAAA,EAAG,OAAO,QAAA;AACjD,IAAA,IAAI,SAAA,CAAU,WAAA,CAAY,aAAa,CAAA,EAAG,OAAO,SAAA;AAAA,EACnD;AAGA,EAAA,IAAI,QAAQ,IAAA,KAAS,aAAA,CAAc,UAAU,SAAA,CAAU,OAAA,CAAQ,aAAa,CAAA,EAAG;AAC7E,IAAA,MAAM,QAAQ,OAAA,CAAQ,aAAA;AACtB,IAAA,IAAI,KAAA,KAAU,MAAA,IAAU,KAAA,KAAU,OAAA,EAAS,OAAO,SAAA;AAClD,IAAA,IAAI,CAAC,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,GAAG,OAAO,QAAA;AAClC,IAAA,OAAO,QAAA;AAAA,EACT;AAGA,EAAA,OAAO,SAAA;AACT;AAcO,MAAM,YAAA,GAAe,CAAC,IAAA,EAAc,SAAA,KAA8B;AACvE,EAAA,IAAI,IAAA,CAAK,MAAA,IAAU,SAAA,EAAW,OAAO,IAAA;AACrC,EAAA,OAAO,CAAA,EAAG,IAAA,CAAK,SAAA,CAAU,CAAA,EAAG,SAAS,CAAC,CAAA,GAAA,CAAA;AACxC;AAKO,MAAM,eAAA,GAAkB,CAAC,KAAA,EAAgB,QAAA,KAA6B;AAC3E,EAAA,IAAI,KAAA,YAAiB,KAAA,EAAO,OAAO,KAAA,CAAM,OAAA;AACzC,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,KAAA;AACtC,EAAA,OAAO,QAAA;AACT;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flagsmith/backstage-plugin",
3
- "version": "0.1.0-pr.7.a55a280",
3
+ "version": "0.1.0-pr.7.de3318e",
4
4
  "description": "Backstage plugin for Flagsmith feature flag management",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {