From 5f6d262f492ad3911fd715accf51d96edff51c80 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:00:13 +0100 Subject: [PATCH 1/6] chore: remove featureSwitchRefactor flag --- .../LegacyFeatureToggleSwitch.tsx | 257 ------ .../LegacyProjectFeatureToggles.tsx | 736 ------------------ .../project/Project/ProjectOverview.tsx | 27 +- frontend/src/interfaces/uiConfig.ts | 1 - .../__snapshots__/create-config.test.ts.snap | 1 - .../feature-toggle-strategies-store.ts | 40 +- src/lib/types/experimental.ts | 5 - src/server-dev.ts | 1 - .../api/admin/project/projects.e2e.test.ts | 5 +- 9 files changed, 25 insertions(+), 1048 deletions(-) delete mode 100644 frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/LegacyFeatureToggleSwitch.tsx delete mode 100644 frontend/src/component/project/Project/ProjectFeatureToggles/LegacyProjectFeatureToggles.tsx diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/LegacyFeatureToggleSwitch.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/LegacyFeatureToggleSwitch.tsx deleted file mode 100644 index fb92c9aec494..000000000000 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/LegacyFeatureToggleSwitch.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import React, { useState, VFC } from 'react'; -import { Box, styled } from '@mui/material'; -import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch'; -import { UPDATE_FEATURE_ENVIRONMENT } from 'component/providers/AccessProvider/permissions'; -import { useOptimisticUpdate } from './hooks/useOptimisticUpdate'; -import { flexRow } from 'themes/themeStyles'; -import { ENVIRONMENT_STRATEGY_ERROR } from 'constants/apiErrors'; -import { formatUnknownError } from 'utils/formatUnknownError'; -import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; -import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import useToast from 'hooks/useToast'; -import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; -import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle'; -import { EnableEnvironmentDialog } from './EnableEnvironmentDialog/EnableEnvironmentDialog'; -import { UpdateEnabledMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage'; -import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; -import { - FeatureStrategyProdGuard, - useFeatureStrategyProdGuard, -} from '../../../../feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard'; - -const StyledBoxContainer = styled(Box)<{ 'data-testid': string }>(() => ({ - mx: 'auto', - ...flexRow, -})); - -interface IFeatureToggleSwitchProps { - featureId: string; - environmentName: string; - projectId: string; - value: boolean; - onError?: () => void; - onToggle?: ( - projectId: string, - feature: string, - env: string, - state: boolean, - ) => void; -} - -/** - * @deprecated remove when flag `featureSwitchRefactor` is removed - */ -export const FeatureToggleSwitch: VFC = ({ - projectId, - featureId, - environmentName, - value, - onToggle, - onError, -}) => { - const { loading, toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } = - useFeatureApi(); - const { setToastData, setToastApiError } = useToast(); - const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); - const { - onChangeRequestToggle, - onChangeRequestToggleClose, - onChangeRequestToggleConfirm, - changeRequestDialogDetails, - } = useChangeRequestToggle(projectId); - const [isChecked, setIsChecked, rollbackIsChecked] = - useOptimisticUpdate(value); - - const [showEnabledDialog, setShowEnabledDialog] = useState(false); - const { feature } = useFeature(projectId, featureId); - const enableProdGuard = useFeatureStrategyProdGuard( - feature, - environmentName, - ); - const [showProdGuard, setShowProdGuard] = useState(false); - - const disabledStrategiesCount = - feature?.environments - .find((env) => env.name === environmentName) - ?.strategies.filter((strategy) => strategy.disabled).length ?? 0; - - const handleToggleEnvironmentOn = async ( - shouldActivateDisabled = false, - ) => { - try { - setIsChecked(!isChecked); - await toggleFeatureEnvironmentOn( - projectId, - feature.name, - environmentName, - shouldActivateDisabled, - ); - setToastData({ - type: 'success', - title: `Available in ${environmentName}`, - text: `${feature.name} is now available in ${environmentName} based on its defined strategies.`, - }); - onToggle?.(projectId, feature.name, environmentName, !isChecked); - } catch (error: unknown) { - if ( - error instanceof Error && - error.message === ENVIRONMENT_STRATEGY_ERROR - ) { - onError?.(); - } else { - setToastApiError(formatUnknownError(error)); - } - rollbackIsChecked(); - } - }; - - const handleToggleEnvironmentOff = async () => { - try { - setIsChecked(!isChecked); - await toggleFeatureEnvironmentOff( - projectId, - feature.name, - environmentName, - ); - setToastData({ - type: 'success', - title: `Unavailable in ${environmentName}`, - text: `${feature.name} is unavailable in ${environmentName} and its strategies will no longer have any effect.`, - }); - onToggle?.(projectId, feature.name, environmentName, !isChecked); - } catch (error: unknown) { - setToastApiError(formatUnknownError(error)); - rollbackIsChecked(); - } - }; - - const handleClick = async () => { - setShowProdGuard(false); - if (isChangeRequestConfigured(environmentName)) { - if (featureHasOnlyDisabledStrategies()) { - setShowEnabledDialog(true); - } else { - onChangeRequestToggle( - feature.name, - environmentName, - !isChecked, - false, - ); - } - return; - } - if (isChecked) { - await handleToggleEnvironmentOff(); - return; - } - - if (featureHasOnlyDisabledStrategies()) { - setShowEnabledDialog(true); - } else { - await handleToggleEnvironmentOn(); - } - }; - - const onClick = async () => { - if (enableProdGuard && !isChangeRequestConfigured(environmentName)) { - setShowProdGuard(true); - } else { - await handleClick(); - } - }; - - const onActivateStrategies = async () => { - if (isChangeRequestConfigured(environmentName)) { - onChangeRequestToggle( - feature.name, - environmentName, - !isChecked, - true, - ); - } else { - await handleToggleEnvironmentOn(true); - } - setShowEnabledDialog(false); - }; - - const onAddDefaultStrategy = async () => { - if (isChangeRequestConfigured(environmentName)) { - onChangeRequestToggle( - feature.name, - environmentName, - !isChecked, - false, - ); - } else { - await handleToggleEnvironmentOn(); - } - setShowEnabledDialog(false); - }; - - const featureHasOnlyDisabledStrategies = () => { - const featureEnvironment = feature?.environments?.find( - (env) => env.name === environmentName, - ); - return ( - featureEnvironment?.strategies && - featureEnvironment?.strategies?.length > 0 && - featureEnvironment?.strategies?.every( - (strategy) => strategy.disabled, - ) - ); - }; - - const key = `${feature.name}-${environmentName}`; - - return ( - <> - - - - setShowEnabledDialog(false)} - environment={environmentName} - disabledStrategiesCount={disabledStrategiesCount} - onActivateDisabledStrategies={onActivateStrategies} - onAddDefaultStrategy={onAddDefaultStrategy} - /> - - } - /> - setShowProdGuard(false)} - onClick={handleClick} - loading={loading} - label={`${isChecked ? 'Disable' : 'Enable'} Environment`} - /> - - ); -}; diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/LegacyProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/LegacyProjectFeatureToggles.tsx deleted file mode 100644 index 37b3dded0520..000000000000 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/LegacyProjectFeatureToggles.tsx +++ /dev/null @@ -1,736 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { - Checkbox, - IconButton, - styled, - Tooltip, - useMediaQuery, - useTheme, -} from '@mui/material'; -import { Add } from '@mui/icons-material'; -import { useNavigate, useSearchParams } from 'react-router-dom'; -import { - SortingRule, - useFlexLayout, - useRowSelect, - useSortBy, - useTable, -} from 'react-table'; -import type { FeatureSchema } from 'openapi'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { PageHeader } from 'component/common/PageHeader/PageHeader'; -import { PageContent } from 'component/common/PageContent/PageContent'; -import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; -import { getCreateTogglePath } from 'utils/routePathHelpers'; -import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; -import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; -import { FeatureSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureSeenCell'; -import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell'; -import { IProject } from 'interfaces/project'; -import { TablePlaceholder, VirtualizedTable } from 'component/common/Table'; -import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; -import useProject from 'hooks/api/getters/useProject/useProject'; -import { createLocalStorage } from 'utils/createLocalStorage'; -import EnvironmentStrategyDialog from 'component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog'; -import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog'; -import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog'; -import { getColumnValues, includesFilter, useSearch } from 'hooks/useSearch'; -import { Search } from 'component/common/Search/Search'; -import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle'; -import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; -import { UpdateEnabledMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage'; -import { IFeatureToggleListItem } from 'interfaces/featureToggle'; -import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader'; -import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell'; -import { - ProjectEnvironmentType, - useEnvironmentsRef, -} from './hooks/useEnvironmentsRef'; -import { FeatureToggleSwitch } from './FeatureToggleSwitch/LegacyFeatureToggleSwitch'; -import { ActionsCell } from './ActionsCell/ActionsCell'; -import { ColumnsMenu } from './ColumnsMenu/ColumnsMenu'; -import { useStyles } from './ProjectFeatureToggles.styles'; -import { usePinnedFavorites } from 'hooks/usePinnedFavorites'; -import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi'; -import { FeatureTagCell } from 'component/common/Table/cells/FeatureTagCell/FeatureTagCell'; -import { useGlobalLocalStorage } from 'hooks/useGlobalLocalStorage'; -import { flexRow } from 'themes/themeStyles'; -import VariantsWarningTooltip from 'component/feature/FeatureView/FeatureVariants/VariantsTooltipWarning'; -import FileDownload from '@mui/icons-material/FileDownload'; -import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; -import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog'; -import { RowSelectCell } from './RowSelectCell/RowSelectCell'; -import { BatchSelectionActionsBar } from '../../../common/BatchSelectionActionsBar/BatchSelectionActionsBar'; -import { ProjectFeaturesBatchActions } from './ProjectFeaturesBatchActions/ProjectFeaturesBatchActions'; -import { FeatureEnvironmentSeenCell } from '../../../common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell'; -import useToast from 'hooks/useToast'; - -const StyledResponsiveButton = styled(ResponsiveButton)(() => ({ - whiteSpace: 'nowrap', -})); - -const StyledSwitchContainer = styled('div', { - shouldForwardProp: (prop) => prop !== 'hasWarning', -})<{ hasWarning?: boolean }>(({ theme, hasWarning }) => ({ - flexGrow: 0, - ...flexRow, - justifyContent: 'center', - ...(hasWarning && { - '::before': { - content: '""', - display: 'block', - width: theme.spacing(2), - }, - }), -})); - -interface IProjectFeatureTogglesProps { - features: IProject['features']; - environments: IProject['environments']; - loading: boolean; -} - -type ListItemType = Pick< - IProject['features'][number], - 'name' | 'lastSeenAt' | 'createdAt' | 'type' | 'stale' | 'favorite' -> & { - environments: { - [key in string]: { - name: string; - enabled: boolean; - variantCount: number; - }; - }; - someEnabledEnvironmentHasVariants: boolean; -}; - -const staticColumns = ['Select', 'Actions', 'name', 'favorite']; - -const defaultSort: SortingRule & { - columns?: string[]; -} = { id: 'createdAt' }; - -/** - * @deprecated remove when flag `featureSwitchRefactor` is removed - */ -export const ProjectFeatureToggles = ({ - features, - loading, - environments: newEnvironments = [], -}: IProjectFeatureTogglesProps) => { - const { classes: styles } = useStyles(); - const theme = useTheme(); - const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); - const [strategiesDialogState, setStrategiesDialogState] = useState({ - open: false, - featureId: '', - environmentName: '', - }); - const [featureStaleDialogState, setFeatureStaleDialogState] = useState<{ - featureId?: string; - stale?: boolean; - }>({}); - const [featureArchiveState, setFeatureArchiveState] = useState< - string | undefined - >(); - const projectId = useRequiredPathParam('projectId'); - const { setToastApiError } = useToast(); - const { value: storedParams, setValue: setStoredParams } = - createLocalStorage( - `${projectId}:FeatureToggleListTable:v1`, - defaultSort, - ); - const { value: globalStore, setValue: setGlobalStore } = - useGlobalLocalStorage(); - const navigate = useNavigate(); - const [searchParams, setSearchParams] = useSearchParams(); - const environments = useEnvironmentsRef( - loading - ? [{ environment: 'a' }, { environment: 'b' }, { environment: 'c' }] - : newEnvironments, - ); - const { refetch } = useProject(projectId); - const { isFavoritesPinned, sortTypes, onChangeIsFavoritePinned } = - usePinnedFavorites( - searchParams.has('favorites') - ? searchParams.get('favorites') === 'true' - : globalStore.favorites, - ); - const { favorite, unfavorite } = useFavoriteFeaturesApi(); - const { - onChangeRequestToggleClose, - onChangeRequestToggleConfirm, - changeRequestDialogDetails, - } = useChangeRequestToggle(projectId); - const [showExportDialog, setShowExportDialog] = useState(false); - const { uiConfig } = useUiConfig(); - const showEnvironmentLastSeen = Boolean( - uiConfig.flags.lastSeenByEnvironment, - ); - - const onFavorite = useCallback( - async (feature: IFeatureToggleListItem) => { - try { - if (feature?.favorite) { - await unfavorite(projectId, feature.name); - } else { - await favorite(projectId, feature.name); - } - refetch(); - } catch (error) { - setToastApiError( - 'Something went wrong, could not update favorite', - ); - } - }, - [projectId, refetch, setToastApiError], - ); - - const showTagsColumn = useMemo( - () => features.some((feature) => feature?.tags?.length), - [features], - ); - - const columns = useMemo( - () => [ - { - id: 'Select', - Header: ({ getToggleAllRowsSelectedProps }: any) => ( - - ), - Cell: ({ row }: any) => ( - - ), - maxWidth: 50, - disableSortBy: true, - hideInMenu: true, - }, - { - id: 'favorite', - Header: ( - - ), - accessor: 'favorite', - Cell: ({ row: { original: feature } }: any) => ( - onFavorite(feature)} - /> - ), - maxWidth: 50, - disableSortBy: true, - hideInMenu: true, - }, - { - Header: 'Seen', - accessor: 'lastSeenAt', - Cell: ({ value, row: { original: feature } }: any) => { - return showEnvironmentLastSeen ? ( - - ) : ( - - ); - }, - align: 'center', - maxWidth: 80, - }, - { - Header: 'Type', - accessor: 'type', - Cell: FeatureTypeCell, - align: 'center', - filterName: 'type', - maxWidth: 80, - }, - { - Header: 'Name', - accessor: 'name', - Cell: ({ value }: { value: string }) => ( - - - - - - ), - minWidth: 100, - sortType: 'alphanumeric', - searchable: true, - }, - ...(showTagsColumn - ? [ - { - id: 'tags', - Header: 'Tags', - accessor: (row: IFeatureToggleListItem) => - row.tags - ?.map(({ type, value }) => `${type}:${value}`) - .join('\n') || '', - Cell: FeatureTagCell, - width: 80, - searchable: true, - filterName: 'tags', - filterBy( - row: IFeatureToggleListItem, - values: string[], - ) { - return includesFilter( - getColumnValues(this, row), - values, - ); - }, - }, - ] - : []), - { - Header: 'Created', - accessor: 'createdAt', - Cell: DateCell, - sortType: 'date', - minWidth: 120, - }, - ...environments.map((value: ProjectEnvironmentType | string) => { - const name = - typeof value === 'string' - ? value - : (value as ProjectEnvironmentType).environment; - return { - Header: loading ? () => '' : name, - maxWidth: 90, - id: `environments.${name}`, - accessor: (row: ListItemType) => - row.environments[name]?.enabled, - align: 'center', - Cell: ({ - value, - row: { original: feature }, - }: { - value: boolean; - row: { original: ListItemType }; - }) => { - const hasWarning = - feature.someEnabledEnvironmentHasVariants && - feature.environments[name].variantCount === 0 && - feature.environments[name].enabled; - - return ( - - - } - /> - - ); - }, - sortType: 'boolean', - filterName: name, - filterParsing: (value: boolean) => - value ? 'enabled' : 'disabled', - }; - }), - - { - id: 'Actions', - maxWidth: 56, - width: 56, - Cell: (props: { row: { original: ListItemType } }) => ( - - ), - disableSortBy: true, - hideInMenu: true, - }, - ], - [projectId, environments, loading], - ); - - const [searchValue, setSearchValue] = useState( - searchParams.get('search') || '', - ); - - const [showTitle, setShowTitle] = useState(true); - - const featuresData = useMemo( - () => - features.map((feature) => ({ - ...feature, - environments: Object.fromEntries( - environments.map((env) => { - const thisEnv = feature?.environments.find( - (featureEnvironment) => - featureEnvironment?.name === env, - ); - return [ - env, - { - name: env, - enabled: thisEnv?.enabled || false, - variantCount: thisEnv?.variantCount || 0, - lastSeenAt: thisEnv?.lastSeenAt, - }, - ]; - }), - ), - someEnabledEnvironmentHasVariants: - feature.environments?.some( - (featureEnvironment) => - featureEnvironment.variantCount > 0 && - featureEnvironment.enabled, - ) || false, - })), - [features, environments], - ); - - const { - data: searchedData, - getSearchText, - getSearchContext, - } = useSearch(columns, searchValue, featuresData); - - const data = useMemo(() => { - if (loading) { - return Array(6).fill({ - type: '-', - name: 'Feature name', - createdAt: new Date(), - environments: { - production: { name: 'production', enabled: false }, - }, - }) as FeatureSchema[]; - } - return searchedData; - }, [loading, searchedData]); - - const initialState = useMemo( - () => { - const allColumnIds = columns - .map( - (column: any) => - (column?.id as string) || - (typeof column?.accessor === 'string' - ? (column?.accessor as string) - : ''), - ) - .filter(Boolean); - let hiddenColumns = environments - .filter((_, index) => index >= 3) - .map((environment) => `environments.${environment}`); - - if (searchParams.has('columns')) { - const columnsInParams = - searchParams.get('columns')?.split(',') || []; - const visibleColumns = [...staticColumns, ...columnsInParams]; - hiddenColumns = allColumnIds.filter( - (columnId) => !visibleColumns.includes(columnId), - ); - } else if (storedParams.columns) { - const visibleColumns = [ - ...staticColumns, - ...storedParams.columns, - ]; - hiddenColumns = allColumnIds.filter( - (columnId) => !visibleColumns.includes(columnId), - ); - } - - return { - sortBy: [ - { - id: searchParams.get('sort') || 'createdAt', - desc: searchParams.has('order') - ? searchParams.get('order') === 'desc' - : storedParams.desc, - }, - ], - hiddenColumns, - selectedRowIds: {}, - }; - }, - [environments], // eslint-disable-line react-hooks/exhaustive-deps - ); - - const getRowId = useCallback((row: any) => row.name, []); - const { - allColumns, - headerGroups, - rows, - state: { selectedRowIds, sortBy, hiddenColumns }, - prepareRow, - setHiddenColumns, - toggleAllRowsSelected, - } = useTable( - { - columns: columns as any[], // TODO: fix after `react-table` v8 update - data, - initialState, - sortTypes, - autoResetHiddenColumns: false, - autoResetSelectedRows: false, - disableSortRemove: true, - autoResetSortBy: false, - getRowId, - }, - useFlexLayout, - useSortBy, - useRowSelect, - ); - - useEffect(() => { - if (loading) { - return; - } - const tableState: Record = {}; - tableState.sort = sortBy[0].id; - if (sortBy[0].desc) { - tableState.order = 'desc'; - } - if (searchValue) { - tableState.search = searchValue; - } - if (isFavoritesPinned) { - tableState.favorites = 'true'; - } - tableState.columns = allColumns - .map(({ id }) => id) - .filter( - (id) => - !staticColumns.includes(id) && !hiddenColumns?.includes(id), - ) - .join(','); - - setSearchParams(tableState, { - replace: true, - }); - setStoredParams((params) => ({ - ...params, - id: sortBy[0].id, - desc: sortBy[0].desc || false, - columns: tableState.columns.split(','), - })); - setGlobalStore((params) => ({ - ...params, - favorites: Boolean(isFavoritesPinned), - })); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - loading, - sortBy, - hiddenColumns, - searchValue, - setSearchParams, - isFavoritesPinned, - ]); - - return ( - <> - - setShowTitle(false)} - onBlur={() => setShowTitle(true)} - hasFilters - getSearchContext={getSearchContext} - id='projectFeatureToggles' - /> - } - /> - - - - - setShowExportDialog(true) - } - sx={(theme) => ({ - marginRight: - theme.spacing(2), - })} - > - - - - } - /> - - navigate(getCreateTogglePath(projectId)) - } - maxWidth='960px' - Icon={Add} - projectId={projectId} - permission={CREATE_FEATURE} - data-testid='NAVIGATE_TO_CREATE_FEATURE' - > - New feature toggle - - - } - > - - } - /> - - } - > - - - - 0} - show={ - - No feature toggles found matching “ - {searchValue} - ” - - } - elseShow={ - - No feature toggles available. Get started by - adding a new feature toggle. - - } - /> - } - /> - - setStrategiesDialogState((prev) => ({ - ...prev, - open: false, - })) - } - projectId={projectId} - {...strategiesDialogState} - /> - { - setFeatureStaleDialogState({}); - refetch(); - }} - featureId={featureStaleDialogState.featureId || ''} - projectId={projectId} - /> - { - refetch(); - }} - onClose={() => { - setFeatureArchiveState(undefined); - }} - featureIds={[featureArchiveState || '']} - projectId={projectId} - />{' '} - - } - /> - setShowExportDialog(false)} - environments={environments} - /> - } - /> - - - toggleAllRowsSelected(false)} - /> - - - ); -}; diff --git a/frontend/src/component/project/Project/ProjectOverview.tsx b/frontend/src/component/project/Project/ProjectOverview.tsx index 1a4523bb2d0c..3b54ba0cc3b8 100644 --- a/frontend/src/component/project/Project/ProjectOverview.tsx +++ b/frontend/src/component/project/Project/ProjectOverview.tsx @@ -3,7 +3,6 @@ import useProject, { useProjectNameOrId, } from 'hooks/api/getters/useProject/useProject'; import { Box, styled } from '@mui/material'; -import { ProjectFeatureToggles as LegacyProjectFeatureToggles } from './ProjectFeatureToggles/LegacyProjectFeatureToggles'; import { ProjectFeatureToggles } from './ProjectFeatureToggles/ProjectFeatureToggles'; import ProjectInfo from './ProjectInfo/ProjectInfo'; import { usePageTitle } from 'hooks/usePageTitle'; @@ -168,7 +167,6 @@ const ProjectOverview = () => { project; usePageTitle(`Project overview – ${projectName}`); const { setLastViewed } = useLastViewedProject(); - const featureSwitchRefactor = useUiFlag('featureSwitchRefactor'); const featureSearchFrontend = useUiFlag('featureSearchFrontend'); useEffect(() => { @@ -190,25 +188,12 @@ const ProjectOverview = () => { - ( - - )} - elseShow={() => ( - - )} + diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index 6ba426531303..ffb6884c5cfd 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -69,7 +69,6 @@ export type UiFlags = { banners?: boolean; disableEnvsOnRevive?: boolean; playgroundImprovements?: boolean; - featureSwitchRefactor?: boolean; scheduledConfigurationChanges?: boolean; featureSearchAPI?: boolean; featureSearchFrontend?: boolean; diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 5c07d1b07efe..82c9eb9daef9 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -88,7 +88,6 @@ exports[`should create default config 1`] = ` "featureNamingPattern": false, "featureSearchAPI": false, "featureSearchFrontend": false, - "featureSwitchRefactor": false, "featuresExportImport": true, "filterInvalidClientMetrics": false, "googleAuthEnabled": false, diff --git a/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts b/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts index 0f072bb0dc0e..9f681f5eaab4 100644 --- a/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts +++ b/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts @@ -688,17 +688,15 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { ]; } - if (this.flagResolver.isEnabled('featureSwitchRefactor')) { - selectColumns = [ - ...selectColumns, - this.db.raw( - 'EXISTS (SELECT 1 FROM feature_strategies WHERE feature_strategies.feature_name = features.name AND feature_strategies.environment = feature_environments.environment) as has_strategies', - ), - this.db.raw( - 'EXISTS (SELECT 1 FROM feature_strategies WHERE feature_strategies.feature_name = features.name AND feature_strategies.environment = feature_environments.environment AND (feature_strategies.disabled IS NULL OR feature_strategies.disabled = false)) as has_enabled_strategies', - ), - ]; - } + selectColumns = [ + ...selectColumns, + this.db.raw( + 'EXISTS (SELECT 1 FROM feature_strategies WHERE feature_strategies.feature_name = features.name AND feature_strategies.environment = feature_environments.environment) as has_strategies', + ), + this.db.raw( + 'EXISTS (SELECT 1 FROM feature_strategies WHERE feature_strategies.feature_name = features.name AND feature_strategies.environment = feature_environments.environment AND (feature_strategies.disabled IS NULL OR feature_strategies.disabled = false)) as has_enabled_strategies', + ), + ]; const sortByMapping = { name: 'feature_name', @@ -840,17 +838,15 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { ]; } - if (this.flagResolver.isEnabled('featureSwitchRefactor')) { - selectColumns = [ - ...selectColumns, - this.db.raw( - 'EXISTS (SELECT 1 FROM feature_strategies WHERE feature_strategies.feature_name = features.name AND feature_strategies.environment = feature_environments.environment) as has_strategies', - ), - this.db.raw( - 'EXISTS (SELECT 1 FROM feature_strategies WHERE feature_strategies.feature_name = features.name AND feature_strategies.environment = feature_environments.environment AND (feature_strategies.disabled IS NULL OR feature_strategies.disabled = false)) as has_enabled_strategies', - ), - ]; - } + selectColumns = [ + ...selectColumns, + this.db.raw( + 'EXISTS (SELECT 1 FROM feature_strategies WHERE feature_strategies.feature_name = features.name AND feature_strategies.environment = feature_environments.environment) as has_strategies', + ), + this.db.raw( + 'EXISTS (SELECT 1 FROM feature_strategies WHERE feature_strategies.feature_name = features.name AND feature_strategies.environment = feature_environments.environment AND (feature_strategies.disabled IS NULL OR feature_strategies.disabled = false)) as has_enabled_strategies', + ), + ]; query = query.select(selectColumns); const rows = await query; diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index 5832ff7f64e6..c67d7133cdbb 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -33,7 +33,6 @@ export type IFlagKey = | 'banners' | 'disableEnvsOnRevive' | 'playgroundImprovements' - | 'featureSwitchRefactor' | 'featureSearchAPI' | 'featureSearchFrontend' | 'scheduledConfigurationChanges' @@ -157,10 +156,6 @@ const flags: IFlags = { process.env.UNLEASH_EXPERIMENTAL_PLAYGROUND_IMPROVEMENTS, false, ), - featureSwitchRefactor: parseEnvVarBoolean( - process.env.UNLEASH_EXPERIMENTAL_FEATURE_SWITCH_REFACTOR, - false, - ), featureSearchAPI: parseEnvVarBoolean( process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_API, false, diff --git a/src/server-dev.ts b/src/server-dev.ts index 198800edd61b..44b8ff84a98a 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -46,7 +46,6 @@ process.nextTick(async () => { useLastSeenRefactor: true, disableEnvsOnRevive: true, playgroundImprovements: true, - featureSwitchRefactor: true, featureSearchAPI: true, featureSearchFrontend: true, }, diff --git a/src/test/e2e/api/admin/project/projects.e2e.test.ts b/src/test/e2e/api/admin/project/projects.e2e.test.ts index 1487e6e72769..a99992f17d19 100644 --- a/src/test/e2e/api/admin/project/projects.e2e.test.ts +++ b/src/test/e2e/api/admin/project/projects.e2e.test.ts @@ -23,7 +23,6 @@ beforeAll(async () => { experimental: { flags: { strictSchemaValidation: true, - featureSwitchRefactor: true, }, }, }, @@ -46,9 +45,7 @@ test('should report has strategies and enabled strategies', async () => { db.stores, { experimental: { - flags: { - featureSwitchRefactor: true, - }, + flags: {}, }, }, db.rawDatabase, From dc575e079a038814f81c14bf000d755461c3bc93 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:58:25 +0100 Subject: [PATCH 2/6] refactor: unify feature toggle switch --- .../EnvironmentStrategyDialog.tsx | 73 ------------------- ...tureOverviewSidePanelEnvironmentSwitch.tsx | 28 +++++-- ...reOverviewSidePanelEnvironmentSwitches.tsx | 17 +---- .../useFeatureToggleSwitch.tsx | 6 +- .../PaginatedProjectFeatureToggles.tsx | 16 ---- .../ProjectFeatureToggles.tsx | 17 ----- 6 files changed, 27 insertions(+), 130 deletions(-) delete mode 100644 frontend/src/component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog.tsx diff --git a/frontend/src/component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog.tsx b/frontend/src/component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog.tsx deleted file mode 100644 index bc97eca01fec..000000000000 --- a/frontend/src/component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useNavigate } from 'react-router-dom'; -import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions'; -import { Dialogue } from 'component/common/Dialogue/Dialogue'; -import PermissionButton from 'component/common/PermissionButton/PermissionButton'; -import { formatCreateStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate'; -import { styled } from '@mui/material'; - -interface IEnvironmentStrategyDialogProps { - open: boolean; - featureId: string; - projectId: string; - onClose: () => void; - environmentName: string; -} - -const StyledParagraph = styled('p')(({ theme }) => ({ - marginBottom: theme.spacing(0.5), - fontSize: theme.fontSizes.bodySize, -})); -const EnvironmentStrategyDialog = ({ - open, - environmentName, - featureId, - projectId, - onClose, -}: IEnvironmentStrategyDialogProps) => { - const navigate = useNavigate(); - - const createStrategyPath = formatCreateStrategyPath( - projectId, - featureId, - environmentName, - 'default', - ); - - const onClick = () => { - onClose(); - navigate(createStrategyPath); - }; - - return ( - onClose()} - title='You need to add a strategy to your toggle' - primaryButtonText='Take me directly to add strategy' - permissionButton={ - - Take me directly to add strategy - - } - secondaryButtonText='Cancel' - > - - Before you can enable the toggle in the environment, you need to - add an activation strategy. - - - You can add the activation strategy by selecting the toggle, - open the environment accordion and add the activation strategy. - - - ); -}; - -export default EnvironmentStrategyDialog; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx index 972bafe917e9..cdbba64aa2fb 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx @@ -4,7 +4,8 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { styled } from '@mui/material'; import StringTruncator from 'component/common/StringTruncator/StringTruncator'; import { FeatureOverviewSidePanelEnvironmentHider } from './FeatureOverviewSidePanelEnvironmentHider'; -import { FeatureToggleSwitch } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/LegacyFeatureToggleSwitch'; +import { FeatureToggleSwitch } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch'; +import { useFeatureToggleSwitch } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch'; const StyledContainer = styled('div')(({ theme }) => ({ marginLeft: theme.spacing(-1.5), @@ -24,7 +25,6 @@ const StyledLabel = styled('label')(() => ({ interface IFeatureOverviewSidePanelEnvironmentSwitchProps { environment: IFeatureEnvironment; callback?: () => void; - showInfoBox: () => void; children?: React.ReactNode; hiddenEnvironments: Set; setHiddenEnvironments: (environment: string) => void; @@ -33,7 +33,6 @@ interface IFeatureOverviewSidePanelEnvironmentSwitchProps { export const FeatureOverviewSidePanelEnvironmentSwitch = ({ environment, callback, - showInfoBox, children, hiddenEnvironments, setHiddenEnvironments, @@ -52,10 +51,25 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ ); + const { onToggle: onFeatureToggle, modals: featureToggleModals } = + useFeatureToggleSwitch(projectId); - const handleToggle = () => { - refetchFeature(); - if (callback) callback(); + const handleToggle = (newState: boolean, onRollback: () => void) => { + onFeatureToggle(newState, { + projectId, + featureId, + environmentName: name, + environmentType: environment.type, + hasStrategies: environment.strategies.length > 0, + hasEnabledStrategies: environment.strategies.some( + (strategy) => !strategy.disabled, + ), + onRollback, + onSuccess: () => { + if (callback) callback(); + refetchFeature(); + }, + }); }; return ( @@ -66,7 +80,6 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ projectId={projectId} environmentName={environment.name} onToggle={handleToggle} - onError={showInfoBox} value={enabled} /> {children ?? defaultContent} @@ -76,6 +89,7 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ hiddenEnvironments={hiddenEnvironments} setHiddenEnvironments={setHiddenEnvironments} /> + {featureToggleModals} ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches.tsx index 166f36508b1d..9fe6d71c7fc7 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches.tsx @@ -1,6 +1,4 @@ -import EnvironmentStrategyDialog from 'component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog'; -import { IFeatureToggle } from 'interfaces/featureToggle'; -import { useState } from 'react'; +import { type IFeatureToggle } from 'interfaces/featureToggle'; import { FeatureOverviewSidePanelEnvironmentSwitch } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch'; import { Link, styled, Tooltip } from '@mui/material'; import { Link as RouterLink } from 'react-router-dom'; @@ -53,8 +51,6 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({ hiddenEnvironments, setHiddenEnvironments, }: IFeatureOverviewSidePanelEnvironmentSwitchesProps) => { - const [showInfoBox, setShowInfoBox] = useState(false); - const [environmentName, setEnvironmentName] = useState(''); const someEnabledEnvironmentHasVariants = feature.environments.some( (environment) => environment.enabled && environment.variants?.length, ); @@ -96,10 +92,6 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({ environment={environment} hiddenEnvironments={hiddenEnvironments} setHiddenEnvironments={setHiddenEnvironments} - showInfoBox={() => { - setEnvironmentName(environment.name); - setShowInfoBox(true); - }} > {environment.name} @@ -120,13 +112,6 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({ ); })} - setShowInfoBox(false)} - projectId={feature.project} - featureId={feature.name} - environmentName={environmentName} - /> ); }; diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch.tsx index b726e433cc54..7f8248f21e0d 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch.tsx @@ -96,7 +96,11 @@ export const useFeatureToggleSwitch: UseFeatureToggleSwitchType = ( }; const ensureActiveStrategies: Middleware = (next) => { - if (!config.hasStrategies || config.hasEnabledStrategies) { + if ( + newState === false || + !config.hasStrategies || + config.hasEnabledStrategies + ) { return next(); } diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx index af2c37ad4c41..3d6091fff737 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx @@ -32,7 +32,6 @@ import { IProject } from 'interfaces/project'; import { TablePlaceholder, VirtualizedTable } from 'component/common/Table'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { createLocalStorage } from 'utils/createLocalStorage'; -import EnvironmentStrategyDialog from 'component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog'; import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog'; import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog'; import { getColumnValues, includesFilter, useSearch } from 'hooks/useSearch'; @@ -102,11 +101,6 @@ export const PaginatedProjectFeatureToggles = ({ const headerLoadingRef = useLoading(initialLoad); const theme = useTheme(); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); - const [strategiesDialogState, setStrategiesDialogState] = useState({ - open: false, - featureId: '', - environmentName: '', - }); const [featureStaleDialogState, setFeatureStaleDialogState] = useState<{ featureId?: string; stale?: boolean; @@ -659,16 +653,6 @@ export const PaginatedProjectFeatureToggles = ({ /> } /> - - setStrategiesDialogState((prev) => ({ - ...prev, - open: false, - })) - } - projectId={projectId} - {...strategiesDialogState} - /> } /> - - setStrategiesDialogState((prev) => ({ - ...prev, - open: false, - })) - } - projectId={projectId} - {...strategiesDialogState} - /> Date: Tue, 14 Nov 2023 11:09:53 +0100 Subject: [PATCH 3/6] refactor: prefix useApi development logs --- frontend/src/hooks/api/actions/useApi/useApi.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/hooks/api/actions/useApi/useApi.ts b/frontend/src/hooks/api/actions/useApi/useApi.ts index 1bc062787065..697b38af0b8a 100644 --- a/frontend/src/hooks/api/actions/useApi/useApi.ts +++ b/frontend/src/hooks/api/actions/useApi/useApi.ts @@ -42,7 +42,7 @@ interface IUseAPI { const timeApiCallStart = (requestId: string) => { // Store the start time in milliseconds - console.log(`Starting timing for request: ${requestId}`); + console.log(`[DEVELOPMENT LOG] Starting timing for request: ${requestId}`); return Date.now(); }; @@ -50,11 +50,13 @@ const timeApiCallEnd = (startTime: number, requestId: string) => { // Calculate the end time and subtract the start time const endTime = Date.now(); const duration = endTime - startTime; - console.log(`Timing for request ${requestId}: ${duration} ms`); + console.log( + `[DEVELOPMENT LOG] Timing for request ${requestId}: ${duration} ms`, + ); if (duration > 500) { console.error( - 'API call took over 500ms. This may indicate a rendering performance problem in your React component.', + '[DEVELOPMENT LOG] API call took over 500ms. This may indicate a rendering performance problem in your React component.', requestId, duration, ); From f2e2e3fbbf99dbb4cbe1a5b70a0b4672c519bdd7 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:19:12 +0100 Subject: [PATCH 4/6] fix: use change requests on feature view --- .../FeatureOverviewSidePanelEnvironmentSwitch.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx index cdbba64aa2fb..0d03efa9f9e6 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx @@ -6,6 +6,7 @@ import StringTruncator from 'component/common/StringTruncator/StringTruncator'; import { FeatureOverviewSidePanelEnvironmentHider } from './FeatureOverviewSidePanelEnvironmentHider'; import { FeatureToggleSwitch } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch'; import { useFeatureToggleSwitch } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch'; +import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; const StyledContainer = styled('div')(({ theme }) => ({ marginLeft: theme.spacing(-1.5), @@ -42,6 +43,7 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ const projectId = useRequiredPathParam('projectId'); const featureId = useRequiredPathParam('featureId'); const { feature, refetchFeature } = useFeature(projectId, featureId); + const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const defaultContent = ( <> @@ -64,6 +66,7 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ hasEnabledStrategies: environment.strategies.some( (strategy) => !strategy.disabled, ), + isChangeRequestEnabled: isChangeRequestConfigured(environment.name), onRollback, onSuccess: () => { if (callback) callback(); From c231c020803c57963732c78279b31610e531f099 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:27:16 +0100 Subject: [PATCH 5/6] refactor: remove destructuring from feature overview sidepanel environment --- ...tureOverviewSidePanelEnvironmentSwitch.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx index 0d03efa9f9e6..b9261140d6c5 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx @@ -38,8 +38,6 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ hiddenEnvironments, setHiddenEnvironments, }: IFeatureOverviewSidePanelEnvironmentSwitchProps) => { - const { name, enabled } = environment; - const projectId = useRequiredPathParam('projectId'); const featureId = useRequiredPathParam('featureId'); const { feature, refetchFeature } = useFeature(projectId, featureId); @@ -48,19 +46,25 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ const defaultContent = ( <> {' '} - {enabled ? 'enabled' : 'disabled'} in + + {environment.enabled ? 'enabled' : 'disabled'} in +   - + ); const { onToggle: onFeatureToggle, modals: featureToggleModals } = useFeatureToggleSwitch(projectId); - const handleToggle = (newState: boolean, onRollback: () => void) => { + const handleToggle = (newState: boolean, onRollback: () => void) => onFeatureToggle(newState, { projectId, featureId, - environmentName: name, + environmentName: environment.name, environmentType: environment.type, hasStrategies: environment.strategies.length > 0, hasEnabledStrategies: environment.strategies.some( @@ -73,7 +77,6 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ refetchFeature(); }, }); - }; return ( @@ -83,7 +86,7 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ projectId={projectId} environmentName={environment.name} onToggle={handleToggle} - value={enabled} + value={environment.enabled} /> {children ?? defaultContent} From efa75e79c30773c97cefb98634e521b4b852d7f9 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:37:05 +0100 Subject: [PATCH 6/6] fix: update unit tests for environment service --- src/test/e2e/services/environment-service.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/e2e/services/environment-service.test.ts b/src/test/e2e/services/environment-service.test.ts index b94a2c0d92e2..d0463188b32a 100644 --- a/src/test/e2e/services/environment-service.test.ts +++ b/src/test/e2e/services/environment-service.test.ts @@ -63,6 +63,8 @@ test('Can connect environment to project', async () => { type: 'production', variantCount: 0, lastSeenAt: null, + hasStrategies: false, + hasEnabledStrategies: false, }, ]); }); @@ -91,6 +93,8 @@ test('Can remove environment from project', async () => { type: 'production', variantCount: 0, lastSeenAt: null, + hasStrategies: false, + hasEnabledStrategies: false, }, ]); });