Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🪟 🎨 Display free tag on release stage pills for users enrolled in the free connectors program #21892

Merged
merged 6 commits into from
Jan 26, 2023

Conversation

ambirdsall
Copy link
Contributor

@ambirdsall ambirdsall commented Jan 25, 2023

What

Displays the free badge on release stage pills if:

  1. the user is enrolled in the free connector program
  2. the release stage is "alpha" or "beta"

Closes #21812.

How, and some additional considerations

  • Defines a new FeatureItem.FreeConnectorProgram flag and adds it to defaultCloudFeatures. As of now, there is no corresponding featureService.FREE_CONNECTOR_PROGRAM flag in LaunchDarkly, since all our dynamic override needs are met by the temporary experiment flag we're using to roll the feature out.
  • Extracts a new, self-contained FreeTag component to encapsulate all interaction with useFreeConnectorProgram, and by extension the cloud-only endpoint that backs it, behind a component boundary. This means OSS builds always get to short-circuit that API via conditionals like { useFeature(FeatureItem.FreeConnectorProgram) && <FreeTag releaseStage={releaseStage} /> }; closes Refactor useFreeConnectorProgram() so it does not fire in OSS #21872
    • Another benefit is to the test code: the initial definition relied a showFreeTag prop, largely as a way to avoid a big, pointless rewrite of some unit test setup code. This pattern dramatically simplified the extra setup needed, making it once again reasonable to centralize the "should the free tag render for this workspace/connector?" logic within the pills.
    • It's defined as a totally self-contained component within the FreeConnectorProgram directory, rather than as a helper component within ReleaseStageBadge.tsx in order to deal with the fact that there are two separate, almost-identical implementations of release stage pills. Obvious possible follow-up refactoring there.

But how does it look

From the connection page title, embedded in a ReleaseStageBadge:
image

From the new source/destination dropdown, embedded in a StageLabel (is there any reason not to just use a ReleaseStageBadge here?):
image

@octavia-squidington-iv octavia-squidington-iv added the area/frontend Related to the Airbyte webapp label Jan 25, 2023
@@ -37,13 +33,10 @@ export const ReleaseStageBadge: React.FC<ReleaseStageBadgeProps> = ({
<div
className={classNames(styles.pill, {
[styles["pill--small"]]: small,
[styles["pill--contains-tag"]]: showFreeTag,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One downside of putting all code shared with OSS on the other side of a component boundary: we no longer have a good way to conditionally change the padding-right from 6px to 2px based on whether a free tag is displayed. Instead, the FreeTag component uses a negative margin-right; that's a hacky solution, though, do let me know if you have a better idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh I don't think the negative margin-right is that bad, but another idea:

{!fcpEnabled && <div
  className={classNames(styles.pill, {
    [styles["pill--small"]]: small
  })}
>
  <FormattedMessage id={`connector.releaseStage.${stage}`} />
</div>}
{fcpEnabled && <FCPReleaseStageBadge stage={stage} />}

Where <FCPReleaseStageBadge> renders the whole pill + conditionally a free tag. It's duplicate code, but I think it's warranted to have a clean split between "feature enabled" and "feature disabled".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like that a bit better, but I'm inclined to save that for later, when consolidating the three implementations of stage pills (there's another copy-paste StageLabel component in cloud/components/experiments/ , used by the sign up page; I left that untouched for now since not-yet-created accounts will never be enrolled). If the overall design of the status pills changes, as things stand, it would be way too easy to overlook one of those.

@ambirdsall ambirdsall force-pushed the alex/display-free-pills branch from 2f87a61 to 06e1f85 Compare January 25, 2023 23:28
@ambirdsall ambirdsall marked this pull request as ready for review January 26, 2023 00:14
Copy link
Contributor

@josephkmh josephkmh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Left a few ideas for improvements, but nothing blocking 👍

@@ -12,7 +12,7 @@ interface IProps {
const Content = styled.div<{ enabled?: boolean }>`
display: flex;
align-items: center;
color: ${({ theme, enabled }) => (!enabled ? theme.greyColor40 : "inheret")};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

@@ -37,13 +33,10 @@ export const ReleaseStageBadge: React.FC<ReleaseStageBadgeProps> = ({
<div
className={classNames(styles.pill, {
[styles["pill--small"]]: small,
[styles["pill--contains-tag"]]: showFreeTag,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh I don't think the negative margin-right is that bad, but another idea:

{!fcpEnabled && <div
  className={classNames(styles.pill, {
    [styles["pill--small"]]: small
  })}
>
  <FormattedMessage id={`connector.releaseStage.${stage}`} />
</div>}
{fcpEnabled && <FCPReleaseStageBadge stage={stage} />}

Where <FCPReleaseStageBadge> renders the whole pill + conditionally a free tag. It's duplicate code, but I think it's warranted to have a clean split between "feature enabled" and "feature disabled".

const { enrollmentStatusQuery } = useFreeConnectorProgram();
const { isEnrolled } = enrollmentStatusQuery.data || {};
const { formatMessage } = useIntl();
const freeStages: Array<ReleaseStage | undefined> = ["alpha", "beta"];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const freeStages: Array<ReleaseStage | undefined> = ["alpha", "beta"];
const freeStages: ReleaseStage[] = ["alpha", "beta"];

I can't imagine why we would want undefined in this array?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type checker is very uncomfortable relying on the fact that (someArray as Array<T>).includes(undefined) === false for all types T, even though it's totally idiomatic js; so the elements of freeStages have to match the type of isEnrolled. Absent type errors, I would have just used an inline array literal (["alpha", "beta"].includes(releaseStage)).

The other reasonable alternative would be to cast isEnrolled, as freeStages.includes(releaseStage as ReleaseStage); I think it's an equivalently good solution for this case, but a general preference for avoiding casts was the tie-breaker (because every time I read one I have to consider the possibility of human error in addition to all the types).

Copy link
Contributor

@josephkmh josephkmh Jan 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I quite follow - the change I suggested passes my TS checker just fine (so does ["alpha", "beta"].includes(releaseStage), for that matter). What error are you seeing there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...none, now 😳. I certainly remember only adding that | undefined in response to some Argument of type ReleaseStage | undefined is not assignable to parameter of type ReleaseStage error, but now it even type-checks if I go back to the inline array of strings I tried initially. I feel like I'm taking crazy pills, but I'll take the cleaner code, I guess.

This is distinct from the LaunchDarkly-backed experiment we're
referencing within `cloud/components/experiments/FreeConnectorProgram`:
it's primary use is to prevent triggering cloud-only API calls in the
OSS build.
It does seem as though we should consolidate on a single release stage
pill implementation! That will have to wait for a follow-up, though.
"...and your little type assertion, too!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/frontend Related to the Airbyte webapp
Projects
None yet
3 participants