diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx
index 6e31210cbd..fc3421697d 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx
@@ -7,7 +7,6 @@ import Edit from '@mui/icons-material/Edit';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
-import { useUiFlag } from 'hooks/useUiFlag';
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
import { useState } from 'react';
import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog';
@@ -98,7 +97,6 @@ const FeatureOverviewMetaData = () => {
const featureId = useRequiredPathParam('featureId');
const { feature, refetchFeature } = useFeature(projectId, featureId);
const { project, description, type } = feature;
- const featureLifecycleEnabled = useUiFlag('featureLifecycle');
const navigate = useNavigate();
const [showDelDialog, setShowDelDialog] = useState(false);
const [showMarkCompletedDialogue, setShowMarkCompletedDialogue] =
@@ -142,10 +140,7 @@ const FeatureOverviewMetaData = () => {
{project}
Lifecycle:
diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx
index d1913e0116..18d40b3aee 100644
--- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx
+++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx
@@ -34,7 +34,6 @@ import { TableEmptyState } from './TableEmptyState/TableEmptyState';
import { useRowActions } from './hooks/useRowActions';
import { useSelectedData } from './hooks/useSelectedData';
import { FeatureOverviewCell } from '../../../common/Table/cells/FeatureOverviewCell/FeatureOverviewCell';
-import { useUiFlag } from 'hooks/useUiFlag';
import {
useProjectFeatureSearch,
useProjectFeatureSearchActions,
@@ -55,7 +54,6 @@ export const ProjectFeatureToggles = ({
environments,
}: IPaginatedProjectFeatureTogglesProps) => {
const projectId = useRequiredPathParam('projectId');
- const featureLifecycleEnabled = useUiFlag('featureLifecycle');
const {
features,
@@ -192,36 +190,30 @@ export const ProjectFeatureToggles = ({
width: '1%',
},
}),
- ...(featureLifecycleEnabled
- ? [
- columnHelper.accessor('lifecycle', {
- id: 'lifecycle',
- header: 'Lifecycle',
- cell: ({ row: { original } }) => (
- {
- setShowMarkCompletedDialogue({
- featureId: original.name,
- open: true,
- });
- }}
- onUncomplete={refetch}
- onArchive={() =>
- setFeatureArchiveState(original.name)
- }
- data-loading
- />
- ),
- enableSorting: false,
- size: 50,
- meta: {
- align: 'center',
- width: '1%',
- },
- }),
- ]
- : []),
+ columnHelper.accessor('lifecycle', {
+ id: 'lifecycle',
+ header: 'Lifecycle',
+ cell: ({ row: { original } }) => (
+ {
+ setShowMarkCompletedDialogue({
+ featureId: original.name,
+ open: true,
+ });
+ }}
+ onUncomplete={refetch}
+ onArchive={() => setFeatureArchiveState(original.name)}
+ data-loading
+ />
+ ),
+ enableSorting: false,
+ size: 50,
+ meta: {
+ align: 'center',
+ width: '1%',
+ },
+ }),
...environments.map((name: string) => {
const isChangeRequestEnabled = isChangeRequestConfigured(name);
@@ -302,7 +294,6 @@ export const ProjectFeatureToggles = ({
tableState.favoritesFirst,
refetch,
isPlaceholder,
- featureLifecycleEnabled,
],
);
@@ -430,16 +421,11 @@ export const ProjectFeatureToggles = ({
id: 'lastSeenAt',
isVisible: columnVisibility.lastSeenAt,
},
- ...(featureLifecycleEnabled
- ? [
- {
- header: 'Lifecycle',
- id: 'lifecycle',
- isVisible:
- columnVisibility.lifecycle,
- },
- ]
- : []),
+ {
+ header: 'Lifecycle',
+ id: 'lifecycle',
+ isVisible: columnVisibility.lifecycle,
+ },
{
id: 'divider',
},
diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/hooks/useDefaultColumnVisibility.ts b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/hooks/useDefaultColumnVisibility.ts
index 2bdc880025..b5ea57d8eb 100644
--- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/hooks/useDefaultColumnVisibility.ts
+++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/hooks/useDefaultColumnVisibility.ts
@@ -1,7 +1,6 @@
import { useCallback } from 'react';
import { useMediaQuery, useTheme } from '@mui/material';
import type { VisibilityState } from '@tanstack/react-table';
-import { useUiFlag } from 'hooks/useUiFlag';
const staticColumns = ['select', 'actions', 'name', 'favorite'];
@@ -22,7 +21,6 @@ export const useDefaultColumnVisibility = (allColumnIds: string[]) => {
const isTinyScreen = useMediaQuery(theme.breakpoints.down('sm'));
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
- const featureLifecycleEnabled = useUiFlag('featureLifecycle');
const showEnvironments = useCallback(
(environmentsToShow: number = 0) =>
@@ -55,7 +53,7 @@ export const useDefaultColumnVisibility = (allColumnIds: string[]) => {
return formatAsColumnVisibility(allColumnIds, [
...staticColumns,
'lastSeenAt',
- ...(featureLifecycleEnabled ? ['lifecycle'] : []),
+ 'lifecycle',
'createdAt',
'createdBy',
'type',
diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap
index 653d94d85a..edc33639f4 100644
--- a/src/lib/__snapshots__/create-config.test.ts.snap
+++ b/src/lib/__snapshots__/create-config.test.ts.snap
@@ -105,7 +105,6 @@ exports[`should create default config 1`] = `
"extendedMetrics": false,
"extendedUsageMetrics": false,
"featureCollaborators": false,
- "featureLifecycle": false,
"featureSearchFeedback": {
"enabled": false,
"name": "withText",
diff --git a/src/lib/features/feature-lifecycle/feature-lifecycle-controller.ts b/src/lib/features/feature-lifecycle/feature-lifecycle-controller.ts
index 1ce31bbd88..3aaddb2dc1 100644
--- a/src/lib/features/feature-lifecycle/feature-lifecycle-controller.ts
+++ b/src/lib/features/feature-lifecycle/feature-lifecycle-controller.ts
@@ -19,7 +19,6 @@ import {
} from '../../openapi';
import Controller from '../../routes/controller';
import type { Request, Response } from 'express';
-import { NotFoundError } from '../../error';
import type { IAuthRequest } from '../../routes/unleash-types';
import type { WithTransactional } from '../../db/transaction';
@@ -120,9 +119,6 @@ export default class FeatureLifecycleController extends Controller {
req: Request,
res: Response,
): Promise {
- if (!this.flagResolver.isEnabled('featureLifecycle')) {
- throw new NotFoundError('Feature lifecycle is disabled.');
- }
const { featureName } = req.params;
const result =
@@ -144,9 +140,6 @@ export default class FeatureLifecycleController extends Controller {
>,
res: Response,
): Promise {
- if (!this.flagResolver.isEnabled('featureLifecycle')) {
- throw new NotFoundError('Feature lifecycle is disabled.');
- }
const { featureName, projectId } = req.params;
const status = req.body;
@@ -162,9 +155,6 @@ export default class FeatureLifecycleController extends Controller {
req: IAuthRequest,
res: Response,
): Promise {
- if (!this.flagResolver.isEnabled('featureLifecycle')) {
- throw new NotFoundError('Feature lifecycle is disabled.');
- }
const { featureName, projectId } = req.params;
await this.featureLifecycleService.transactional((service) =>
diff --git a/src/lib/features/feature-lifecycle/feature-lifecycle-read-model.ts b/src/lib/features/feature-lifecycle/feature-lifecycle-read-model.ts
index caed22d8ed..f7b308095b 100644
--- a/src/lib/features/feature-lifecycle/feature-lifecycle-read-model.ts
+++ b/src/lib/features/feature-lifecycle/feature-lifecycle-read-model.ts
@@ -36,10 +36,6 @@ export class FeatureLifecycleReadModel implements IFeatureLifecycleReadModel {
}
async getStageCount(): Promise {
- if (!this.flagResolver.isEnabled('featureLifecycleMetrics')) {
- return [];
- }
-
const { rows } = await this.db.raw(`
SELECT
stage,
@@ -65,10 +61,6 @@ export class FeatureLifecycleReadModel implements IFeatureLifecycleReadModel {
}
async getStageCountByProject(): Promise {
- if (!this.flagResolver.isEnabled('featureLifecycleMetrics')) {
- return [];
- }
-
const { rows } = await this.db.raw(`
SELECT
f.project,
@@ -133,10 +125,6 @@ export class FeatureLifecycleReadModel implements IFeatureLifecycleReadModel {
public async getAllWithStageDuration(): Promise<
IProjectLifecycleStageDuration[]
> {
- if (!this.flagResolver.isEnabled('featureLifecycleMetrics')) {
- return [];
- }
-
const featureLifeCycles = await this.getAll();
return calculateStageDurations(featureLifeCycles);
}
diff --git a/src/lib/features/feature-lifecycle/feature-lifecycle-service.test.ts b/src/lib/features/feature-lifecycle/feature-lifecycle-service.test.ts
index 9b3a2de61d..358d130751 100644
--- a/src/lib/features/feature-lifecycle/feature-lifecycle-service.test.ts
+++ b/src/lib/features/feature-lifecycle/feature-lifecycle-service.test.ts
@@ -1,7 +1,6 @@
import {
CLIENT_METRICS_ADDED,
FEATURE_ARCHIVED,
- FEATURE_COMPLETED,
FEATURE_CREATED,
FEATURE_REVIVED,
type IEnvironment,
@@ -101,33 +100,3 @@ test('can insert and read lifecycle stages', async () => {
{ stage: 'initial', enteredStageAt: expect.any(Date) },
]);
});
-
-test('ignores lifecycle state updates when flag disabled', async () => {
- const eventBus = new EventEmitter();
- const { featureLifecycleService, eventStore, environmentStore } =
- createFakeFeatureLifecycleService({
- flagResolver: { isEnabled: () => false },
- eventBus,
- getLogger: noLoggerProvider,
- } as unknown as IUnleashConfig);
- const featureName = 'testFeature';
-
- await environmentStore.create({
- name: 'my-dev-environment',
- type: 'development',
- } as IEnvironment);
- featureLifecycleService.listen();
-
- await eventStore.emit(FEATURE_CREATED, { featureName });
- await eventStore.emit(FEATURE_COMPLETED, { featureName });
- await eventBus.emit(CLIENT_METRICS_ADDED, {
- bucket: { toggles: { [featureName]: 'irrelevant' } },
- environment: 'development',
- });
- await eventStore.emit(FEATURE_ARCHIVED, { featureName });
-
- const lifecycle =
- await featureLifecycleService.getFeatureLifecycle(featureName);
-
- expect(lifecycle).toEqual([]);
-});
diff --git a/src/lib/features/feature-lifecycle/feature-lifecycle-service.ts b/src/lib/features/feature-lifecycle/feature-lifecycle-service.ts
index 0b2d44aa5a..ae33e19b83 100644
--- a/src/lib/features/feature-lifecycle/feature-lifecycle-service.ts
+++ b/src/lib/features/feature-lifecycle/feature-lifecycle-service.ts
@@ -77,19 +77,10 @@ export class FeatureLifecycleService {
);
}
- private async checkEnabled(fn: () => Promise) {
- const enabled = this.flagResolver.isEnabled('featureLifecycle');
- if (enabled) {
- return fn();
- }
- }
-
listen() {
- void this.checkEnabled(() => this.featureLifecycleStore.backfill());
+ this.featureLifecycleStore.backfill();
this.eventStore.on(FEATURE_CREATED, async (event) => {
- await this.checkEnabled(() =>
- this.featureInitialized(event.featureName),
- );
+ await this.featureInitialized(event.featureName);
});
this.eventBus.on(
CLIENT_METRICS_ADDED,
@@ -103,22 +94,19 @@ export class FeatureLifecycleService {
const features = metrics.map(
(metric) => metric.featureName,
);
- await this.checkEnabled(() =>
- this.featuresReceivedMetrics(features, environment),
+ await this.featuresReceivedMetrics(
+ features,
+ environment,
);
}
}
},
);
this.eventStore.on(FEATURE_ARCHIVED, async (event) => {
- await this.checkEnabled(() =>
- this.featureArchived(event.featureName),
- );
+ await this.featureArchived(event.featureName);
});
this.eventStore.on(FEATURE_REVIVED, async (event) => {
- await this.checkEnabled(() =>
- this.featureRevived(event.featureName),
- );
+ await this.featureRevived(event.featureName);
});
}
diff --git a/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts b/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts
index 87c4aa38a9..7f05c5e274 100644
--- a/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts
+++ b/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts
@@ -32,9 +32,7 @@ beforeAll(async () => {
db.stores,
{
experimental: {
- flags: {
- featureLifecycle: true,
- },
+ flags: {},
},
},
db.rawDatabase,
diff --git a/src/lib/features/feature-search/feature-search-store.ts b/src/lib/features/feature-search/feature-search-store.ts
index b49942bca5..7bc944fe15 100644
--- a/src/lib/features/feature-search/feature-search-store.ts
+++ b/src/lib/features/feature-search/feature-search-store.ts
@@ -11,7 +11,6 @@ import type {
} from '../../types';
import FeatureToggleStore from '../feature-toggle/feature-toggle-store';
import type { Db } from '../../db/db';
-import Raw = Knex.Raw;
import type {
IFeatureSearchParams,
IQueryParam,
@@ -19,6 +18,7 @@ import type {
import { applyGenericQueryParams, applySearchFilters } from './search-utils';
import type { FeatureSearchEnvironmentSchema } from '../../openapi/spec/feature-search-environment-schema';
import { generateImageUrl } from '../../util';
+import Raw = Knex.Raw;
const sortEnvironments = (overview: IFeatureSearchOverview[]) => {
return overview.map((data: IFeatureSearchOverview) => ({
@@ -112,9 +112,6 @@ class FeatureSearchStore implements IFeatureSearchStore {
const validatedSortOrder =
sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc';
- const featureLifecycleEnabled =
- this.flagResolver.isEnabled('featureLifecycle');
-
const finalQuery = this.db
.with('ranked_features', (query) => {
query.from('features');
@@ -315,27 +312,22 @@ class FeatureSearchStore implements IFeatureSearchStore {
.joinRaw('CROSS JOIN total_features')
.whereBetween('final_rank', [offset + 1, offset + limit])
.orderBy('final_rank');
- if (featureLifecycleEnabled) {
- finalQuery
- .select(
- 'lifecycle.latest_stage',
- 'lifecycle.stage_status',
- 'lifecycle.entered_stage_at',
- )
- .leftJoin(
- 'lifecycle',
- 'ranked_features.feature_name',
- 'lifecycle.stage_feature',
- );
- }
+ finalQuery
+ .select(
+ 'lifecycle.latest_stage',
+ 'lifecycle.stage_status',
+ 'lifecycle.entered_stage_at',
+ )
+ .leftJoin(
+ 'lifecycle',
+ 'ranked_features.feature_name',
+ 'lifecycle.stage_feature',
+ );
this.queryExtraData(finalQuery);
const rows = await finalQuery;
stopTimer();
if (rows.length > 0) {
- const overview = this.getAggregatedSearchData(
- rows,
- featureLifecycleEnabled,
- );
+ const overview = this.getAggregatedSearchData(rows);
const features = sortEnvironments(
overview,
) as IFeatureSearchOverview[];
@@ -461,10 +453,7 @@ class FeatureSearchStore implements IFeatureSearchStore {
return rankingSql;
}
- getAggregatedSearchData(
- rows,
- featureLifecycleEnabled: boolean,
- ): IFeatureSearchOverview[] {
+ getAggregatedSearchData(rows): IFeatureSearchOverview[] {
const entriesMap: Map = new Map();
const orderedEntries: IFeatureSearchOverview[] = [];
@@ -501,17 +490,15 @@ class FeatureSearchStore implements IFeatureSearchStore {
}),
},
};
- if (featureLifecycleEnabled) {
- entry.lifecycle = row.latest_stage
- ? {
- stage: row.latest_stage,
- ...(row.stage_status
- ? { status: row.stage_status }
- : {}),
- enteredStageAt: row.entered_stage_at,
- }
- : undefined;
- }
+ entry.lifecycle = row.latest_stage
+ ? {
+ stage: row.latest_stage,
+ ...(row.stage_status
+ ? { status: row.stage_status }
+ : {}),
+ enteredStageAt: row.entered_stage_at,
+ }
+ : undefined;
entriesMap.set(row.feature_name, entry);
orderedEntries.push(entry);
}
diff --git a/src/lib/features/feature-search/feature.search.e2e.test.ts b/src/lib/features/feature-search/feature.search.e2e.test.ts
index e2c26876d7..86a8ee7d49 100644
--- a/src/lib/features/feature-search/feature.search.e2e.test.ts
+++ b/src/lib/features/feature-search/feature.search.e2e.test.ts
@@ -21,7 +21,6 @@ beforeAll(async () => {
experimental: {
flags: {
strictSchemaValidation: true,
- featureLifecycle: true,
anonymiseEventLog: true,
},
},
diff --git a/src/lib/metrics.test.ts b/src/lib/metrics.test.ts
index 20a46ab343..be70f46dfc 100644
--- a/src/lib/metrics.test.ts
+++ b/src/lib/metrics.test.ts
@@ -52,11 +52,6 @@ beforeAll(async () => {
server: {
serverMetrics: true,
},
- experimental: {
- flags: {
- featureLifecycleMetrics: true,
- },
- },
});
stores = createStores();
eventStore = stores.eventStore;
diff --git a/src/lib/server-impl.test.ts b/src/lib/server-impl.test.ts
index 66d522ff61..36effa95f8 100644
--- a/src/lib/server-impl.test.ts
+++ b/src/lib/server-impl.test.ts
@@ -1,7 +1,6 @@
import express from 'express';
import { createTestConfig } from '../test/config/test-config';
import { create, start } from './server-impl';
-import FakeEventStore from '../test/fixtures/fake-event-store';
jest.mock(
'./routes',
@@ -15,7 +14,6 @@ jest.mock(
const noop = () => {};
-const eventStore = new FakeEventStore();
const settingStore = {
get: () => {
Promise.resolve('secret');
@@ -34,21 +32,20 @@ jest.mock('./metrics', () => ({
},
}));
+jest.mock('./services', () => ({
+ createServices() {
+ return {
+ featureLifecycleService: { listen() {} },
+ schedulerService: { stop() {}, start() {} },
+ addonService: { destroy() {} },
+ };
+ },
+}));
+
jest.mock('./db', () => ({
createStores() {
return {
- db: {
- destroy: () => undefined,
- },
- clientInstanceStore: {
- destroy: noop,
- removeInstancesOlderThanTwoDays: noop,
- },
- clientMetricsStore: { destroy: noop, on: noop },
- eventStore,
- publicSignupTokenStore: { destroy: noop, on: noop },
settingStore,
- projectStore: { getAll: () => Promise.resolve([]) },
};
},
}));
diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts
index 16677f115e..df52c4d1eb 100644
--- a/src/lib/types/experimental.ts
+++ b/src/lib/types/experimental.ts
@@ -52,8 +52,6 @@ export type IFlagKey =
| 'displayEdgeBanner'
| 'disableShowContextFieldSelectionValues'
| 'projectOverviewRefactorFeedback'
- | 'featureLifecycle'
- | 'featureLifecycleMetrics'
| 'parseProjectFromSession'
| 'manyStrategiesPagination'
| 'enableLegacyVariants'
@@ -267,10 +265,6 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_PROJECT_OVERVIEW_REFACTOR_FEEDBACK,
false,
),
- featureLifecycle: parseEnvVarBoolean(
- process.env.UNLEASH_EXPERIMENTAL_FEATURE_LIFECYCLE,
- false,
- ),
parseProjectFromSession: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_PARSE_PROJECT_FROM_SESSION,
false,
diff --git a/src/server-dev.ts b/src/server-dev.ts
index 60842e4fae..e6a31f6c31 100644
--- a/src/server-dev.ts
+++ b/src/server-dev.ts
@@ -48,7 +48,6 @@ process.nextTick(async () => {
outdatedSdksBanner: true,
disableShowContextFieldSelectionValues: false,
projectOverviewRefactorFeedback: true,
- featureLifecycle: true,
parseProjectFromSession: true,
manyStrategiesPagination: true,
enableLegacyVariants: false,