Browse Source

adds forceHide option for guides (#33212)

This PR adds a new state called forceHide on the guide store which will prevent guides from being shown. The purpose of this is to let the Sandbox hide any guides while the user is entering their email into the email modal. After the user enters their email, we'll show the guides.
Stephen Cefali 2 years ago
parent
commit
c99aa9f053

+ 4 - 0
static/app/actionCreators/guides.tsx

@@ -26,6 +26,10 @@ export function nextStep() {
   GuideActions.nextStep();
 }
 
+export function setForceHide(forceHide: boolean) {
+  GuideActions.setForceHide(forceHide);
+}
+
 export function toStep(step: number) {
   GuideActions.toStep(step);
 }

+ 1 - 0
static/app/actions/guideActions.tsx

@@ -7,6 +7,7 @@ const GuideActions = createActions([
   'toStep',
   'registerAnchor',
   'unregisterAnchor',
+  'setForceHide',
 ]);
 
 export default GuideActions;

+ 2 - 0
static/app/bootstrap/exportGlobals.tsx

@@ -38,8 +38,10 @@ const SentryApp = {
   },
 
   // The following components are used in legacy django HTML views
+  // or in the Sentry sandbox
   ConfigStore: require('sentry/stores/configStore').default,
   HookStore: require('sentry/stores/hookStore').default,
+  GuideActionCreator: require('sentry/actionCreators/guides'),
   Modal: require('sentry/actionCreators/modal'),
   getModalPortal: require('sentry/utils/getModalPortal').default,
   Client: require('sentry/api').Client,

+ 4 - 2
static/app/components/assistant/guideAnchor.tsx

@@ -82,7 +82,9 @@ class BaseGuideAnchor extends Component<Props, State> {
 
   onGuideStateChange(data: GuideStoreState) {
     const active =
-      data.currentGuide?.steps[data.currentStep]?.target === this.props.target ?? false;
+      (data.currentGuide?.steps[data.currentStep]?.target === this.props.target ??
+        false) &&
+      !data.forceHide;
 
     this.setState({
       active,
@@ -237,7 +239,7 @@ type WrapperProps = React.PropsWithChildren<{disabled?: boolean} & Props>;
  * anchors on the page to determine which guides can be shown on the page.
  */
 function GuideAnchor({disabled, children, ...rest}: WrapperProps) {
-  if (disabled || window.localStorage.getItem('hide_anchors') === '1') {
+  if (disabled) {
     return <Fragment>{children}</Fragment>;
   }
   return <BaseGuideAnchor {...rest}>{children}</BaseGuideAnchor>;

+ 14 - 0
static/app/stores/guideStore.tsx

@@ -36,6 +36,10 @@ export type GuideStoreState = {
    * Current step of the current guide
    */
   currentStep: number;
+  /**
+   * Hides guides that normally would be shown
+   */
+  forceHide: boolean;
   /**
    * We force show a guide if the URL contains #assistant
    */
@@ -59,6 +63,7 @@ export type GuideStoreState = {
 };
 
 const defaultState: GuideStoreState = {
+  forceHide: false,
   guides: [],
   anchors: new Set(),
   currentGuide: null,
@@ -74,6 +79,7 @@ interface GuideStoreDefinition extends StoreDefinition {
 
   onFetchSucceeded(data: GuidesServerData): void;
   onRegisterAnchor(target: string): void;
+  onSetForceHide(forceHide: boolean): void;
   onUnregisterAnchor(target: string): void;
   recordCue(guide: string): void;
   state: GuideStoreState;
@@ -102,6 +108,9 @@ const storeConfig: GuideStoreDefinition = {
     this.unsubscribeListeners.push(
       this.listenTo(GuideActions.unregisterAnchor, this.onUnregisterAnchor)
     );
+    this.unsubscribeListeners.push(
+      this.listenTo(GuideActions.setForceHide, this.onSetForceHide)
+    );
     this.unsubscribeListeners.push(
       this.listenTo(OrganizationsActions.setActive, this.onSetActiveOrganization)
     );
@@ -190,6 +199,11 @@ const storeConfig: GuideStoreDefinition = {
     this.updateCurrentGuide();
   },
 
+  onSetForceHide(forceHide) {
+    this.state.forceHide = forceHide;
+    this.trigger(this.state);
+  },
+
   recordCue(guide) {
     const user = ConfigStore.get('user');
     if (!user) {

+ 16 - 0
tests/js/spec/components/assistant/guideAnchor.spec.jsx

@@ -128,4 +128,20 @@ describe('GuideAnchor', function () {
     expect(screen.queryByTestId('guide-container')).not.toBeInTheDocument();
     expect(screen.getByTestId('child-div')).toBeInTheDocument();
   });
+
+  it('if forceHide is true, do not render guide', async function () {
+    render(
+      <div>
+        <GuideAnchor target="issue_title" />
+        <GuideAnchor target="exception" />
+      </div>
+    );
+
+    GuideActions.fetchSucceeded(serverGuide);
+    expect(await screen.findByText("Let's Get This Over With")).toBeInTheDocument();
+    GuideActions.setForceHide(true);
+    expect(await screen.findByText("Let's Get This Over With")).not.toBeInTheDocument();
+    GuideActions.setForceHide(false);
+    expect(await screen.findByText("Let's Get This Over With")).toBeInTheDocument();
+  });
 });

+ 8 - 0
tests/js/spec/stores/guideStore.spec.jsx

@@ -69,6 +69,14 @@ describe('GuideStore', function () {
     window.location.hash = '';
   });
 
+  it('should force hide', function () {
+    expect(GuideStore.state.forceHide).toEqual(false);
+    GuideStore.onSetForceHide(true);
+    expect(GuideStore.state.forceHide).toEqual(true);
+    GuideStore.onSetForceHide(false);
+    expect(GuideStore.state.forceHide).toEqual(false);
+  });
+
   it('should record analytics events when guide is cued', function () {
     const spy = jest.spyOn(GuideStore, 'recordCue');
     GuideStore.onFetchSucceeded(data);