Skip to content

Commit 810db4f

Browse files
authored
Merge pull request #819 from jeff-phillips-18/feature-flags
Add ability to turn on feature flags via search param 'devFeatureFlags'
2 parents e21c118 + 08df224 commit 810db4f

File tree

10 files changed

+210
-75
lines changed

10 files changed

+210
-75
lines changed

src/app/contribute/skill/[...slug]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// src/app/contribute/skill/[...slug]/page.tsx
22
import * as React from 'react';
3-
import { AppLayout } from '@/components/AppLayout';
3+
import { AppLayout, FeaturePages } from '@/components/AppLayout';
44
import ViewSkillPage from '@/components/Contribute/Skill/View/ViewSkillPage';
55

66
type PageProps = {
@@ -11,7 +11,7 @@ const SkillViewPage = async ({ params }: PageProps) => {
1111
const resolvedParams = await params;
1212

1313
return (
14-
<AppLayout className="contribute-page">
14+
<AppLayout className="contribute-page" requiredFeature={FeaturePages.Skill}>
1515
<ViewSkillPage branchName={resolvedParams.slug[0]} isDraft={resolvedParams.slug[1] === 'isDraft'} />
1616
</AppLayout>
1717
);

src/app/contribute/skill/edit/[...slug]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// src/app/contribute/skill/edit/[...slug]/page.tsx
22
import * as React from 'react';
3-
import { AppLayout } from '@/components/AppLayout';
3+
import { AppLayout, FeaturePages } from '@/components/AppLayout';
44
import EditSkill from '@/components/Contribute/Skill/Edit/EditSkill';
55

66
type PageProps = {
@@ -11,7 +11,7 @@ const EditSkillPage = async ({ params }: PageProps) => {
1111
const resolvedParams = await params;
1212

1313
return (
14-
<AppLayout className="contribute-page">
14+
<AppLayout className="contribute-page" requiredFeature={FeaturePages.Skill}>
1515
<EditSkill branchName={resolvedParams.slug[0]} isDraft={resolvedParams.slug[1] === 'isDraft'} />
1616
</AppLayout>
1717
);

src/app/contribute/skill/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// src/app/contribute/skill/page.tsx
22
'use client';
33
import React from 'react';
4-
import { AppLayout } from '@/components/AppLayout';
4+
import { AppLayout, FeaturePages } from '@/components/AppLayout';
55
import SkillForm from '@/components/Contribute/Skill/Edit/SkillForm';
66

77
const AddSkillPage: React.FunctionComponent = () => (
8-
<AppLayout className="contribute-page">
8+
<AppLayout className="contribute-page" requiredFeature={FeaturePages.Skill}>
99
<SkillForm />
1010
</AppLayout>
1111
);

src/app/experimental/chat-eval/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33

44
import * as React from 'react';
55
import '@patternfly/react-core/dist/styles/base.css';
6-
import { AppLayout } from '@/components/AppLayout';
6+
import { AppLayout, FeaturePages } from '@/components/AppLayout';
77
import ChatEval from '@/components/Experimental/ChatEval/ChatEval';
88

99
const ChatEvalPage: React.FunctionComponent = () => {
1010
return (
11-
<AppLayout className="chatBotPage">
11+
<AppLayout className="chatBotPage" requiredFeature={FeaturePages.Experimental}>
1212
<ChatEval />
1313
</AppLayout>
1414
);

src/app/experimental/fine-tune/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33

44
import * as React from 'react';
55
import '@patternfly/react-core/dist/styles/base.css';
6-
import { AppLayout } from '@/components/AppLayout';
6+
import { AppLayout, FeaturePages } from '@/components/AppLayout';
77
import FineTuning from '@/components/Experimental/FineTuning';
88

99
const FineTune: React.FunctionComponent = () => {
1010
return (
11-
<AppLayout>
11+
<AppLayout requiredFeature={FeaturePages.Experimental}>
1212
<FineTuning />
1313
</AppLayout>
1414
);

src/app/playground/chat/page.tsx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,34 @@
22
'use client';
33

44
import React from 'react';
5-
import { PageSection, Content } from '@patternfly/react-core/';
6-
import { AppLayout } from '@/components/AppLayout';
5+
import { Content, PageSection } from '@patternfly/react-core/';
6+
import { AppLayout, FeaturePages } from '@/components/AppLayout';
77
import ChatPageSidePanelHelp from '@/components/SidePanelContents/ChatPageSidePanelHelp';
88
import PageDescriptionWithHelp from '@/components/Common/PageDescriptionWithHelp';
99
import ModelsContextProvider from '../../../components/Chat/ModelsContext';
1010
import ChatBotContainer from '../../../components/Chat/ChatBotContainer';
1111

1212
import './chatbotPage.css';
1313

14-
const ChatPage: React.FC = () => (
15-
<AppLayout className="chatBotPage">
16-
<PageSection>
17-
<Content component="h1">Chat with a model</Content>
18-
<PageDescriptionWithHelp
19-
description={`Before you start adding new skills and knowledge to your model, you can check its baseline
14+
const ChatPage: React.FC = () => {
15+
return (
16+
<AppLayout className="chatBotPage" requiredFeature={FeaturePages.Playground}>
17+
<PageSection>
18+
<Content component="h1">Chat with a model</Content>
19+
<PageDescriptionWithHelp
20+
description={`Before you start adding new skills and knowledge to your model, you can check its baseline
2021
performance by chatting with a language model that's hosted on your cloud. Choose a supported model
2122
from the model selector or configure your own custom endpoints to gain direct access
2223
to a specific model you've trained.`}
23-
helpText="Learn more about chatting with models"
24-
sidePanelContent={<ChatPageSidePanelHelp />}
25-
/>
26-
</PageSection>
27-
<ModelsContextProvider>
28-
<ChatBotContainer />
29-
</ModelsContextProvider>
30-
</AppLayout>
31-
);
24+
helpText="Learn more about chatting with models"
25+
sidePanelContent={<ChatPageSidePanelHelp />}
26+
/>
27+
</PageSection>
28+
<ModelsContextProvider>
29+
<ChatBotContainer />
30+
</ModelsContextProvider>
31+
</AppLayout>
32+
);
33+
};
3234

3335
export default ChatPage;

src/app/playground/endpoints/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React from 'react';
44
import { v4 as uuidv4 } from 'uuid';
55
import {
66
Button,
7+
ClipboardCopy,
78
Content,
89
DataList,
910
DataListAction,
@@ -14,13 +15,12 @@ import {
1415
Flex,
1516
FlexItem,
1617
PageSection,
17-
Truncate,
18-
ClipboardCopy
18+
Truncate
1919
} from '@patternfly/react-core';
20-
import { BanIcon, CheckCircleIcon, EyeSlashIcon, EyeIcon, QuestionCircleIcon } from '@patternfly/react-icons';
20+
import { BanIcon, CheckCircleIcon, EyeIcon, EyeSlashIcon, QuestionCircleIcon } from '@patternfly/react-icons';
2121
import { Endpoint, ModelEndpointStatus } from '@/types';
2222
import { fetchEndpointStatus } from '@/services/modelService';
23-
import { AppLayout } from '@/components/AppLayout';
23+
import { AppLayout, FeaturePages } from '@/components/AppLayout';
2424
import CustomEndpointsSidePanelHelp from '@/components/SidePanelContents/CustomEndpointsSidePanelHelp';
2525
import EditEndpointModal from '@/app/playground/endpoints/EditEndpointModal';
2626
import DeleteEndpointModal from '@/app/playground/endpoints/DeleteEndpoinModal';
@@ -188,7 +188,7 @@ const EndpointsPage: React.FC = () => {
188188
};
189189

190190
return (
191-
<AppLayout>
191+
<AppLayout requiredFeature={FeaturePages.Playground}>
192192
<PageSection>
193193
<Flex justifyContent={{ default: 'justifyContentSpaceBetween' }} gap={{ default: 'gapMd' }}>
194194
<FlexItem>

src/components/AppLayout.tsx

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,22 @@ import { useFeatureFlags } from '@/context/FeatureFlagsContext';
3838
import { useAlerts } from '@/context/AlertContext';
3939
import { useSideDrawer } from '@/context/SideDrawerContext';
4040
import ThemePreference from '@/components/ThemePreference/ThemePreference';
41+
import DevFlagsBanner from '@/components/Banner/DevFlagsBanner';
4142
import HelpDropdown from './HelpDropdown/HelpDropdown';
4243
import UserMenu from './UserMenu/UserMenu';
4344

4445
import '../styles/globals.scss';
4546

47+
export enum FeaturePages {
48+
Skill = 'Skill',
49+
Playground = 'Playground',
50+
Experimental = 'Experimental'
51+
}
52+
4653
interface IAppLayout {
4754
children: React.ReactNode;
4855
className?: string;
56+
requiredFeature?: FeaturePages;
4957
}
5058

5159
type Route = {
@@ -59,25 +67,57 @@ const isRouteActive = (pathname: string, route: Route) => {
5967
return pathname.startsWith(route.path) || route.altPaths?.some((altPath) => pathname.startsWith(altPath));
6068
};
6169

62-
const AppLayout: React.FunctionComponent<IAppLayout> = ({ children, className }) => {
70+
const AppLayout: React.FunctionComponent<IAppLayout> = ({ children, className, requiredFeature }) => {
71+
const router = useRouter();
72+
const pathname = usePathname();
6373
const { data: session, status } = useSession();
6474
const {
6575
loaded,
66-
featureFlags: { playgroundFeaturesEnabled, experimentalFeaturesEnabled }
76+
featureFlags: { playgroundFeaturesEnabled, experimentalFeaturesEnabled, skillFeaturesEnabled }
6777
} = useFeatureFlags();
6878
const { alerts, removeAlert } = useAlerts();
6979
const sideDrawerContext = useSideDrawer();
7080

71-
const router = useRouter();
72-
const pathname = usePathname();
73-
7481
React.useEffect(() => {
7582
if (status === 'loading') return; // Do nothing while loading
7683
if (!session && pathname !== '/login') {
7784
router.push('/login'); // Redirect if not authenticated and not already on login page
7885
}
7986
}, [session, status, pathname, router]);
8087

88+
const routes = React.useMemo(
89+
() =>
90+
[
91+
{ path: '/dashboard', altPaths: ['/contribute'], label: 'My contributions' },
92+
{ path: '/documents', label: 'Documents' },
93+
...(playgroundFeaturesEnabled
94+
? [
95+
{
96+
path: '/playground',
97+
label: 'Playground',
98+
children: [
99+
{ path: '/playground/chat', label: 'Chat with a model' },
100+
{ path: '/playground/endpoints', label: 'Custom model endpoints' }
101+
]
102+
}
103+
]
104+
: []),
105+
...(experimentalFeaturesEnabled
106+
? [
107+
{
108+
path: '/experimental',
109+
label: 'Experimental features',
110+
children: [
111+
{ path: '/experimental/fine-tune', label: 'Fine-tuning' },
112+
{ path: '/experimental/chat-eval', label: 'Model chat eval' }
113+
]
114+
}
115+
]
116+
: [])
117+
].filter(Boolean) as Route[],
118+
[experimentalFeaturesEnabled, playgroundFeaturesEnabled]
119+
);
120+
81121
if (!loaded || status === 'loading') {
82122
return (
83123
<Bullseye>
@@ -90,34 +130,17 @@ const AppLayout: React.FunctionComponent<IAppLayout> = ({ children, className })
90130
return null; // Return nothing if not authenticated to avoid flicker
91131
}
92132

93-
const routes = [
94-
{ path: '/dashboard', altPaths: ['/contribute'], label: 'My contributions' },
95-
{ path: '/documents', label: 'Documents' },
96-
...(playgroundFeaturesEnabled
97-
? [
98-
{
99-
path: '/playground',
100-
label: 'Playground',
101-
children: [
102-
{ path: '/playground/chat', label: 'Chat with a model' },
103-
{ path: '/playground/endpoints', label: 'Custom model endpoints' }
104-
]
105-
}
106-
]
107-
: []),
108-
...(experimentalFeaturesEnabled
109-
? [
110-
{
111-
path: '/experimental',
112-
label: 'Experimental features',
113-
children: [
114-
{ path: '/experimental/fine-tune', label: 'Fine-tuning' },
115-
{ path: '/experimental/chat-eval', label: 'Model chat eval' }
116-
]
117-
}
118-
]
119-
: [])
120-
].filter(Boolean) as Route[];
133+
if (requiredFeature) {
134+
const featureEnabled =
135+
(requiredFeature === FeaturePages.Playground && playgroundFeaturesEnabled) ||
136+
(requiredFeature === FeaturePages.Experimental && experimentalFeaturesEnabled) ||
137+
(requiredFeature === FeaturePages.Skill && skillFeaturesEnabled);
138+
139+
if (!featureEnabled) {
140+
router.push('/404');
141+
return null;
142+
}
143+
}
121144

122145
const Header = (
123146
<Masthead>
@@ -200,6 +223,7 @@ const AppLayout: React.FunctionComponent<IAppLayout> = ({ children, className })
200223
skipToContent={PageSkipToContent}
201224
isContentFilled
202225
>
226+
<DevFlagsBanner />
203227
{children}
204228
<AlertGroup isToast isLiveRegion>
205229
{alerts.map((alert) => (
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// src/components/AppLayout.tsx
2+
'use client';
3+
4+
import * as React from 'react';
5+
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
6+
import { Banner, Flex, FlexItem, Button } from '@patternfly/react-core';
7+
import { useFeatureFlags } from '@/context/FeatureFlagsContext';
8+
9+
const FEATURE_FLAG_PARAM = 'devFeatureFlags';
10+
11+
const DevFlagsBanner: React.FC = () => {
12+
const router = useRouter();
13+
const pathname = usePathname();
14+
const searchParams = useSearchParams();
15+
const { devFeatureFlagsEnabled, setDevFeatureFlagsEnabled } = useFeatureFlags();
16+
17+
React.useEffect(() => {
18+
const featureFlagParam = searchParams.get(FEATURE_FLAG_PARAM);
19+
if (featureFlagParam !== null) {
20+
setDevFeatureFlagsEnabled(true);
21+
}
22+
}, [searchParams, setDevFeatureFlagsEnabled]);
23+
24+
const disableFeatureFlags = () => {
25+
setDevFeatureFlagsEnabled(false);
26+
27+
const params = new URLSearchParams(searchParams.toString());
28+
params.delete(FEATURE_FLAG_PARAM);
29+
router.push(pathname + '?' + params.toString());
30+
};
31+
32+
if (!devFeatureFlagsEnabled) {
33+
return null;
34+
}
35+
return (
36+
<Banner color="blue">
37+
<Flex justifyContent={{ default: 'justifyContentSpaceAround' }}>
38+
<FlexItem>
39+
Feature flags are overridden in the current session.{' '}
40+
<Button variant="link" isInline onClick={disableFeatureFlags}>
41+
Click here
42+
</Button>{' '}
43+
to reset back to defaults.
44+
</FlexItem>
45+
</Flex>
46+
</Banner>
47+
);
48+
};
49+
50+
export default DevFlagsBanner;

0 commit comments

Comments
 (0)