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

[Issue #2695] opportunity and search toggle with feature flag env vars #3115

Merged
merged 16 commits into from
Dec 11, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
not working! search params not coming through for opportunity page
doug-s-nava committed Dec 11, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit d9d0680471dbc303493e5ed9ac18b7a77d9a2d63
6 changes: 5 additions & 1 deletion frontend/.env.production
Original file line number Diff line number Diff line change
@@ -11,4 +11,8 @@ SENDY_API_KEY=
SENDY_API_URL=
SENDY_LIST_ID=

API_URL=http://api.simpler.grants.gov
API_URL=http://localhost:8080

# Hardcode public auth key for local to local calls
# This is also hardcoded and checked-in on the API
API_AUTH_TOKEN=LOCAL_AUTH_12345678
11 changes: 8 additions & 3 deletions frontend/src/app/[locale]/opportunity/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -3,8 +3,9 @@ import NotFound from "src/app/[locale]/not-found";
import { fetchOpportunity } from "src/app/api/fetchers";
import { OPPORTUNITY_CRUMBS } from "src/constants/breadcrumbs";
import { ApiRequestError, parseErrorStatus } from "src/errors";
import withFeatureFlagStatic from "src/hoc/search/withFeatureFlagStatic";
import withFeatureFlag from "src/hoc/search/withFeatureFlag";
import { Opportunity } from "src/types/opportunity/opportunityResponseTypes";
import { WithFeatureFlagProps } from "src/types/uiTypes";

import { getTranslations } from "next-intl/server";
import { notFound, redirect } from "next/navigation";
@@ -21,7 +22,9 @@ import OpportunityIntro from "src/components/opportunity/OpportunityIntro";
import OpportunityLink from "src/components/opportunity/OpportunityLink";
import OpportunityStatusWidget from "src/components/opportunity/OpportunityStatusWidget";

type OpportunityListingProps = { params: { id: string } };
type OpportunityListingProps = {
params: { id: string };
} & WithFeatureFlagProps;

export const revalidate = 600; // invalidate ten minutes

@@ -46,6 +49,8 @@ export async function generateMetadata({ params }: { params: { id: string } }) {
return meta;
}

export const dynamic = "force-static";

export function generateStaticParams() {
return [];
}
@@ -148,7 +153,7 @@ async function OpportunityListing({ params }: OpportunityListingProps) {
);
}

