Browse Source

feat(ui): Add metrics switch to performance page [INGEST-572] (#30011)

Matej Minar 3 years ago
parent
commit
75aee25352

+ 3 - 1
static/app/views/performance/index.tsx

@@ -7,6 +7,8 @@ import {PageContent} from 'app/styles/organization';
 import {Organization} from 'app/types';
 import withOrganization from 'app/utils/withOrganization';
 
+import {MetricsSwitchContextContainer} from './metricsSwitch';
+
 type Props = {
   organization: Organization;
 };
@@ -30,7 +32,7 @@ class PerformanceContainer extends Component<Props> {
         organization={organization}
         renderDisabled={this.renderNoAccess}
       >
-        {children}
+        <MetricsSwitchContextContainer>{children}</MetricsSwitchContextContainer>
       </Feature>
     );
   }

+ 12 - 7
static/app/views/performance/landing/index.tsx

@@ -3,6 +3,7 @@ import styled from '@emotion/styled';
 import {Location} from 'history';
 
 import Button from 'app/components/button';
+import ButtonBar from 'app/components/buttonBar';
 import SearchBar from 'app/components/events/searchBar';
 import GlobalSdkUpdateAlert from 'app/components/globalSdkUpdateAlert';
 import * as Layout from 'app/components/layouts/thirds';
@@ -19,6 +20,7 @@ import {generateAggregateFields} from 'app/utils/discover/fields';
 import {OpBreakdownFilterProvider} from 'app/utils/performance/contexts/operationBreakdownFilter';
 import useTeams from 'app/utils/useTeams';
 
+import {MetricsSwitch} from '../metricsSwitch';
 import Filter, {SpanOperationBreakdownFilter} from '../transactionSummary/filter';
 import {getTransactionSearchQuery} from '../utils';
 
