@flagsmith/backstage-plugin 0.1.0-pr.7.3135fb9 → 0.1.0-pr.7.ffc27ed

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 (46) hide show
  1. package/dist/api/FlagsmithClient.esm.js +2 -28
  2. package/dist/api/FlagsmithClient.esm.js.map +1 -1
  3. package/dist/components/FlagsTab/EnvironmentTable.esm.js +45 -83
  4. package/dist/components/FlagsTab/EnvironmentTable.esm.js.map +1 -1
  5. package/dist/components/FlagsTab/ExpandableRow.esm.js +101 -188
  6. package/dist/components/FlagsTab/ExpandableRow.esm.js.map +1 -1
  7. package/dist/components/FlagsTab/FeatureDetailsGrid.esm.js +35 -111
  8. package/dist/components/FlagsTab/FeatureDetailsGrid.esm.js.map +1 -1
  9. package/dist/components/FlagsTab/SegmentOverridesSection.esm.js +0 -1
  10. package/dist/components/FlagsTab/SegmentOverridesSection.esm.js.map +1 -1
  11. package/dist/components/FlagsTab/index.esm.js +16 -48
  12. package/dist/components/FlagsTab/index.esm.js.map +1 -1
  13. package/dist/components/FlagsmithOverviewCard/FeatureFlagRow.esm.js +0 -1
  14. package/dist/components/FlagsmithOverviewCard/FeatureFlagRow.esm.js.map +1 -1
  15. package/dist/components/FlagsmithOverviewCard/FlagStatsRow.esm.js +0 -1
  16. package/dist/components/FlagsmithOverviewCard/FlagStatsRow.esm.js.map +1 -1
  17. package/dist/components/FlagsmithOverviewCard/index.esm.js +5 -4
  18. package/dist/components/FlagsmithOverviewCard/index.esm.js.map +1 -1
  19. package/dist/components/FlagsmithUsageCard/UsageChart.esm.js +3 -40
  20. package/dist/components/FlagsmithUsageCard/UsageChart.esm.js.map +1 -1
  21. package/dist/components/FlagsmithUsageCard/UsageTooltip.esm.js +52 -0
  22. package/dist/components/FlagsmithUsageCard/UsageTooltip.esm.js.map +1 -0
  23. package/dist/components/FlagsmithUsageCard/index.esm.js +13 -11
  24. package/dist/components/FlagsmithUsageCard/index.esm.js.map +1 -1
  25. package/dist/components/shared/FlagsmithLink.esm.js +4 -12
  26. package/dist/components/shared/FlagsmithLink.esm.js.map +1 -1
  27. package/dist/hooks/useFlagsmithProject.esm.js +1 -7
  28. package/dist/hooks/useFlagsmithProject.esm.js.map +1 -1
  29. package/dist/hooks/useFlagsmithUsage.esm.js +3 -6
  30. package/dist/hooks/useFlagsmithUsage.esm.js.map +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/package.json +1 -1
  33. package/dist/components/FlagsTab/FeatureAnalyticsSection.esm.js +0 -178
  34. package/dist/components/FlagsTab/FeatureAnalyticsSection.esm.js.map +0 -1
  35. package/dist/components/shared/ChartTooltip.esm.js +0 -33
  36. package/dist/components/shared/ChartTooltip.esm.js.map +0 -1
  37. package/dist/components/shared/ErrorState.esm.js +0 -13
  38. package/dist/components/shared/ErrorState.esm.js.map +0 -1
  39. package/dist/constants/index.esm.js +0 -38
  40. package/dist/constants/index.esm.js.map +0 -1
  41. package/dist/theme/sharedStyles.esm.js +0 -19
  42. package/dist/theme/sharedStyles.esm.js.map +0 -1
  43. package/dist/utils/dateFormatters.esm.js +0 -15
  44. package/dist/utils/dateFormatters.esm.js.map +0 -1
  45. package/dist/utils/flagTypeHelpers.esm.js +0 -42
  46. package/dist/utils/flagTypeHelpers.esm.js.map +0 -1
