@flagsmith/backstage-plugin 0.1.0-pr.7.3135fb9

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 (60) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +163 -0
  3. package/dist/api/FlagsmithClient.esm.js +149 -0
  4. package/dist/api/FlagsmithClient.esm.js.map +1 -0
  5. package/dist/components/FlagsTab/EnvironmentTable.esm.js +124 -0
  6. package/dist/components/FlagsTab/EnvironmentTable.esm.js.map +1 -0
  7. package/dist/components/FlagsTab/ExpandableRow.esm.js +218 -0
  8. package/dist/components/FlagsTab/ExpandableRow.esm.js.map +1 -0
  9. package/dist/components/FlagsTab/FeatureAnalyticsSection.esm.js +178 -0
  10. package/dist/components/FlagsTab/FeatureAnalyticsSection.esm.js.map +1 -0
  11. package/dist/components/FlagsTab/FeatureDetailsGrid.esm.js +160 -0
  12. package/dist/components/FlagsTab/FeatureDetailsGrid.esm.js.map +1 -0
  13. package/dist/components/FlagsTab/SegmentOverridesSection.esm.js +129 -0
  14. package/dist/components/FlagsTab/SegmentOverridesSection.esm.js.map +1 -0
  15. package/dist/components/FlagsTab/index.esm.js +138 -0
  16. package/dist/components/FlagsTab/index.esm.js.map +1 -0
  17. package/dist/components/FlagsmithOverviewCard/FeatureFlagRow.esm.js +41 -0
  18. package/dist/components/FlagsmithOverviewCard/FeatureFlagRow.esm.js.map +1 -0
  19. package/dist/components/FlagsmithOverviewCard/FlagStatsRow.esm.js +46 -0
  20. package/dist/components/FlagsmithOverviewCard/FlagStatsRow.esm.js.map +1 -0
  21. package/dist/components/FlagsmithOverviewCard/index.esm.js +90 -0
  22. package/dist/components/FlagsmithOverviewCard/index.esm.js.map +1 -0
  23. package/dist/components/FlagsmithUsageCard/UsageChart.esm.js +84 -0
  24. package/dist/components/FlagsmithUsageCard/UsageChart.esm.js.map +1 -0
  25. package/dist/components/FlagsmithUsageCard/index.esm.js +62 -0
  26. package/dist/components/FlagsmithUsageCard/index.esm.js.map +1 -0
  27. package/dist/components/shared/ChartTooltip.esm.js +33 -0
  28. package/dist/components/shared/ChartTooltip.esm.js.map +1 -0
  29. package/dist/components/shared/ErrorState.esm.js +13 -0
  30. package/dist/components/shared/ErrorState.esm.js.map +1 -0
  31. package/dist/components/shared/FlagStatusIndicator.esm.js +61 -0
  32. package/dist/components/shared/FlagStatusIndicator.esm.js.map +1 -0
  33. package/dist/components/shared/FlagsmithLink.esm.js +74 -0
  34. package/dist/components/shared/FlagsmithLink.esm.js.map +1 -0
  35. package/dist/components/shared/LoadingState.esm.js +35 -0
  36. package/dist/components/shared/LoadingState.esm.js.map +1 -0
  37. package/dist/components/shared/MiniPagination.esm.js +66 -0
  38. package/dist/components/shared/MiniPagination.esm.js.map +1 -0
  39. package/dist/components/shared/SearchInput.esm.js +54 -0
  40. package/dist/components/shared/SearchInput.esm.js.map +1 -0
  41. package/dist/constants/index.esm.js +38 -0
  42. package/dist/constants/index.esm.js.map +1 -0
  43. package/dist/hooks/useFlagsmithProject.esm.js +49 -0
  44. package/dist/hooks/useFlagsmithProject.esm.js.map +1 -0
  45. package/dist/hooks/useFlagsmithUsage.esm.js +44 -0
  46. package/dist/hooks/useFlagsmithUsage.esm.js.map +1 -0
  47. package/dist/index.d.ts +111 -0
  48. package/dist/index.esm.js +41 -0
  49. package/dist/index.esm.js.map +1 -0
  50. package/dist/theme/flagsmithTheme.esm.js +28 -0
  51. package/dist/theme/flagsmithTheme.esm.js.map +1 -0
  52. package/dist/theme/sharedStyles.esm.js +19 -0
  53. package/dist/theme/sharedStyles.esm.js.map +1 -0
  54. package/dist/utils/dateFormatters.esm.js +15 -0
  55. package/dist/utils/dateFormatters.esm.js.map +1 -0
  56. package/dist/utils/flagHelpers.esm.js +25 -0
  57. package/dist/utils/flagHelpers.esm.js.map +1 -0
  58. package/dist/utils/flagTypeHelpers.esm.js +42 -0
  59. package/dist/utils/flagTypeHelpers.esm.js.map +1 -0
  60. package/package.json +108 -0
