question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[ACI post-project connection] Implement consolidated logic for “Cypress-triggered events” like displaying the CI prompt, smart banners, and post-login messages.

See original GitHub issue

Context This Whimsical diagram(only visible to internal Cy employees) gathers together the logic that has evolved to manage smart banners, the old CI prompt, LoginConnect modal state, as well as the state of various buttons in the Top Nav, Specs List Page, Settings Page that allow users to log in and/or connect a project to he Dashboard.

The diagram represents User-Triggered Actions with solid arrows between states, and Cypress-Triggered Actions with dotted arrows. Cypress-triggered actions like displaying a notification are intended to only happen when certain specific rules have been met, and also shouldn’t happen close together, so as to avoid becoming annoying, and increase the chance that they are useful.

Currently the logic around these things has been implemented similarly in more than one place, and it’s time to consolidate all of it so that we can easily add new features that depend on this logic.

Requested Change

A solution is needed that gathers all of the logic from that Whimsical into a single system whose overall purpose is to

  1. determine if Cypress has permission to do something, and
  2. provide a reactive single source of truth to define which of the small number of possible states a user is currently in.

For example:

SpecsListBanners.vue currently uses a watcher to set various flags as the user logs in and a project gets setup, so it can determine which banner variant should be shown. It includes logic for “whether the user state is correct” (has a project? etc) to show a certain banner, and “whether Cypress is allowed” to show the banner (has more than 4 days of Cypress use?). Using the new solution, when SpecsListBanner mounts it would check which of the N possible states a user is in (which is provided from a store or similar) and if that state maps to a variant of the banner, it would call a function that checks if it has permission to open. After opening and after being dismissed, it would make the appropriate calls to this helper to record the updated date and keep everything in sync.

The “what state is the user in” question is partially solved by the login-connect-store introduced in #23762 and the related CloudViewerAndProject component that provides state via a scoped slot. But these are essentially stepping stones to a more holistic system. That branch has not been merged. It could be a starting point for this work, or just a reference point to start fresh.

The “whether Cypress is allowed” to do something question is not centrally managed at the moment. It is about to get a little more complex as now there is a 1-day delay being introduced.

Rationale for doing this One reason to gather this logic centrally is to have a smoother developer experience using these kinds of values and figuring out if we can display something to the user in an increasingly complicated set of circumstances. It should be clear how to quickly “add a button” anywhere and tell it how to behave in the context of the LoginConnect state, without having to recreate the logic to derive that state. This should make us more nimble with future requests.

Another developer reason to extract this stuff is that it has nothing to do with Vue, this business logic can and should be unit tested on its own and independent of a UI framework

There’s a third reason to pause and do this now: to get aligned with Product in terms of how we are naming the states users can be in, and how we define the rules for Cypress-triggered actions. The less translation we need when we talk about these things and implementing them, the better. Chris France is already using state charts to ferret out all the allowed states of these flows for figma and prototype reasons. While I’m not suggesting we get a full state machine pattern going here, we should be thinking in that spirit, and especially we should go out of our way to reject impossible states, which might lead to showing the banner and the CI prompt at the same time, or something like that. I’d expect long term this kind of thing might evolve towards a state machine.

For now a clear and human readable expression of the rules, plus some validation, is likely good enough (see suggested implementation below).

Acceptance Criteria

  • user state like const isProjectConnected = !!currentProject?.projectId && currentProject.cloudProject?.__typename === 'CloudProject' is computed in one place and accessible to components that need it
  • cypress-triggered actions check if they have permission to happen, and record when they have happened
  • business logic related to the states and transitions in Whimsical is encapsulated in plain TS helpers that are unit tested, and the unit tests describe all of the expected scenarios
  • logic from the Whimsical (like a 1-day delay) is implemented in the helper code and tested, but not wired up in the app yet, let’s merge the helper, then do the wiring in #23768, #23767, and #23766.

Suggested implementation (for estimation purposes)

This could be done in many ways, I don’t think this ticket needs to be too prescriptive as to the implementation. Though it could be broken down further into multiple steps.

I do have an pseudocode example of how the code that manages this in a common language with Product might look, which would let us easily change rules or add new rules. This of course implies a set of functions and validators that aren’t shown that would actually parse objects like this and apply the rules.

Psuedocode for how we might manage the rules in a human readable way
const userStates = {  
// this names the 4 states defined in whimsical and expresses the rules in a human readable way
// a validator function should be used, and throw if 2 states have the same set of conditions
// we can only be in one of these at a time, ever
  loggedOut: {
    matches: ['notLoggedIn']
  },
  needsOrg: {
    matches: ['isLoggedIn', 'hasNoOrgs']
   }, 
  needsConnectedProject: {
    matches: [
      'isLoggedIn', 
      'hasAtLeastOneOrg', 
      'hasNoConnectedProject'
    ]
   }, 
  needsRecordedRun : {
    matches: [
      'isLoggedIn', 
      'hasAtLeastOneOrg',
      'hasConnectedProject', 
      'hasNoRecordedRuns', 
      minSpecsInCurrentTestingType(4)
    ]
   }  
}

const features = {
  smartBanner: { // feature - where we are in the app
    cypressTriggerRules: [ // conditions that must be true for Cypress to trigger this feature to open
      minDaysSince('firstOpen', 4), 
      minDaysSince('docsAutoOpen', 1),
      minDaysSince('connectProject', 1),
      minDaysSince('recordRunShown', 1),
      isInState(['loggedOut', 'needsOrg', 'needsConnectedProject', 'needsRecordedRun'])
      // needs a validator that the rules do not conflict
      // or can nest rules in paths to prevent impossible states
    ],
    content: { // content - customized per cohort - can live in existing i18n json
      loggedOut: { // user state - derived from gql
          a: 'Log in',
          b: 'Log in, please',
          default: 'Log in' // no cohort
      },
      needsOrg: {
          a: 'Do an org now',
          b: 'Make an org pls',
          default: 'Make org'
      }
    }
  },
  loginConnectModal: {
    // ...
  }
}

Issue Analytics

  • State:closed
  • Created a year ago
  • Reactions:2
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
marktnoonancommented, Sep 13, 2022

Here’s a rough loom showing, based on our current code, what logic would move into the new system. It feels more lightweight to me as an extension of the work that’s already been done than starting from scratch: https://www.loom.com/share/f3c8e32352b24300a5b4aaaa67ed155b

1reaction
lmiller1990commented, Sep 13, 2022

TL;DR you hit the nail on the head at the end: we need a (kind of) state machine to manage complexity. I 100% agree this should be decoupled from the UI layer. I wonder if XState is a good fit (probably not? steep learning curve, complicated) but it has Vue bindings: https://xstate.js.org/docs/recipes/vue.html. If we end up re-inventing a state machine anyway, maybe it would be a good fit, but we’d probably need to rejig things a bit to make it fit.

I think this ticket has a lot of thinking and planning involved. My estimate reflects that - I expect the engineer to spend the majority of development time thinking/basic prototyping, as opposed to just going with the first thing they come up with. Looping someone in early to bounce ideas might be a good move, too.

Read more comments on GitHub >

github_iconTop Results From Across the Web

No results found

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found