@@ -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((theme) => ({
7
+ const useStyles = makeStyles(() => ({
8
8
  link: {
9
9
  display: "inline-flex",
10
10
  alignItems: "center",
@@ -23,23 +23,17 @@ const useStyles = makeStyles((theme) => ({
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"
31
26
  }
32
27
  }));
33
28
  const FlagsmithLink = ({
34
29
  href,
35
30
  children,
36
31
  tooltip = "Open in Flagsmith",
37
- iconOnly = false,
38
- onClick
32
+ iconOnly = false
39
33
  }) => {
40
34
  const classes = useStyles();
41
35
  if (iconOnly) {
42
- return /* @__PURE__ */ jsx(Tooltip, { title: tooltip, classes: { tooltip: classes.tooltip }, children: /* @__PURE__ */ jsx(
36
+ return /* @__PURE__ */ jsx(Tooltip, { title: tooltip, children: /* @__PURE__ */ jsx(
43
37
  IconButton,
44
38
  {
45
39
  className: classes.iconButton,
@@ -48,12 +42,11 @@ const FlagsmithLink = ({
48
42
  rel: "noopener noreferrer",
49
43
  size: "small",
50
44
  "aria-label": tooltip,
51
- onClick,
52
45
  children: /* @__PURE__ */ jsx(LaunchIcon, { fontSize: "small", "aria-hidden": "true" })
53
46
  }
54
47
  ) });
55
48
  }
56
- return /* @__PURE__ */ jsx(Tooltip, { title: tooltip, classes: { tooltip: classes.tooltip }, children: /* @__PURE__ */ jsxs(
49
+ return /* @__PURE__ */ jsx(Tooltip, { title: tooltip, children: /* @__PURE__ */ jsxs(
57
50
  Link,
58
51
  {
59
52
  className: classes.link,
@@ -61,7 +54,6 @@ const FlagsmithLink = ({
61
54
  target: "_blank",
62
55
  rel: "noopener noreferrer",
63
56
  "aria-label": `${tooltip} (opens in new tab)`,
64
- onClick,
65
57
  children: [
66
58
  children,
67
59
  /* @__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(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;;;;"}
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;;;;"}
@@ -36,13 +36,7 @@ function useFlagsmithProject(projectId) {
36
36
  };
37
37
  fetchData();
38
38
  }, [projectId, 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 };
39
+ return { project, environments, features, loading, error, client };
46
40
  }
47
41
 
48
42
  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 // 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
+ {"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,14 +1,10 @@
1
- import { useMemo, useState, useEffect } from 'react';
1
+ import { 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
- );
12
8
  const [project, setProject] = useState(null);
13
9
  const [usageData, setUsageData] = useState([]);
14
10
  const [loading, setLoading] = useState(true);
@@ -21,6 +17,7 @@ function useFlagsmithUsage(projectId, orgId) {
21
17
  }
22
18
  const fetchData = async () => {
23
19
  try {
20
+ const client = new FlagsmithClient(discoveryApi, fetchApi);
24
21
  const projectData = await client.getProject(parseInt(projectId, 10));
25
22
  setProject(projectData);
26
23
  const usage = await client.getUsageData(
@@ -35,7 +32,7 @@ function useFlagsmithUsage(projectId, orgId) {
35
32
  }
36
33
  };
37
34
  fetchData();
38
- }, [projectId, orgId, client]);
35
+ }, [projectId, orgId, discoveryApi, fetchApi]);
39
36
  const totalFlags = usageData.reduce((sum, day) => sum + (day.flags ?? 0), 0);
40
37
  return { project, usageData, totalFlags, loading, error };
41
38
  }
@@ -1 +1 @@
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;;;;"}
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;;;;"}
package/dist/index.d.ts CHANGED
@@ -100,7 +100,7 @@ declare const flagsmithPlugin: _backstage_frontend_plugin_api.OverridableFronten
100
100
  defaultTitle?: [Error: `Use the 'title' param instead`];
101
101
  title: string;
102
102
  defaultGroup?: [Error: `Use the 'group' param instead`];
103
- group?: ("development" | "overview" | "documentation" | "deployment" | "operation" | "observability") | (string & {});
103
+ group?: ("overview" | "documentation" | "development" | "deployment" | "operation" | "observability") | (string & {});
104
104
  loader: () => Promise<JSX.Element>;
105
105
  routeRef?: _backstage_frontend_plugin_api.RouteRef;
106
106
  filter?: string | _backstage_plugin_catalog_react_alpha.EntityPredicate | ((entity: _backstage_catalog_model.Entity) => boolean);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flagsmith/backstage-plugin",
3
- "version": "0.1.0-pr.7.3135fb9",
3
+ "version": "0.1.0-pr.7.ffc27ed",
4
4
  "description": "Backstage plugin for Flagsmith feature flag management",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -1,178 +0,0 @@
1
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { useState, useEffect } from 'react';
3
- import { Box, Typography, CircularProgress } from '@material-ui/core';
4
- import { makeStyles } from '@material-ui/core/styles';
5
- import { ResponsiveContainer, LineChart, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Line } from 'recharts';
6
- import { CHART_CONFIG, getEnvColor, MAX_TABLE_ENVIRONMENTS } from '../../constants/index.esm.js';
7
- import { getErrorMessage } from '../../utils/flagTypeHelpers.esm.js';
8
- import { formatShortDate } from '../../utils/dateFormatters.esm.js';
9
- import '../shared/FlagStatusIndicator.esm.js';
10
- import '../shared/SearchInput.esm.js';
11
- import '../shared/FlagsmithLink.esm.js';
12
- import '@material-ui/icons/ChevronLeft';
13
- import '@material-ui/icons/ChevronRight';
14
- import { ChartTooltip, ChartTooltipText } from '../shared/ChartTooltip.esm.js';
15
-
16
- const useStyles = makeStyles((theme) => ({
17
- container: {
18
- padding: theme.spacing(2),
19
- border: `1px solid ${theme.palette.divider}`,
20
- borderRadius: theme.shape.borderRadius,
21
- backgroundColor: theme.palette.background.paper
22
- },
23
- chartContainer: {
24
- width: "100%",
25
- height: CHART_CONFIG.HEIGHT,
26
- marginTop: theme.spacing(1)
27
- },
28
- noData: {
29
- display: "flex",
30
- alignItems: "center",
31
- justifyContent: "center",
32
- height: 150,
33
- color: theme.palette.text.secondary
34
- },
35
- loading: {
36
- display: "flex",
37
- alignItems: "center",
38
- justifyContent: "center",
39
- gap: theme.spacing(1),
40
- height: 150
41
- }
42
- }));
43
- const AnalyticsTooltip = ({ active, payload, label }) => {
44
- return /* @__PURE__ */ jsx(ChartTooltip, { active, payload, label, children: (data, tooltipLabel) => /* @__PURE__ */ jsxs(Fragment, { children: [
45
- /* @__PURE__ */ jsx(ChartTooltipText, { variant: "subtitle2", fontWeight: 600, children: tooltipLabel }),
46
- /* @__PURE__ */ jsx(Box, { mt: 1, children: data.map((entry, index) => /* @__PURE__ */ jsxs(
47
- Box,
48
- {
49
- display: "flex",
50
- alignItems: "center",
51
- mt: 0.25,
52
- style: { gap: "4px" },
53
- children: [
54
- /* @__PURE__ */ jsx(
55
- "span",
56
- {
57
- style: {
58
- display: "inline-block",
59
- width: "8px",
60
- height: "8px",
61
- backgroundColor: entry.color,
62
- borderRadius: "50%"
63
- }
64
- }
65
- ),
66
- /* @__PURE__ */ jsxs(ChartTooltipText, { children: [
67
- entry.name,
68
- ": ",
69
- entry.value
70
- ] })
71
- ]
72
- },
73
- `item-${index}`
74
- )) })
75
- ] }) });
76
- };
77
- const transformUsageData = (usageByEnv) => {
78
- const dataByDate = /* @__PURE__ */ new Map();
79
- const envNames = [];
80
- usageByEnv.forEach((data, envName) => {
81
- envNames.push(envName);
82
- data.forEach((item) => {
83
- const date = formatShortDate(item.day);
84
- if (!dataByDate.has(date)) {
85
- dataByDate.set(date, { date });
86
- }
87
- const point = dataByDate.get(date);
88
- point[envName] = item.flags ?? 0;
89
- });
90
- });
91
- const sortedData = Array.from(dataByDate.values()).sort((a, b) => {
92
- const dateA = new Date(a.date);
93
- const dateB = new Date(b.date);
94
- return dateA.getTime() - dateB.getTime();
95
- });
96
- return { chartData: sortedData, envNames };
97
- };
98
- const FeatureAnalyticsSection = ({
99
- client,
100
- orgId,
101
- projectId,
102
- environments
103
- }) => {
104
- const classes = useStyles();
105
- const [loading, setLoading] = useState(true);
106
- const [chartData, setChartData] = useState([]);
107
- const [envNames, setEnvNames] = useState([]);
108
- const [error, setError] = useState(null);
109
- useEffect(() => {
110
- const fetchUsageData = async () => {
111
- if (!orgId || !projectId || environments.length === 0) {
112
- setLoading(false);
113
- return;
114
- }
115
- try {
116
- setLoading(true);
117
- setError(null);
118
- const displayedEnvs = environments.slice(0, MAX_TABLE_ENVIRONMENTS);
119
- const usageByEnv = await client.getUsageDataByEnvironments(
120
- orgId,
121
- projectId,
122
- displayedEnvs
123
- );
124
- const { chartData: data, envNames: names } = transformUsageData(usageByEnv);
125
- setChartData(data);
126
- setEnvNames(names);
127
- } catch (err) {
128
- setError(getErrorMessage(err, "Failed to load analytics"));
129
- } finally {
130
- setLoading(false);
131
- }
132
- };
133
- fetchUsageData();
134
- }, [client, orgId, projectId, environments]);
135
- if (loading) {
136
- return /* @__PURE__ */ jsxs(Box, { className: classes.container, children: [
137
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "Usage Analytics" }),
138
- /* @__PURE__ */ jsxs(Box, { className: classes.loading, children: [
139
- /* @__PURE__ */ jsx(CircularProgress, { size: 20 }),
140
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "textSecondary", children: "Loading analytics..." })
141
- ] })
142
- ] });
143
- }
144
- if (error) {
145
- return /* @__PURE__ */ jsxs(Box, { className: classes.container, children: [
146
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "Usage Analytics" }),
147
- /* @__PURE__ */ jsx(Box, { className: classes.noData, children: /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "error", children: error }) })
148
- ] });
149
- }
150
- if (chartData.length === 0) {
151
- return null;
152
- }
153
- return /* @__PURE__ */ jsxs(Box, { className: classes.container, children: [
154
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "Usage Analytics (Last 30 Days)" }),
155
- /* @__PURE__ */ jsx(Box, { className: classes.chartContainer, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(LineChart, { data: chartData, margin: CHART_CONFIG.MARGIN, children: [
156
- /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3" }),
157
- /* @__PURE__ */ jsx(XAxis, { dataKey: "date", fontSize: 12 }),
158
- /* @__PURE__ */ jsx(YAxis, { fontSize: 12 }),
159
- /* @__PURE__ */ jsx(Tooltip, { content: /* @__PURE__ */ jsx(AnalyticsTooltip, {}) }),
160
- /* @__PURE__ */ jsx(Legend, {}),
161
- envNames.map((envName, index) => /* @__PURE__ */ jsx(
162
- Line,
163
- {
164
- type: "monotone",
165
- dataKey: envName,
166
- stroke: getEnvColor(envName, index),
167
- strokeWidth: 2,
168
- dot: false,
169
- activeDot: { r: 4 }
170
- },
171
- envName
172
- ))
173
- ] }) }) })
174
- ] });
175
- };
176
-
177
- export { FeatureAnalyticsSection };
178
- //# sourceMappingURL=FeatureAnalyticsSection.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FeatureAnalyticsSection.esm.js","sources":["../../../src/components/FlagsTab/FeatureAnalyticsSection.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { Box, CircularProgress, Typography } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport {\n LineChart,\n Line,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n Legend,\n ResponsiveContainer,\n} from 'recharts';\nimport {\n FlagsmithClient,\n FlagsmithEnvironment,\n FlagsmithUsageData,\n} from '../../api/FlagsmithClient';\nimport {\n CHART_CONFIG,\n MAX_TABLE_ENVIRONMENTS,\n getEnvColor,\n} from '../../constants';\nimport { getErrorMessage } from '../../utils/flagTypeHelpers';\nimport { formatShortDate } from '../../utils/dateFormatters';\nimport { ChartTooltip, ChartTooltipText } from '../shared';\n\nconst useStyles = makeStyles(theme => ({\n container: {\n padding: theme.spacing(2),\n border: `1px solid ${theme.palette.divider}`,\n borderRadius: theme.shape.borderRadius,\n backgroundColor: theme.palette.background.paper,\n },\n chartContainer: {\n width: '100%',\n height: CHART_CONFIG.HEIGHT,\n marginTop: theme.spacing(1),\n },\n noData: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n height: 150,\n color: theme.palette.text.secondary,\n },\n loading: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: theme.spacing(1),\n height: 150,\n },\n}));\n\ninterface ChartDataPoint {\n date: string;\n [envName: string]: number | string;\n}\n\n/**\n * Custom tooltip component for analytics chart\n */\nconst AnalyticsTooltip = ({ active, payload, label }: any) => {\n return (\n <ChartTooltip active={active} payload={payload} label={label}>\n {(data, tooltipLabel) => (\n <>\n <ChartTooltipText variant=\"subtitle2\" fontWeight={600}>\n {tooltipLabel}\n </ChartTooltipText>\n <Box mt={1}>\n {data.map((entry: any, index: number) => (\n <Box\n key={`item-${index}`}\n display=\"flex\"\n alignItems=\"center\"\n mt={0.25}\n style={{ gap: '4px' }}\n >\n <span\n style={{\n display: 'inline-block',\n width: '8px',\n height: '8px',\n backgroundColor: entry.color,\n borderRadius: '50%',\n }}\n />\n <ChartTooltipText>\n {entry.name}: {entry.value}\n </ChartTooltipText>\n </Box>\n ))}\n </Box>\n </>\n )}\n </ChartTooltip>\n );\n};\n\ninterface FeatureAnalyticsSectionProps {\n client: FlagsmithClient;\n orgId: number;\n projectId: number;\n environments: FlagsmithEnvironment[];\n}\n\n/**\n * Transform usage data by environment into chart-friendly format\n */\nconst transformUsageData = (\n usageByEnv: Map<string, FlagsmithUsageData[]>,\n): { chartData: ChartDataPoint[]; envNames: string[] } => {\n const dataByDate = new Map<string, ChartDataPoint>();\n const envNames: string[] = [];\n\n usageByEnv.forEach((data: FlagsmithUsageData[], envName: string) => {\n envNames.push(envName);\n data.forEach(item => {\n const date = formatShortDate(item.day);\n if (!dataByDate.has(date)) {\n dataByDate.set(date, { date });\n }\n const point = dataByDate.get(date)!;\n point[envName] = item.flags ?? 0;\n });\n });\n\n const sortedData = Array.from(dataByDate.values()).sort((a, b) => {\n const dateA = new Date(a.date);\n const dateB = new Date(b.date);\n return dateA.getTime() - dateB.getTime();\n });\n\n return { chartData: sortedData, envNames };\n};\n\nexport const FeatureAnalyticsSection = ({\n client,\n orgId,\n projectId,\n environments,\n}: FeatureAnalyticsSectionProps) => {\n const classes = useStyles();\n const [loading, setLoading] = useState(true);\n const [chartData, setChartData] = useState<ChartDataPoint[]>([]);\n const [envNames, setEnvNames] = useState<string[]>([]);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const fetchUsageData = async () => {\n if (!orgId || !projectId || environments.length === 0) {\n setLoading(false);\n return;\n }\n\n try {\n setLoading(true);\n setError(null);\n\n const displayedEnvs = environments.slice(0, MAX_TABLE_ENVIRONMENTS);\n\n const usageByEnv = await client.getUsageDataByEnvironments(\n orgId,\n projectId,\n displayedEnvs,\n );\n\n const { chartData: data, envNames: names } = transformUsageData(usageByEnv);\n setChartData(data);\n setEnvNames(names);\n } catch (err) {\n setError(getErrorMessage(err, 'Failed to load analytics'));\n } finally {\n setLoading(false);\n }\n };\n\n fetchUsageData();\n }, [client, orgId, projectId, environments]);\n\n if (loading) {\n return (\n <Box className={classes.container}>\n <Typography variant=\"subtitle2\" gutterBottom>\n Usage Analytics\n </Typography>\n <Box className={classes.loading}>\n <CircularProgress size={20} />\n <Typography variant=\"body2\" color=\"textSecondary\">\n Loading analytics...\n </Typography>\n </Box>\n </Box>\n );\n }\n\n if (error) {\n return (\n <Box className={classes.container}>\n <Typography variant=\"subtitle2\" gutterBottom>\n Usage Analytics\n </Typography>\n <Box className={classes.noData}>\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n </Box>\n </Box>\n );\n }\n\n if (chartData.length === 0) {\n return null;\n }\n\n return (\n <Box className={classes.container}>\n <Typography variant=\"subtitle2\" gutterBottom>\n Usage Analytics (Last 30 Days)\n </Typography>\n <Box className={classes.chartContainer}>\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <LineChart data={chartData} margin={CHART_CONFIG.MARGIN}>\n <CartesianGrid strokeDasharray=\"3 3\" />\n <XAxis dataKey=\"date\" fontSize={12} />\n <YAxis fontSize={12} />\n <Tooltip content={<AnalyticsTooltip />} />\n <Legend />\n {envNames.map((envName, index) => (\n <Line\n key={envName}\n type=\"monotone\"\n dataKey={envName}\n stroke={getEnvColor(envName, index)}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4 }}\n />\n ))}\n </LineChart>\n </ResponsiveContainer>\n </Box>\n </Box>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -1,33 +0,0 @@
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
@@ -1 +0,0 @@
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;;;;"}
@@ -1,13 +0,0 @@
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
@@ -1 +0,0 @@
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;;;;"}
@@ -1,38 +0,0 @@
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
@@ -1 +0,0 @@
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;;;;"}
@@ -1,19 +0,0 @@
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
@@ -1 +0,0 @@
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;;;;"}
@@ -1,15 +0,0 @@
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
@@ -1 +0,0 @@
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;;;;"}
@@ -1,42 +0,0 @@
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
@@ -1 +0,0 @@
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;;;;"}