From 862899b45a8ad337d781aac63bd609e31b9fb241 Mon Sep 17 00:00:00 2001 From: Drew Volz Date: Sat, 30 Nov 2024 12:45:16 -0800 Subject: [PATCH] remove app-config and feature flag implementation --- .../useCourseSearchRecentsScreen.test.tsx | 70 ----------- modules/app-config/index.ts | 40 ------ modules/app-config/package.json | 16 --- modules/app-config/types.ts | 19 --- package-lock.json | 13 -- source/lib/storage.ts | 16 --- source/navigation/routes.tsx | 5 - source/navigation/types.tsx | 3 +- source/views/home/index.tsx | 2 +- source/views/settings/index.ts | 4 - .../views/settings/screens/feature-flags.tsx | 114 ------------------ .../settings/screens/overview/developer.tsx | 2 - source/views/views.ts | 17 +-- 13 files changed, 3 insertions(+), 318 deletions(-) delete mode 100644 modules/app-config/__tests__/useCourseSearchRecentsScreen.test.tsx delete mode 100644 modules/app-config/index.ts delete mode 100644 modules/app-config/package.json delete mode 100644 modules/app-config/types.ts delete mode 100644 source/views/settings/screens/feature-flags.tsx diff --git a/modules/app-config/__tests__/useCourseSearchRecentsScreen.test.tsx b/modules/app-config/__tests__/useCourseSearchRecentsScreen.test.tsx deleted file mode 100644 index 4c5234f8bd..0000000000 --- a/modules/app-config/__tests__/useCourseSearchRecentsScreen.test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, {ReactElement} from 'react' -import {describe, expect, test} from '@jest/globals' -import {renderHook, waitFor} from '@testing-library/react-native' -import {QueryClient, QueryClientProvider} from '@tanstack/react-query' - -import {useFeature} from '../index' -import {AppConfigEntry} from '../types' - -jest.mock('../../../modules/constants', () => ({ - isDevMode: jest.fn(), -})) - -jest.mock('../../../source/lib/storage', () => ({ - getFeatureFlag: jest.fn(), -})) - -describe('useCourseSearchRecentsScreen', () => { - let queryClient: QueryClient - - beforeAll(() => { - queryClient = new QueryClient() - }) - - afterAll(() => { - queryClient.clear() - queryClient.removeQueries() - }) - - const queryWrapper = ({children}: {children: ReactElement}) => ( - {children} - ) - - // eslint-disable-next-line @typescript-eslint/no-var-requires - const isDevModeMock = require('../../../modules/constants').isDevMode - const getFeatureFlagMock = - // eslint-disable-next-line @typescript-eslint/no-var-requires - require('../../../source/lib/storage').getFeatureFlag - - test('it should return true in dev when feature is enabled', async () => { - isDevModeMock.mockReturnValue(true) - getFeatureFlagMock.mockReturnValue(true) - - const {result} = renderHook( - () => useFeature(AppConfigEntry.Courses_ShowRecentSearchScreen), - { - wrapper: queryWrapper, - }, - ) - - await waitFor(() => { - expect(result.current).toBe(true) - }) - }) - - test('it should return false in dev when feature is disabled', async () => { - isDevModeMock.mockReturnValue(true) - getFeatureFlagMock.mockReturnValue(false) - - const {result} = renderHook( - () => useFeature(AppConfigEntry.Courses_ShowRecentSearchScreen), - { - wrapper: queryWrapper, - }, - ) - - await waitFor(() => { - expect(result.current).toBe(false) - }) - }) -}) diff --git a/modules/app-config/index.ts b/modules/app-config/index.ts deleted file mode 100644 index 29c08f034e..0000000000 --- a/modules/app-config/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {getFeatureFlag} from '../../source/lib/storage' -import {AppConfigEntry, FeatureFlag} from './types' -import {useQuery} from '@tanstack/react-query' -import {isDevMode} from '@frogpond/constants' - -export type {AppConfigEntry, FeatureFlag} from './types' - -// helper method to query exported __DEV__ feature flags -export const useFeature = (featureKey: AppConfigEntry): boolean => { - let {data: featureValue = false} = useQuery({ - queryKey: ['app', 'app:feature-flag', featureKey], - queryFn: () => getFeatureFlag(featureKey), - onSuccess: (newValue) => { - return isDevMode() ? newValue : false - }, - }) - - return isDevMode() ? featureValue : false -} - -// datastore for the __DEV__ feature flags -export const AppConfig = async (): Promise => { - if (!isDevMode()) { - return [] - } - - return [ - { - title: 'Show the course search recents screen', - configKey: AppConfigEntry.Courses_ShowRecentSearchScreen, - active: await getFeatureFlag( - AppConfigEntry.Courses_ShowRecentSearchScreen, - ), - }, - ] -} - -// exported feature flags -export const useCourseSearchRecentsScreen = (): boolean => - useFeature(AppConfigEntry.Courses_ShowRecentSearchScreen) diff --git a/modules/app-config/package.json b/modules/app-config/package.json deleted file mode 100644 index 566206932c..0000000000 --- a/modules/app-config/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@frogpond/app-config", - "version": "1.0.0", - "description": "", - "main": "index.ts", - "author": "", - "license": "ISC", - "scripts": { - "test": "jest" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-native": "^0.72.4" - }, - "dependencies": {} -} diff --git a/modules/app-config/types.ts b/modules/app-config/types.ts deleted file mode 100644 index 6e2cade4df..0000000000 --- a/modules/app-config/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface FeatureFlag { - configKey: AppConfigEntry - title: string - active: boolean -} - -/** - * __DEV__ app config keys - * - * Reserved for dev-only flags for internal experimentation, these - * will never be A/B flags nor will they return true in production. - * - * The format is SECTION_KEY where the first underscore will be - * split at render time for our view to group by, but the entirety - * of the value (section + key) will be stored. - */ -export enum AppConfigEntry { - Courses_ShowRecentSearchScreen = 'Courses_ShowRecentSearchScreen', -} diff --git a/package-lock.json b/package-lock.json index 6be3bd88ba..6428ae7d20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -158,15 +158,6 @@ "ky": "1.7.2" } }, - "modules/app-config": { - "name": "@frogpond/app-config", - "version": "1.0.0", - "license": "ISC", - "peerDependencies": { - "react": "^18.0.0", - "react-native": "^0.72.4" - } - }, "modules/app-theme": { "name": "@frogpond/app-theme", "version": "1.0.0", @@ -3010,10 +3001,6 @@ "resolved": "modules/api", "link": true }, - "node_modules/@frogpond/app-config": { - "resolved": "modules/app-config", - "link": true - }, "node_modules/@frogpond/app-theme": { "resolved": "modules/app-theme", "link": true diff --git a/source/lib/storage.ts b/source/lib/storage.ts index 4e7bd55e9b..55a4abceb3 100644 --- a/source/lib/storage.ts +++ b/source/lib/storage.ts @@ -6,7 +6,6 @@ import { setItem, setStoragePrefix, } from '@frogpond/storage' -import {AppConfigEntry} from '@frogpond/app-config' import type {FilterComboType} from '../views/sis/course-search/lib/format-filter-combo' import type {CourseType, TermType} from './course-search/types' @@ -14,21 +13,6 @@ export {clearAsyncStorage} setStoragePrefix('aao:') -/// MARK: Feature flags - -const featureFlagsKey = 'app:feature-flag' -export function setFeatureFlag( - name: AppConfigEntry, - value: boolean, -): Promise { - const key = `${featureFlagsKey}:${name}` - return setItem(key, value) -} -export function getFeatureFlag(name: AppConfigEntry): Promise { - const key = `${featureFlagsKey}:${name}` - return getItemAsBoolean(key) -} - /// MARK: Settings const homescreenOrderKey = 'homescreen:view-order' diff --git a/source/navigation/routes.tsx b/source/navigation/routes.tsx index e0c0f2400b..18dad5f247 100644 --- a/source/navigation/routes.tsx +++ b/source/navigation/routes.tsx @@ -284,11 +284,6 @@ const SettingsStackScreens = () => { {/* developer */} - +} export type CafeMenuParamList = { CarletonBurtonMenu: undefined @@ -100,7 +100,6 @@ export type SettingsStackParamList = { Credits: undefined [debug.NavigationKey]: {keyPath: string[]} Faq: undefined - FeatureFlags: undefined IconSettings: undefined Legal: undefined NetworkLogger: undefined diff --git a/source/views/home/index.tsx b/source/views/home/index.tsx index ea1b342990..1f191c0c38 100644 --- a/source/views/home/index.tsx +++ b/source/views/home/index.tsx @@ -25,7 +25,7 @@ const styles = StyleSheet.create({ function HomePage(): JSX.Element { let navigation = useNavigation() - let allViews = AllViews().filter((view) => !view.disabled ?? true) + let allViews = AllViews().filter((view) => !view.disabled) let columns = partitionByIndex(allViews) return ( diff --git a/source/views/settings/index.ts b/source/views/settings/index.ts index 2b411d24c6..9e0905fb78 100644 --- a/source/views/settings/index.ts +++ b/source/views/settings/index.ts @@ -7,10 +7,6 @@ export {IconSettingsView} from './screens/change-icon' export {CreditsView} from './screens/credits' export {LegalView} from './screens/legal' export {PrivacyView} from './screens/privacy' -export { - View as FeatureFlagView, - NavigationOptions as FeatureFlagNavigationOptions, -} from './screens/feature-flags' // Developer settings export {DebugRootView} from './screens/debug' diff --git a/source/views/settings/screens/feature-flags.tsx b/source/views/settings/screens/feature-flags.tsx deleted file mode 100644 index a8e5577f34..0000000000 --- a/source/views/settings/screens/feature-flags.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import * as React from 'react' -import {StyleSheet, Text, SectionList} from 'react-native' - -import restart from 'react-native-restart' -import {NativeStackNavigationOptions} from '@react-navigation/native-stack' - -import {CellToggle} from '@frogpond/tableview/cells' -import {ListEmpty, ListSectionHeader, ListSeparator} from '@frogpond/lists' -import * as c from '@frogpond/colors' -import {LoadingView, NoticeView} from '@frogpond/notice' -import {toLaxTitleCase} from '@frogpond/titlecase' -import {Touchable} from '@frogpond/touchable' -import {AppConfig, FeatureFlag} from '@frogpond/app-config' - -import {groupBy, orderBy} from 'lodash' -import {commonStyles} from '../../../../modules/navigation-buttons/styles' -import * as storage from '../../../lib/storage' - -export const FeatureFlagsView = (): JSX.Element => { - let [loading, setLoading] = React.useState(true) - let [sections, setSections] = React.useState([]) - - React.useEffect(() => { - async function fetchData() { - let config = await AppConfig() - setSections(config) - setLoading(false) - } - fetchData() - }, [sections]) - - if (!sections) { - return - } - - let findGroup = (configKey: FeatureFlag['configKey']) => { - return configKey.split('_')?.[0] ?? 'Unknown' - } - - let sorters: Array<(flag: FeatureFlag) => string> = [ - (flag) => findGroup(flag.configKey), - (flag) => flag.title, - ] - - let ordered: Array<'desc' | 'asc'> = ['desc', 'asc', 'desc'] - - let sorted = orderBy(sections, sorters, ordered) - let grouped = groupBy(sorted, (s) => findGroup(s.configKey)) - let groupedSections = Object.entries(grouped) - .map(([key, value]) => ({ - title: key, - data: value, - })) - .sort((a, b) => a.title.localeCompare(b.title)) - - return ( - - ) : ( - - ) - } - contentContainerStyle={styles.contentContainer} - contentInsetAdjustmentBehavior="automatic" - keyExtractor={(item, key) => `${item.title}-${key}`} - renderItem={({item: {title, active, configKey}}) => ( - { - storage.setFeatureFlag(configKey, newValue) - }} - value={Boolean(active)} - /> - )} - renderSectionHeader={({section: {title}}) => ( - - )} - sections={groupedSections} - /> - ) -} - -let styles = StyleSheet.create({ - contentContainer: { - flexGrow: 1, - backgroundColor: c.systemBackground, - }, -}) - -export {FeatureFlagsView as View} - -export const NavigationOptions: NativeStackNavigationOptions = { - title: 'Feature Flags', - headerRight: () => ( - restart.Restart()} - style={commonStyles.button} - > - {/* eslint-disable-next-line react-native/no-inline-styles */} - - Reload - - - ), -} diff --git a/source/views/settings/screens/overview/developer.tsx b/source/views/settings/screens/overview/developer.tsx index 16c7686311..0dd467901c 100644 --- a/source/views/settings/screens/overview/developer.tsx +++ b/source/views/settings/screens/overview/developer.tsx @@ -14,7 +14,6 @@ export const DeveloperSection = (): React.ReactElement => { const onComponentsButton = () => navigation.navigate('ComponentLibrary') const onAPIButton = () => navigation.navigate('APITest') const onBonAppButton = () => navigation.navigate('BonAppPicker') - const onFeatureFlagsButton = () => navigation.navigate('FeatureFlags') const onDebugButton = () => navigation.navigate(DebugKey, {keyPath: ['Root']}) const onNetworkLoggerButton = () => navigation.navigate('NetworkLogger') const sendSentryMessage = () => { @@ -42,7 +41,6 @@ export const DeveloperSection = (): React.ReactElement => { return ( <>
- diff --git a/source/views/views.ts b/source/views/views.ts index 7a5512a484..ea8d3bd6f1 100644 --- a/source/views/views.ts +++ b/source/views/views.ts @@ -1,5 +1,5 @@ import * as c from '@frogpond/colors' -import {RootViewsParamList, MiscViewParamList} from '../navigation/types' +import {RootViewsParamList} from '../navigation/types' import {NavigationKey as menus} from './menus' import {NavigationKey as sis} from './sis' @@ -8,8 +8,6 @@ import {NavigationKey as streaming} from './streaming' import {NavigationKey as news} from './news' import {NavigationKey as transportation} from './transportation' -import {useCourseSearchRecentsScreen} from '@frogpond/app-config' - const hours: keyof RootViewsParamList = 'BuildingHours' const directory: keyof RootViewsParamList = 'Directory' const importantContacts: keyof RootViewsParamList = 'Contacts' @@ -18,7 +16,6 @@ const studentOrgs: keyof RootViewsParamList = 'StudentOrgs' const more: keyof RootViewsParamList = 'More' const printJobs: keyof RootViewsParamList = 'PrintJobs' const courseSearch: keyof RootViewsParamList = 'CourseSearch' -const courseSearchResults: keyof MiscViewParamList = 'CourseSearchResults' type CommonView = { title: string @@ -41,8 +38,6 @@ type WebLinkView = { export type ViewType = CommonView & (NativeView | WebLinkView) export const AllViews = (): Array => { - const showRecentCourseSearches = useCourseSearchRecentsScreen() - return [ { type: 'view', @@ -157,7 +152,6 @@ export const AllViews = (): Array => { tint: c.tealToSeafoam[0], }, { - disabled: !showRecentCourseSearches, type: 'view', view: courseSearch, title: 'Course Catalog', @@ -165,15 +159,6 @@ export const AllViews = (): Array => { foreground: 'light', tint: c.lavender, }, - { - disabled: showRecentCourseSearches, - type: 'view', - view: courseSearchResults, - title: 'Course Catalog', - icon: 'lab-flask', - foreground: 'light', - tint: c.lavender, - }, { type: 'url', url: 'https://oleville.com/',