@@ -86,13 +88,16 @@ export function PerformanceLanding(props: Props) {
         </Layout.HeaderContent>
         <Layout.HeaderActions>
           {!showOnboarding && (
-            <Button
-              priority="primary"
-              data-test-id="landing-header-trends"
-              onClick={() => handleTrendsClick()}
-            >
-              {t('View Trends')}
-            </Button>
+            <ButtonBar gap={3}>
+              <MetricsSwitch />
+              <Button
+                priority="primary"
+                data-test-id="landing-header-trends"
+                onClick={() => handleTrendsClick()}
+              >
+                {t('View Trends')}
+              </Button>
+            </ButtonBar>
           )}
         </Layout.HeaderActions>
 

+ 7 - 0
static/app/views/performance/landing/widgets/components/widgetContainer.tsx

@@ -2,6 +2,7 @@ import {useEffect, useState} from 'react';
 import styled from '@emotion/styled';
 
 import MenuItem from 'app/components/menuItem';
+import {t} from 'app/locale';
 import {Organization} from 'app/types';
 import trackAdvancedAnalyticsEvent from 'app/utils/analytics/trackAdvancedAnalyticsEvent';
 import localStorage from 'app/utils/localStorage';
@@ -9,6 +10,7 @@ import {usePerformanceDisplayType} from 'app/utils/performance/contexts/performa
 import useOrganization from 'app/utils/useOrganization';
 import withOrganization from 'app/utils/withOrganization';
 import ContextMenu from 'app/views/dashboardsV2/contextMenu';
+import {useMetricsSwitch} from 'app/views/performance/metricsSwitch';
 import {PROJECT_PERFORMANCE_TYPE} from 'app/views/performance/utils';
 
 import {GenericPerformanceWidgetDataType} from '../types';
@@ -102,6 +104,7 @@ function trackChartSettingChange(
 
 const _WidgetContainer = (props: Props) => {
   const {organization, index, chartHeight, allowedCharts, ...rest} = props;
+  const {isMetricsData} = useMetricsSwitch();
   const performanceType = usePerformanceDisplayType();
   let _chartSetting = getChartSetting(
     index,
@@ -148,6 +151,10 @@ const _WidgetContainer = (props: Props) => {
     ),
   };
 
+  if (isMetricsData) {
+    return <h1>{t('Using metrics')}</h1>;
+  }
+
   switch (widgetProps.dataType) {
     case GenericPerformanceWidgetDataType.trends:
       return <TrendsWidget {...props} {...widgetProps} />;

+ 79 - 0
static/app/views/performance/metricsSwitch.tsx

@@ -0,0 +1,79 @@
+import {createContext, ReactNode, useContext, useState} from 'react';
+import styled from '@emotion/styled';
+
+import Feature from 'app/components/acl/feature';
+import Switch from 'app/components/switchButton';
+import {t} from 'app/locale';
+import space from 'app/styles/space';
+import localStorage from 'app/utils/localStorage';
+import useOrganization from 'app/utils/useOrganization';
+
+const FEATURE_FLAG = 'metrics-performance-ui';
+
+/**
+ * This is a temporary component used for debugging metrics data on performance pages.
+ * Visible only to small amount of internal users.
+ */
+function MetricsSwitch() {
+  const organization = useOrganization();
+  const {isMetricsData, setIsMetricsData} = useMetricsSwitch();
+
+  return (
+    <Feature features={[FEATURE_FLAG]} organization={organization}>
+      <Label>
+        {t('Metrics Data')}
+        <Switch
+          isActive={isMetricsData}
+          toggle={() => setIsMetricsData(!isMetricsData)}
+          size="lg"
+        />
+      </Label>
+    </Feature>
+  );
+}
+
+const Label = styled('label')`
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 0;
+  gap: ${space(1)};
+  font-weight: normal;
+`;
+
+const MetricsSwitchContext = createContext({
+  isMetricsData: false,
+  setIsMetricsData: (_isMetricsData: boolean) => {},
+});
+
+function MetricsSwitchContextContainer({children}: {children: ReactNode}) {
+  const organization = useOrganization();
+  const localStorageKey = `metrics-performance:${organization.slug}`;
+  const [isMetricsData, setIsMetricsData] = useState(
+    localStorage.getItem(localStorageKey) === 'true'
+  );
+
+  function handleSetIsMetricsData(value: boolean) {
+    localStorage.setItem(localStorageKey, value.toString());
+    setIsMetricsData(value);
+  }
+
+  return (
+    <MetricsSwitchContext.Provider
+      value={{
+        isMetricsData: isMetricsData && organization.features.includes(FEATURE_FLAG),
+        setIsMetricsData: handleSetIsMetricsData,
+      }}
+    >
+      {children}
+    </MetricsSwitchContext.Provider>
+  );
+}
+
+function useMetricsSwitch() {
+  const contextValue = useContext(MetricsSwitchContext);
+
+  return contextValue;
+}
+
+export {MetricsSwitch, MetricsSwitchContextContainer, useMetricsSwitch};

+ 52 - 0
tests/js/spec/views/performance/metricsSwitch.spec.tsx

@@ -0,0 +1,52 @@
+import React from 'react';
+
+import {mountWithTheme, screen, userEvent} from 'sentry-test/reactTestingLibrary';
+
+import {
+  MetricsSwitch,
+  MetricsSwitchContextContainer,
+  useMetricsSwitch,
+} from 'app/views/performance/metricsSwitch';
+
+function TestComponent() {
+  const {isMetricsData} = useMetricsSwitch();
+  return (
+    <React.Fragment>
+      <MetricsSwitch />
+      {isMetricsData ? 'using metrics' : 'using transactions'}
+    </React.Fragment>
+  );
+}
+
+describe('MetricsSwitch', () => {
+  it('MetricsSwitchContextContainer renders children', () => {
+    mountWithTheme(<MetricsSwitchContextContainer>abc</MetricsSwitchContextContainer>, {
+      organization: TestStubs.Organization(),
+    });
+    expect(screen.getByText('abc')).toBeInTheDocument();
+  });
+
+  it('MetricsSwitch is not visible to users without feature flag', () => {
+    const {container} = mountWithTheme(<MetricsSwitch />, {
+      organization: TestStubs.Organization(),
+    });
+    expect(container).toBeEmptyDOMElement();
+  });
+
+  it('toggles between transactions and metrics', () => {
+    mountWithTheme(
+      <MetricsSwitchContextContainer>
+        <TestComponent />
+      </MetricsSwitchContextContainer>,
+      {
+        organization: TestStubs.Organization({features: ['metrics-performance-ui']}),
+      }
+    );
+
+    expect(screen.getByText('using transactions')).toBeInTheDocument();
+
+    userEvent.click(screen.getByRole('checkbox'));
+
+    expect(screen.getByText('using metrics')).toBeInTheDocument();
+  });
+});