export default withFeatureFlagStatic<OpportunityListingProps, never>(
export default withFeatureFlag<OpportunityListingProps, never>(
OpportunityListing,
"opportunityOff",
() => redirect("/maintenance"),
3 changes: 2 additions & 1 deletion frontend/src/app/[locale]/search/page.tsx
Original file line number Diff line number Diff line change
@@ -79,7 +79,8 @@ function Search({ searchParams }: SearchPageProps) {

// Exports page behind feature flags
export default withFeatureFlag<SearchPageProps, never>(
withFeatureFlag(Search, "hideSearchV0", notFound),
// withFeatureFlag(Search, "hideSearchV0", notFound),
Search,
"searchOff",
() => redirect("/maintenance"),
);
2 changes: 2 additions & 0 deletions frontend/src/constants/environments.ts
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ const {
NEXT_PUBLIC_BASE_URL,
FEATURE_SEARCH_OFF = "false",
FEATURE_OPPORTUNITY_OFF = "false",
NEXT_BUILD = "false",
} = process.env;

// home for all interpreted server side environment variables
@@ -29,4 +30,5 @@ export const environment: { [key: string]: string } = {
GOOGLE_TAG_MANAGER_ID: "GTM-MV57HMHS",
FEATURE_OPPORTUNITY_OFF,
FEATURE_SEARCH_OFF,
NEXT_BUILD,
};
2 changes: 2 additions & 0 deletions frontend/src/constants/featureFlags.ts
Original file line number Diff line number Diff line change
@@ -8,3 +8,5 @@ export const featureFlags: FeatureFlags = {
searchOff: false,
opportunityOff: false,
};

// http://localhost:3000/es/opportunity/33?_ff=opportunityOff%3Afalse
54 changes: 44 additions & 10 deletions frontend/src/hoc/search/withFeatureFlag.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,60 @@
import { environment } from "src/constants/environments";
import { FeatureFlagsManager } from "src/services/FeatureFlagManager";
import { WithFeatureFlagProps } from "src/types/uiTypes";

import { cookies } from "next/headers";
import React, { ComponentType } from "react";

const withFeatureFlag = <P extends WithFeatureFlagProps, R>(
// const withSearchParams = <P extends WithFeatureFlagProps, R>(
// WrappedComponent: ComponentType<P>,
// featureFlagName: string,
// onEnabled: () => R,
// ) => ({ searchParams }: WithFeatureFlagProps) {
// const featureFlagsManager = new FeatureFlagsManager(cookies());

// if (featureFlagsManager.isFeatureEnabled(featureFlagName, searchParams)) {
// return onEnabled();
// }

// return <WrappedComponent {...props} />;
// }

// since this relies on search params coming in as a prop, this can only be used on a top level page component
// for other components we'll need a different implementation
const withFeatureFlag = <P, R>(
WrappedComponent: ComponentType<P>,
featureFlagName: string,
onEnabled: () => R,
) => {
const ComponentWithFeatureFlag = (props: P) => {
const featureFlagsManager = new FeatureFlagsManager(cookies());
const { searchParams } = props;
// if we're in the middle of a build, that means this is an ssg rendering pass.
// in that case we can skip this feature flag business
if (environment.NEXT_BUILD === "true") {
return WrappedComponent;
}

if (featureFlagsManager.isFeatureEnabled(featureFlagName, searchParams)) {
return onEnabled();
}
// the problem is not that this returns a function
// i guess it wants the function it returns to return an element

return <WrappedComponent {...props} />;
};
// top level component to grab search params from the top level page props
// return (props: P & WithFeatureFlagProps) => {
// const searchParams = props.searchParams || {};
// // wrap the flagged component to close over search params and accept other props as normal
// const ComponentWithFeatureFlag = (props: P & WithFeatureFlagProps) => {
// const searchParams = props.searchParams || {};
// const featureFlagsManager = new FeatureFlagsManager(cookies());

return ComponentWithFeatureFlag;
// if (featureFlagsManager.isFeatureEnabled(featureFlagName, searchParams)) {
// return onEnabled();
// }

// return <WrappedComponent {...props} />;
// };
// return <ComponentWithFeatureFlag {...props} searchParams={searchParams} />;
// };

return (props) => {
return <div>hi</div>;
};
};

export default withFeatureFlag;
51 changes: 30 additions & 21 deletions frontend/src/hoc/search/withFeatureFlagStatic.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
import { FeatureFlagsManager } from "src/services/FeatureFlagManager";
import { ServerSideSearchParams } from "src/types/searchRequestURLTypes";
// import { environment } from "src/constants/environments";
// import { FeatureFlagsManager } from "src/services/FeatureFlagManager";
// import { ServerSideSearchParams } from "src/types/searchRequestURLTypes";

import React, { ComponentType } from "react";
// import React, { ComponentType } from "react";

export const withFeatureFlagStatic = <
P extends { params: ServerSideSearchParams },
R,
>(
WrappedComponent: ComponentType<P>,
featureFlagName: string,
onEnabled: () => R,
) => {
const ComponentWithFeatureFlag = (props: P) => {
const featureFlagsManager = new FeatureFlagsManager();
// export const withFeatureFlagStatic = <
// P extends { params: ServerSideSearchParams },
// R,
// >(
// WrappedComponent: ComponentType<P>,
// featureFlagName: string,
// onEnabled: () => R,
// ) => {
// // eslint-disable-next-line
// console.log("### flag render", featureFlagName, environment.NEXT_BUILD);

if (featureFlagsManager.isFeatureEnabled(featureFlagName)) {
return onEnabled();
}
// // ok we can switch off of the flag name (if we set up a config for non-ssg flags)
// // but still need a way to know that we're in SSG
// if (environment.NEXT_BUILD) {
// return WrappedComponent;
// }
// const ComponentWithFeatureFlag = (props: P) => {
// const featureFlagsManager = new FeatureFlagsManager();

return <WrappedComponent {...props} />;
};
// if (featureFlagsManager.isFeatureEnabled(featureFlagName)) {
// return onEnabled();
// }

return ComponentWithFeatureFlag;
};
// return <WrappedComponent {...props} />;
// };

export default withFeatureFlagStatic;
// return ComponentWithFeatureFlag;
// };

// export default withFeatureFlagStatic;
15 changes: 3 additions & 12 deletions frontend/src/services/FeatureFlagManager.ts
Original file line number Diff line number Diff line change
@@ -130,18 +130,6 @@ export class FeatureFlagsManager {
};
}

/**
* Check whether a feature flag is disabled
* @param name - Feature flag name
* @example isFeatureEnabled("featureFlagName")
*/
isFeatureDisabled(
name: string,
searchParams?: ServerSideSearchParams,
): boolean {
return !this.isFeatureEnabled(name, searchParams);
}

/**
* Check whether a feature flag is enabled
* @param name - Feature flag name
@@ -167,6 +155,9 @@ export class FeatureFlagsManager {
featureFlagBoolean = featureFlagsObject[name];
}

// eslint-disable-next-line
console.log("$$$ is feature enabled", name, featureFlagBoolean);

return featureFlagBoolean;
}