@@ -0,0 +1,49 @@
1
+ import { useMemo, useState, useEffect } from 'react';
2
+ import { useApi, discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
3
+ import { FlagsmithClient } from '../api/FlagsmithClient.esm.js';
4
+
5
+ function useFlagsmithProject(projectId) {
6
+ const discoveryApi = useApi(discoveryApiRef);
7
+ const fetchApi = useApi(fetchApiRef);
8
+ const client = useMemo(
9
+ () => new FlagsmithClient(discoveryApi, fetchApi),
10
+ [discoveryApi, fetchApi]
11
+ );
12
+ const [project, setProject] = useState(null);
13
+ const [environments, setEnvironments] = useState([]);
14
+ const [features, setFeatures] = useState([]);
15
+ const [loading, setLoading] = useState(true);
16
+ const [error, setError] = useState(null);
17
+ useEffect(() => {
18
+ if (!projectId) {
19
+ setError("No Flagsmith project ID found in entity annotations");
20
+ setLoading(false);
21
+ return;
22
+ }
23
+ const fetchData = async () => {
24
+ try {
25
+ const projectData = await client.getProject(parseInt(projectId, 10));
26
+ setProject(projectData);
27
+ const envs = await client.getProjectEnvironments(parseInt(projectId, 10));
28
+ setEnvironments(envs || []);
29
+ const projectFeatures = await client.getProjectFeatures(projectId);
30
+ setFeatures(projectFeatures || []);
31
+ } catch (err) {
32
+ setError(err instanceof Error ? err.message : "Unknown error");
33
+ } finally {
34
+ setLoading(false);
35
+ }
36
+ };
37
+ fetchData();
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 };
46
+ }
47
+
48
+ export { useFlagsmithProject };
49
+ //# sourceMappingURL=useFlagsmithProject.esm.js.map
@@ -0,0 +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;;;;"}
@@ -0,0 +1,44 @@
1
+ import { useMemo, useState, useEffect } from 'react';
2
+ import { useApi, discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
3
+ import { FlagsmithClient } from '../api/FlagsmithClient.esm.js';
4
+
5
+ function useFlagsmithUsage(projectId, orgId) {
6
+ const discoveryApi = useApi(discoveryApiRef);
7
+ const fetchApi = useApi(fetchApiRef);
8
+ const client = useMemo(
9
+ () => new FlagsmithClient(discoveryApi, fetchApi),
10
+ [discoveryApi, fetchApi]
11
+ );
12
+ const [project, setProject] = useState(null);
13
+ const [usageData, setUsageData] = useState([]);
14
+ const [loading, setLoading] = useState(true);
15
+ const [error, setError] = useState(null);
16
+ useEffect(() => {
17
+ if (!projectId || !orgId) {
18
+ setError("Missing Flagsmith project ID or organization ID in entity annotations");
19
+ setLoading(false);
20
+ return;
21
+ }
22
+ const fetchData = async () => {
23
+ try {
24
+ const projectData = await client.getProject(parseInt(projectId, 10));
25
+ setProject(projectData);
26
+ const usage = await client.getUsageData(
27
+ parseInt(orgId, 10),
28
+ parseInt(projectId, 10)
29
+ );
30
+ setUsageData(usage);
31
+ } catch (err) {
32
+ setError(err instanceof Error ? err.message : "Unknown error");
33
+ } finally {
34
+ setLoading(false);
35
+ }
36
+ };
37
+ fetchData();
38
+ }, [projectId, orgId, client]);
39
+ const totalFlags = usageData.reduce((sum, day) => sum + (day.flags ?? 0), 0);
40
+ return { project, usageData, totalFlags, loading, error };
41
+ }
42
+
43
+ export { useFlagsmithUsage };
44
+ //# sourceMappingURL=useFlagsmithUsage.esm.js.map
@@ -0,0 +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;;;;"}
@@ -0,0 +1,111 @@
1
+ import * as _backstage_catalog_model from '@backstage/catalog-model';
2
+ import * as _backstage_frontend_plugin_api from '@backstage/frontend-plugin-api';
3
+ import * as react from 'react';
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;
12
+
13
+ /**
14
+ * Flagsmith plugin for Backstage's new frontend system.
15
+ *
16
+ * This plugin provides:
17
+ * - Entity content tab showing feature flags
18
+ * - Overview card for entity pages
19
+ * - Usage analytics card
20
+ */
21
+ declare const flagsmithPlugin: _backstage_frontend_plugin_api.OverridableFrontendPlugin<{}, {}, {
22
+ "entity-card:flagsmith/overview": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
23
+ kind: "entity-card";
24
+ name: "overview";
25
+ config: {
26
+ filter: _backstage_plugin_catalog_react_alpha.EntityPredicate | undefined;
27
+ type: "content" | "summary" | "info" | undefined;
28
+ };
29
+ configInput: {
30
+ filter?: _backstage_plugin_catalog_react_alpha.EntityPredicate | undefined;
31
+ type?: "content" | "summary" | "info" | undefined;
32
+ };
33
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<(entity: _backstage_catalog_model.Entity) => boolean, "catalog.entity-filter-function", {
34
+ optional: true;
35
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "catalog.entity-filter-expression", {
36
+ optional: true;
37
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_plugin_catalog_react_alpha.EntityCardType, "catalog.entity-card-type", {
38
+ optional: true;
39
+ }>;
40
+ inputs: {};
41
+ params: {
42
+ loader: () => Promise<JSX.Element>;
43
+ filter?: string | _backstage_plugin_catalog_react_alpha.EntityPredicate | ((entity: _backstage_catalog_model.Entity) => boolean);
44
+ type?: _backstage_plugin_catalog_react_alpha.EntityCardType;
45
+ };
46
+ }>;
47
+ "entity-card:flagsmith/usage": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
48
+ kind: "entity-card";
49
+ name: "usage";
50
+ config: {
51
+ filter: _backstage_plugin_catalog_react_alpha.EntityPredicate | undefined;
52
+ type: "content" | "summary" | "info" | undefined;
53
+ };
54
+ configInput: {
55
+ filter?: _backstage_plugin_catalog_react_alpha.EntityPredicate | undefined;
56
+ type?: "content" | "summary" | "info" | undefined;
57
+ };
58
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<(entity: _backstage_catalog_model.Entity) => boolean, "catalog.entity-filter-function", {
59
+ optional: true;
60
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "catalog.entity-filter-expression", {
61
+ optional: true;
62
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_plugin_catalog_react_alpha.EntityCardType, "catalog.entity-card-type", {
63
+ optional: true;
64
+ }>;
65
+ inputs: {};
66
+ params: {
67
+ loader: () => Promise<JSX.Element>;
68
+ filter?: string | _backstage_plugin_catalog_react_alpha.EntityPredicate | ((entity: _backstage_catalog_model.Entity) => boolean);
69
+ type?: _backstage_plugin_catalog_react_alpha.EntityCardType;
70
+ };
71
+ }>;
72
+ "entity-content:flagsmith/flags": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
73
+ kind: "entity-content";
74
+ name: "flags";
75
+ config: {
76
+ path: string | undefined;
77
+ title: string | undefined;
78
+ filter: _backstage_plugin_catalog_react_alpha.EntityPredicate | undefined;
79
+ group: string | false | undefined;
80
+ };
81
+ configInput: {
82
+ filter?: _backstage_plugin_catalog_react_alpha.EntityPredicate | undefined;
83
+ title?: string | undefined;
84
+ path?: string | undefined;
85
+ group?: string | false | undefined;
86
+ };
87
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
88
+ optional: true;
89
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<(entity: _backstage_catalog_model.Entity) => boolean, "catalog.entity-filter-function", {
90
+ optional: true;
91
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "catalog.entity-filter-expression", {
92
+ optional: true;
93
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "catalog.entity-content-title", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "catalog.entity-content-group", {
94
+ optional: true;
95
+ }>;
96
+ inputs: {};
97
+ params: {
98
+ defaultPath?: [Error: `Use the 'path' param instead`];
99
+ path: string;
100
+ defaultTitle?: [Error: `Use the 'title' param instead`];
101
+ title: string;
102
+ defaultGroup?: [Error: `Use the 'group' param instead`];
103
+ group?: ("development" | "overview" | "documentation" | "deployment" | "operation" | "observability") | (string & {});
104
+ loader: () => Promise<JSX.Element>;
105
+ routeRef?: _backstage_frontend_plugin_api.RouteRef;
106
+ filter?: string | _backstage_plugin_catalog_react_alpha.EntityPredicate | ((entity: _backstage_catalog_model.Entity) => boolean);
107
+ };
108
+ }>;
109
+ }>;
110
+
111
+ export { FlagsTab, FlagsmithOverviewCard, FlagsmithUsageCard, flagsmithPlugin as default };
@@ -0,0 +1,41 @@
1
+ import { createElement } from 'react';
2
+ import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
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';
7
+
8
+ const flagsTabContent = EntityContentBlueprint.make({
9
+ name: "flags",
10
+ params: {
11
+ path: "/flagsmith",
12
+ title: "Feature Flags",
13
+ filter: "has:annotation:flagsmith.com/project-id",
14
+ loader: () => import('./components/FlagsTab/index.esm.js').then((m) => createElement(m.FlagsTab))
15
+ }
16
+ });
17
+ const overviewCard = EntityCardBlueprint.make({
18
+ name: "overview",
19
+ params: {
20
+ filter: "has:annotation:flagsmith.com/project-id",
21
+ loader: () => import('./components/FlagsmithOverviewCard/index.esm.js').then(
22
+ (m) => createElement(m.FlagsmithOverviewCard)
23
+ )
24
+ }
25
+ });
26
+ const usageCard = EntityCardBlueprint.make({
27
+ name: "usage",
28
+ params: {
29
+ filter: "has:annotation:flagsmith.com/project-id,flagsmith.com/org-id",
30
+ loader: () => import('./components/FlagsmithUsageCard/index.esm.js').then(
31
+ (m) => createElement(m.FlagsmithUsageCard)
32
+ )
33
+ }
34
+ });
35
+ const flagsmithPlugin = createFrontendPlugin({
36
+ pluginId: "flagsmith",
37
+ extensions: [flagsTabContent, overviewCard, usageCard]
38
+ });
39
+
40
+ export { flagsmithPlugin as default };
41
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +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\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,28 @@
1
+ const flagsmithColors = {
2
+ /** Teal - primary brand color, used for enabled states */
3
+ primary: "#0AC2A3",
4
+ /** Purple - secondary brand color, used for focus/branding */
5
+ secondary: "#7B51FB",
6
+ /** Green - flag enabled indicator */
7
+ enabled: "#4CAF50",
8
+ /** Gray - flag disabled indicator */
9
+ disabled: "#9E9E9E",
10
+ /** Orange - segment overrides warning */
11
+ warning: "#FF9800"};
12
+ const FLAGSMITH_DASHBOARD_URL = "https://app.flagsmith.com";
13
+ function buildFlagUrl(projectId, environmentId, featureId) {
14
+ const base = `${FLAGSMITH_DASHBOARD_URL}/project/${projectId}/environment/${environmentId}/features`;
15
+ if (featureId) {
16
+ return `${base}?feature=${featureId}`;
17
+ }
18
+ return base;
19
+ }
20
+ function buildProjectUrl(projectId, environmentId) {
21
+ if (environmentId) {
22
+ return `${FLAGSMITH_DASHBOARD_URL}/project/${projectId}/environment/${environmentId}/features`;
23
+ }
24
+ return `${FLAGSMITH_DASHBOARD_URL}/project/${projectId}`;
25
+ }
26
+
27
+ export { FLAGSMITH_DASHBOARD_URL, buildFlagUrl, buildProjectUrl, flagsmithColors };
28
+ //# sourceMappingURL=flagsmithTheme.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flagsmithTheme.esm.js","sources":["../../src/theme/flagsmithTheme.ts"],"sourcesContent":["/**\n * Flagsmith brand colors and theme constants\n */\nexport const flagsmithColors = {\n /** Teal - primary brand color, used for enabled states */\n primary: '#0AC2A3',\n /** Purple - secondary brand color, used for focus/branding */\n secondary: '#7B51FB',\n /** Green - flag enabled indicator */\n enabled: '#4CAF50',\n /** Gray - flag disabled indicator */\n disabled: '#9E9E9E',\n /** Orange - segment overrides warning */\n warning: '#FF9800',\n /** Background colors for status states */\n background: {\n enabled: 'rgba(76, 175, 80, 0.08)',\n disabled: 'rgba(158, 158, 158, 0.08)',\n warning: 'rgba(255, 152, 0, 0.1)',\n },\n};\n\n/** Default Flagsmith dashboard URL */\nexport const FLAGSMITH_DASHBOARD_URL = 'https://app.flagsmith.com';\n\n/**\n * Build URL to a specific feature flag in the Flagsmith dashboard\n */\nexport function buildFlagUrl(\n projectId: string | number,\n environmentId: string | number,\n featureId?: string | number,\n): string {\n const base = `${FLAGSMITH_DASHBOARD_URL}/project/${projectId}/environment/${environmentId}/features`;\n if (featureId) {\n return `${base}?feature=${featureId}`;\n }\n return base;\n}\n\n/**\n * Build URL to the project features page in the Flagsmith dashboard\n */\nexport function buildProjectUrl(\n projectId: string | number,\n environmentId?: string | number,\n): string {\n if (environmentId) {\n return `${FLAGSMITH_DASHBOARD_URL}/project/${projectId}/environment/${environmentId}/features`;\n }\n return `${FLAGSMITH_DASHBOARD_URL}/project/${projectId}`;\n}\n"],"names":[],"mappings":"AAGO,MAAM,eAAA,GAAkB;AAAA;AAAA,EAE7B,OAAA,EAAS,SAAA;AAAA;AAAA,EAET,SAAA,EAAW,SAAA;AAAA;AAAA,EAEX,OAAA,EAAS,SAAA;AAAA;AAAA,EAET,QAAA,EAAU,SAAA;AAAA;AAAA,EAEV,OAAA,EAAS,SAOX;AAGO,MAAM,uBAAA,GAA0B;AAKhC,SAAS,YAAA,CACd,SAAA,EACA,aAAA,EACA,SAAA,EACQ;AACR,EAAA,MAAM,OAAO,CAAA,EAAG,uBAAuB,CAAA,SAAA,EAAY,SAAS,gBAAgB,aAAa,CAAA,SAAA,CAAA;AACzF,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,OAAO,CAAA,EAAG,IAAI,CAAA,SAAA,EAAY,SAAS,CAAA,CAAA;AAAA,EACrC;AACA,EAAA,OAAO,IAAA;AACT;AAKO,SAAS,eAAA,CACd,WACA,aAAA,EACQ;AACR,EAAA,IAAI,aAAA,EAAe;AACjB,IAAA,OAAO,CAAA,EAAG,uBAAuB,CAAA,SAAA,EAAY,SAAS,gBAAgB,aAAa,CAAA,SAAA,CAAA;AAAA,EACrF;AACA,EAAA,OAAO,CAAA,EAAG,uBAAuB,CAAA,SAAA,EAAY,SAAS,CAAA,CAAA;AACxD;;;;"}
@@ -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,25 @@
1
+ function getFeatureEnvStatus(feature, envId) {
2
+ if (!feature.environment_state) {
3
+ return feature.default_enabled ?? false;
4
+ }
5
+ const state = feature.environment_state.find((s) => s.id === envId);
6
+ return state?.enabled ?? feature.default_enabled ?? false;
7
+ }
8
+ function buildEnvStatusTooltip(feature, environments) {
9
+ return environments.map((env) => `${env.name}: ${getFeatureEnvStatus(feature, env.id) ? "On" : "Off"}`).join(" \u2022 ");
10
+ }
11
+ function calculateFeatureStats(features) {
12
+ const enabledCount = features.filter((f) => f.default_enabled).length;
13
+ return {
14
+ enabledCount,
15
+ disabledCount: features.length - enabledCount
16
+ };
17
+ }
18
+ function paginate(items, page, pageSize) {
19
+ const totalPages = Math.ceil(items.length / pageSize);
20
+ const paginatedItems = items.slice(page * pageSize, (page + 1) * pageSize);
21
+ return { paginatedItems, totalPages };
22
+ }
23
+
24
+ export { buildEnvStatusTooltip, calculateFeatureStats, getFeatureEnvStatus, paginate };
25
+ //# sourceMappingURL=flagHelpers.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flagHelpers.esm.js","sources":["../../src/utils/flagHelpers.ts"],"sourcesContent":["import { FlagsmithFeature, FlagsmithEnvironment } from '../api/FlagsmithClient';\n\n/**\n * Get the enabled status for a feature in a specific environment.\n * Falls back to default_enabled if no environment-specific state exists.\n */\nexport function getFeatureEnvStatus(\n feature: FlagsmithFeature,\n envId: number,\n): boolean {\n if (!feature.environment_state) {\n return feature.default_enabled ?? false;\n }\n const state = feature.environment_state.find(s => s.id === envId);\n return state?.enabled ?? feature.default_enabled ?? false;\n}\n\n/**\n * Build a tooltip string showing feature status across all environments.\n */\nexport function buildEnvStatusTooltip(\n feature: FlagsmithFeature,\n environments: FlagsmithEnvironment[],\n): string {\n return environments\n .map(env => `${env.name}: ${getFeatureEnvStatus(feature, env.id) ? 'On' : 'Off'}`)\n .join(' • ');\n}\n\n/**\n * Calculate enabled/disabled feature counts.\n */\nexport function calculateFeatureStats(features: FlagsmithFeature[]): {\n enabledCount: number;\n disabledCount: number;\n} {\n const enabledCount = features.filter(f => f.default_enabled).length;\n return {\n enabledCount,\n disabledCount: features.length - enabledCount,\n };\n}\n\n/**\n * Paginate an array of items.\n */\nexport function paginate<T>(\n items: T[],\n page: number,\n pageSize: number,\n): {\n paginatedItems: T[];\n totalPages: number;\n} {\n const totalPages = Math.ceil(items.length / pageSize);\n const paginatedItems = items.slice(page * pageSize, (page + 1) * pageSize);\n return { paginatedItems, totalPages };\n}\n"],"names":[],"mappings":"AAMO,SAAS,mBAAA,CACd,SACA,KAAA,EACS;AACT,EAAA,IAAI,CAAC,QAAQ,iBAAA,EAAmB;AAC9B,IAAA,OAAO,QAAQ,eAAA,IAAmB,KAAA;AAAA,EACpC;AACA,EAAA,MAAM,QAAQ,OAAA,CAAQ,iBAAA,CAAkB,KAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,KAAK,CAAA;AAChE,EAAA,OAAO,KAAA,EAAO,OAAA,IAAW,OAAA,CAAQ,eAAA,IAAmB,KAAA;AACtD;AAKO,SAAS,qBAAA,CACd,SACA,YAAA,EACQ;AACR,EAAA,OAAO,aACJ,GAAA,CAAI,CAAA,GAAA,KAAO,CAAA,EAAG,GAAA,CAAI,IAAI,CAAA,EAAA,EAAK,mBAAA,CAAoB,OAAA,EAAS,GAAA,CAAI,EAAE,CAAA,GAAI,IAAA,GAAO,KAAK,CAAA,CAAE,CAAA,CAChF,KAAK,UAAK,CAAA;AACf;AAKO,SAAS,sBAAsB,QAAA,EAGpC;AACA,EAAA,MAAM,eAAe,QAAA,CAAS,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,eAAe,CAAA,CAAE,MAAA;AAC7D,EAAA,OAAO;AAAA,IACL,YAAA;AAAA,IACA,aAAA,EAAe,SAAS,MAAA,GAAS;AAAA,GACnC;AACF;AAKO,SAAS,QAAA,CACd,KAAA,EACA,IAAA,EACA,QAAA,EAIA;AACA,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,SAAS,QAAQ,CAAA;AACpD,EAAA,MAAM,iBAAiB,KAAA,CAAM,KAAA,CAAM,OAAO,QAAA,EAAA,CAAW,IAAA,GAAO,KAAK,QAAQ,CAAA;AACzE,EAAA,OAAO,EAAE,gBAAgB,UAAA,EAAW;AACtC;;;;"}
@@ -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 ADDED
@@ -0,0 +1,108 @@
1
+ {
2
+ "name": "@flagsmith/backstage-plugin",
3
+ "version": "0.1.0-pr.7.3135fb9",
4
+ "description": "Backstage plugin for Flagsmith feature flag management",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/Flagsmith/flagsmith-backstage-plugin.git"
9
+ },
10
+ "homepage": "https://flagsmith.github.io/flagsmith-backstage-plugin/",
11
+ "bugs": {
12
+ "url": "https://github.com/Flagsmith/flagsmith-backstage-plugin/issues"
13
+ },
14
+ "keywords": [
15
+ "backstage",
16
+ "backstage-plugin",
17
+ "flagsmith",
18
+ "feature-flags",
19
+ "feature-toggles"
20
+ ],
21
+ "main": "dist/index.esm.js",
22
+ "types": "dist/index.d.ts",
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "main": "dist/index.esm.js",
26
+ "types": "dist/index.d.ts"
27
+ },
28
+ "backstage": {
29
+ "role": "frontend-plugin",
30
+ "pluginId": "flagsmith",
31
+ "pluginPackages": [
32
+ "@flagsmith/backstage-plugin"
33
+ ],
34
+ "features": {
35
+ ".": "@backstage/FrontendPlugin"
36
+ }
37
+ },
38
+ "sideEffects": false,
39
+ "scripts": {
40
+ "start": "backstage-cli package start",
41
+ "build": "backstage-cli package build",
42
+ "lint": "backstage-cli package lint",
43
+ "lint:fix": "backstage-cli package lint --fix",
44
+ "test": "backstage-cli package test",
45
+ "clean": "backstage-cli package clean",
46
+ "prepack": "backstage-cli package prepack",
47
+ "postpack": "backstage-cli package postpack",
48
+ "tsc": "tsc || test -f dist-types/src/index.d.ts",
49
+ "build:all": "yarn tsc && backstage-cli package build",
50
+ "prepare": "husky"
51
+ },
52
+ "lint-staged": {
53
+ "*.{ts,tsx}": [
54
+ "backstage-cli package lint --fix"
55
+ ]
56
+ },
57
+ "exports": {
58
+ ".": {
59
+ "backstage": "@backstage/FrontendPlugin",
60
+ "import": "./dist/index.esm.js",
61
+ "types": "./dist/index.d.ts",
62
+ "default": "./dist/index.esm.js"
63
+ },
64
+ "./package.json": "./package.json"
65
+ },
66
+ "dependencies": {
67
+ "@backstage/core-components": "^0.18.2",
68
+ "@backstage/core-plugin-api": "^1.11.1",
69
+ "@backstage/frontend-plugin-api": "^0.13.2",
70
+ "@backstage/plugin-catalog-react": "^1.13.3",
71
+ "@material-ui/core": "^4.9.13",
72
+ "@material-ui/icons": "^4.9.1",
73
+ "recharts": "^2.5.0"
74
+ },
75
+ "peerDependencies": {
76
+ "react": "^16.13.1 || ^17.0.0 || ^18.0.0"
77
+ },
78
+ "devDependencies": {
79
+ "@backstage/catalog-model": "^1.7.6",
80
+ "@backstage/cli": "^0.34.4",
81
+ "@backstage/core-app-api": "^1.19.1",
82
+ "@backstage/dev-utils": "^1.1.15",
83
+ "@backstage/test-utils": "^1.7.12",
84
+ "@testing-library/jest-dom": "^6.0.0",
85
+ "@testing-library/react": "^14.0.0",
86
+ "@testing-library/user-event": "^14.0.0",
87
+ "husky": "^9.1.7",
88
+ "lint-staged": "^16.2.7",
89
+ "msw": "^1.0.0",
90
+ "react": "^18.0.0",
91
+ "react-dom": "^18.0.0",
92
+ "react-router-dom": "^6.0.0"
93
+ },
94
+ "files": [
95
+ "dist"
96
+ ],
97
+ "msw": {
98
+ "workerDirectory": "public"
99
+ },
100
+ "typesVersions": {
101
+ "*": {
102
+ "package.json": [
103
+ "package.json"
104
+ ]
105
+ }
106
+ },
107
+ "module": "./dist/index.esm.js"
108
+ }