1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-13 13:48:59 +02:00

[Gitar] Cleaning up stale flag: archiveProjects with value true (#8201)

This commit is contained in:
gitar-bot[bot] 2024-09-23 11:51:55 +02:00 committed by GitHub
parent b3ecb92edf
commit 1296327c03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 51 additions and 132 deletions

View File

@ -44,7 +44,6 @@ import { ProjectApplications } from '../ProjectApplications/ProjectApplications'
import { ProjectInsights } from './ProjectInsights/ProjectInsights'; import { ProjectInsights } from './ProjectInsights/ProjectInsights';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview'; import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { ProjectArchived } from './ArchiveProject/ProjectArchived'; import { ProjectArchived } from './ArchiveProject/ProjectArchived';
import { useUiFlag } from 'hooks/useUiFlag';
const StyledBadge = styled(Badge)(({ theme }) => ({ const StyledBadge = styled(Badge)(({ theme }) => ({
position: 'absolute', position: 'absolute',
@ -77,7 +76,6 @@ export const Project = () => {
const basePath = `/projects/${projectId}`; const basePath = `/projects/${projectId}`;
const projectName = project?.name || projectId; const projectName = project?.name || projectId;
const { favorite, unfavorite } = useFavoriteProjectsApi(); const { favorite, unfavorite } = useFavoriteProjectsApi();
const archiveProjectsEnabled = useUiFlag('archiveProjects');
const [showDelDialog, setShowDelDialog] = useState(false); const [showDelDialog, setShowDelDialog] = useState(false);
@ -192,7 +190,7 @@ export const Project = () => {
</Box> </Box>
); );
if (archiveProjectsEnabled && Boolean(project.archivedAt)) { if (project.archivedAt) {
return <ProjectArchived name={project.name} />; return <ProjectArchived name={project.name} />;
} }

View File

@ -10,12 +10,10 @@ import { Alert, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { UpdateEnterpriseSettings } from './UpdateEnterpriseSettings'; import { UpdateEnterpriseSettings } from './UpdateEnterpriseSettings';
import { UpdateProject } from './UpdateProject'; import { UpdateProject } from './UpdateProject';
import { DeleteProjectForm } from './DeleteProjectForm';
import useProjectOverview, { import useProjectOverview, {
featuresCount, featuresCount,
} from 'hooks/api/getters/useProjectOverview/useProjectOverview'; } from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { ArchiveProjectForm } from './ArchiveProjectForm'; import { ArchiveProjectForm } from './ArchiveProjectForm';
import { useUiFlag } from 'hooks/useUiFlag';
const StyledFormContainer = styled('div')(({ theme }) => ({ const StyledFormContainer = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
@ -28,7 +26,6 @@ const EditProject = () => {
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
const id = useRequiredPathParam('projectId'); const id = useRequiredPathParam('projectId');
const { project } = useProjectOverview(id); const { project } = useProjectOverview(id);
const archiveProjectsEnabled = useUiFlag('archiveProjects');
if (!project.name) { if (!project.name) {
return null; return null;
@ -52,19 +49,7 @@ const EditProject = () => {
condition={isEnterprise()} condition={isEnterprise()}
show={<UpdateEnterpriseSettings project={project} />} show={<UpdateEnterpriseSettings project={project} />}
/> />
<ConditionallyRender <ArchiveProjectForm featureCount={featuresCount(project)} />
condition={archiveProjectsEnabled}
show={
<ArchiveProjectForm
featureCount={featuresCount(project)}
/>
}
elseShow={
<DeleteProjectForm
featureCount={featuresCount(project)}
/>
}
/>
</StyledFormContainer> </StyledFormContainer>
</> </>
); );

View File

@ -25,7 +25,6 @@ import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
import { groupProjects } from './group-projects'; import { groupProjects } from './group-projects';
import { ProjectGroup } from './ProjectGroup'; import { ProjectGroup } from './ProjectGroup';
import { CreateProjectDialog } from '../Project/CreateProject/NewCreateProjectForm/CreateProjectDialog'; import { CreateProjectDialog } from '../Project/CreateProject/NewCreateProjectForm/CreateProjectDialog';
import { useUiFlag } from 'hooks/useUiFlag';
const StyledApiError = styled(ApiError)(({ theme }) => ({ const StyledApiError = styled(ApiError)(({ theme }) => ({
maxWidth: '500px', maxWidth: '500px',
@ -126,7 +125,6 @@ export const ProjectList = () => {
const [searchValue, setSearchValue] = useState( const [searchValue, setSearchValue] = useState(
searchParams.get('search') || '', searchParams.get('search') || '',
); );
const archiveProjectsEnabled = useUiFlag('archiveProjects');
const myProjects = new Set(useProfile().profile?.projects || []); const myProjects = new Set(useProfile().profile?.projects || []);
@ -200,9 +198,6 @@ export const ProjectList = () => {
</> </>
} }
/> />
<ConditionallyRender
condition={Boolean(archiveProjectsEnabled)}
show={
<> <>
<Link <Link
component={RouterLink} component={RouterLink}
@ -212,8 +207,6 @@ export const ProjectList = () => {
</Link> </Link>
<PageHeader.Divider /> <PageHeader.Divider />
</> </>
}
/>
<ProjectCreationButton /> <ProjectCreationButton />
</> </>

View File

@ -36,7 +36,6 @@ const NewProjectList = () => {
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const [state, setState] = useProjectsListState(); const [state, setState] = useProjectsListState();
const archiveProjectsEnabled = useUiFlag('archiveProjects');
const myProjects = new Set(useProfile().profile?.projects || []); const myProjects = new Set(useProfile().profile?.projects || []);
@ -78,10 +77,7 @@ const NewProjectList = () => {
} }
/> />
<ConditionallyRender <ProjectArchiveLink />
condition={Boolean(archiveProjectsEnabled)}
show={<ProjectArchiveLink />}
/>
<ProjectCreationButton <ProjectCreationButton
isDialogOpen={Boolean(state.create)} isDialogOpen={Boolean(state.create)}
setIsDialogOpen={(create) => setIsDialogOpen={(create) =>

View File

@ -86,7 +86,6 @@ export type UiFlags = {
enableLegacyVariants?: boolean; enableLegacyVariants?: boolean;
navigationSidebar?: boolean; navigationSidebar?: boolean;
flagCreator?: boolean; flagCreator?: boolean;
archiveProjects?: boolean;
projectListImprovements?: boolean; projectListImprovements?: boolean;
onboardingUI?: boolean; onboardingUI?: boolean;
eventTimeline?: boolean; eventTimeline?: boolean;

View File

@ -1230,7 +1230,6 @@ class FeatureToggleService {
} }
private async validateActiveProject(projectId: string) { private async validateActiveProject(projectId: string) {
if (this.flagResolver.isEnabled('archiveProjects')) {
const hasActiveProject = const hasActiveProject =
await this.projectStore.hasActiveProject(projectId); await this.projectStore.hasActiveProject(projectId);
if (!hasActiveProject) { if (!hasActiveProject) {
@ -1239,7 +1238,6 @@ class FeatureToggleService {
); );
} }
} }
}
async createFeatureToggle( async createFeatureToggle(
projectId: string, projectId: string,
@ -1253,12 +1251,8 @@ class FeatureToggleService {
await this.validateName(value.name); await this.validateName(value.name);
await this.validateFeatureFlagNameAgainstPattern(value.name, projectId); await this.validateFeatureFlagNameAgainstPattern(value.name, projectId);
let projectExists: boolean; const projectExists =
if (this.flagResolver.isEnabled('archiveProjects')) { await this.projectStore.hasActiveProject(projectId);
projectExists = await this.projectStore.hasActiveProject(projectId);
} else {
projectExists = await this.projectStore.hasProject(projectId);
}
if (await this.projectStore.isFeatureLimitReached(projectId)) { if (await this.projectStore.isFeatureLimitReached(projectId)) {
throw new InvalidOperationError( throw new InvalidOperationError(

View File

@ -38,7 +38,6 @@ let segmentService: ISegmentService;
let eventService: EventService; let eventService: EventService;
let environmentService: EnvironmentService; let environmentService: EnvironmentService;
let unleashConfig: IUnleashConfig; let unleashConfig: IUnleashConfig;
const TEST_USER_ID = -9999;
const mockConstraints = (): IConstraint[] => { const mockConstraints = (): IConstraint[] => {
return Array.from({ length: 5 }).map(() => ({ return Array.from({ length: 5 }).map(() => ({
values: ['x', 'y', 'z'], values: ['x', 'y', 'z'],
@ -51,7 +50,7 @@ const irrelevantDate = new Date();
beforeAll(async () => { beforeAll(async () => {
const config = createTestConfig({ const config = createTestConfig({
experimental: { flags: { archiveProjects: true } }, experimental: { flags: {} },
}); });
db = await dbInit( db = await dbInit(
'feature_toggle_service_v2_service_serial', 'feature_toggle_service_v2_service_serial',
@ -77,7 +76,6 @@ beforeEach(async () => {
}); });
test('Should create feature flag strategy configuration', async () => { test('Should create feature flag strategy configuration', async () => {
const projectId = 'default'; const projectId = 'default';
const username = 'feature-flag';
const config: Omit<FeatureStrategySchema, 'id'> = { const config: Omit<FeatureStrategySchema, 'id'> = {
name: 'default', name: 'default',
constraints: [], constraints: [],
@ -104,7 +102,6 @@ test('Should create feature flag strategy configuration', async () => {
test('Should be able to update existing strategy configuration', async () => { test('Should be able to update existing strategy configuration', async () => {
const projectId = 'default'; const projectId = 'default';
const username = 'existing-strategy';
const featureName = 'update-existing-strategy'; const featureName = 'update-existing-strategy';
const config: Omit<FeatureStrategySchema, 'id'> = { const config: Omit<FeatureStrategySchema, 'id'> = {
name: 'default', name: 'default',
@ -139,8 +136,6 @@ test('Should be able to update existing strategy configuration', async () => {
test('Should be able to get strategy by id', async () => { test('Should be able to get strategy by id', async () => {
const featureName = 'get-strategy-by-id'; const featureName = 'get-strategy-by-id';
const projectId = 'default'; const projectId = 'default';
const userName = 'strategy';
const config: Omit<FeatureStrategySchema, 'id'> = { const config: Omit<FeatureStrategySchema, 'id'> = {
name: 'default', name: 'default',
constraints: [], constraints: [],
@ -168,8 +163,6 @@ test('Should be able to get strategy by id', async () => {
test('should ignore name in the body when updating feature flag', async () => { test('should ignore name in the body when updating feature flag', async () => {
const featureName = 'body-name-update'; const featureName = 'body-name-update';
const projectId = 'default'; const projectId = 'default';
const userName = 'strategy';
const secondFeatureName = 'body-name-update2'; const secondFeatureName = 'body-name-update2';
await service.createFeatureToggle( await service.createFeatureToggle(
@ -213,8 +206,6 @@ test('should ignore name in the body when updating feature flag', async () => {
test('should not get empty rows as features', async () => { test('should not get empty rows as features', async () => {
const projectId = 'default'; const projectId = 'default';
const userName = 'strategy';
await service.createFeatureToggle( await service.createFeatureToggle(
projectId, projectId,
{ {
@ -505,7 +496,6 @@ test('If change requests are enabled, cannot change variants without going via C
}); });
test('If CRs are protected for any environment in the project stops bulk update of variants', async () => { test('If CRs are protected for any environment in the project stops bulk update of variants', async () => {
const user = { email: 'test@example.com', username: 'test-user' } as User;
const project = await stores.projectStore.create({ const project = await stores.projectStore.create({
id: 'crOnVariantsProject', id: 'crOnVariantsProject',
name: 'crOnVariantsProject', name: 'crOnVariantsProject',
@ -599,7 +589,6 @@ test('getPlaygroundFeatures should return ids and titles (if they exist) on clie
const projectId = 'default'; const projectId = 'default';
const title = 'custom strategy title'; const title = 'custom strategy title';
const userName = 'strategy';
const config: Omit<FeatureStrategySchema, 'id'> = { const config: Omit<FeatureStrategySchema, 'id'> = {
name: 'default', name: 'default',
constraints: [], constraints: [],
@ -819,7 +808,6 @@ test('Should enable disabled strategies on feature environment enabled', async (
const flagName = 'enableThisFlag'; const flagName = 'enableThisFlag';
const project = 'default'; const project = 'default';
const environment = 'default'; const environment = 'default';
const shouldActivateDisabledStrategies = true;
await service.createFeatureToggle( await service.createFeatureToggle(
project, project,
{ {
@ -856,7 +844,7 @@ test('Should enable disabled strategies on feature environment enabled', async (
true, true,
TEST_AUDIT_USER, TEST_AUDIT_USER,
{ email: 'test@example.com' } as User, { email: 'test@example.com' } as User,
shouldActivateDisabledStrategies, true,
); );
const strategy = await service.getStrategy(createdConfig.id); const strategy = await service.getStrategy(createdConfig.id);

View File

@ -104,13 +104,11 @@ export class ProjectReadModel implements IProjectReadModel {
}) })
.orderBy('projects.name', 'asc'); .orderBy('projects.name', 'asc');
if (this.flagResolver.isEnabled('archiveProjects')) {
if (query?.archived === true) { if (query?.archived === true) {
projects = projects.whereNot(`${TABLE}.archived_at`, null); projects = projects.whereNot(`${TABLE}.archived_at`, null);
} else { } else {
projects = projects.where(`${TABLE}.archived_at`, null); projects = projects.where(`${TABLE}.archived_at`, null);
} }
}
if (query?.id) { if (query?.id) {
projects = projects.where(`${TABLE}.id`, query.id); projects = projects.where(`${TABLE}.id`, query.id);
@ -124,12 +122,9 @@ export class ProjectReadModel implements IProjectReadModel {
'MAX(events.created_at) AS last_updated', 'MAX(events.created_at) AS last_updated',
), ),
'project_settings.project_mode', 'project_settings.project_mode',
'projects.archived_at',
] as (string | Raw<any>)[]; ] as (string | Raw<any>)[];
if (this.flagResolver.isEnabled('archiveProjects')) {
selectColumns.push(`${TABLE}.archived_at`);
}
let groupByColumns = ['projects.id', 'project_settings.project_mode']; let groupByColumns = ['projects.id', 'project_settings.project_mode'];
if (userId) { if (userId) {
@ -179,13 +174,11 @@ export class ProjectReadModel implements IProjectReadModel {
.leftJoin('project_stats', 'project_stats.project', 'projects.id') .leftJoin('project_stats', 'project_stats.project', 'projects.id')
.orderBy('projects.name', 'asc'); .orderBy('projects.name', 'asc');
if (this.flagResolver.isEnabled('archiveProjects')) {
if (query?.archived === true) { if (query?.archived === true) {
projects = projects.whereNot(`${TABLE}.archived_at`, null); projects = projects.whereNot(`${TABLE}.archived_at`, null);
} else { } else {
projects = projects.where(`${TABLE}.archived_at`, null); projects = projects.where(`${TABLE}.archived_at`, null);
} }
}
if (query?.id) { if (query?.id) {
projects = projects.where(`${TABLE}.id`, query.id); projects = projects.where(`${TABLE}.id`, query.id);
@ -199,12 +192,9 @@ export class ProjectReadModel implements IProjectReadModel {
'count(features.name) FILTER (WHERE features.archived_at is null and features.potentially_stale IS TRUE) AS potentially_stale_feature_count', 'count(features.name) FILTER (WHERE features.archived_at is null and features.potentially_stale IS TRUE) AS potentially_stale_feature_count',
), ),
'project_stats.avg_time_to_prod_current_window', 'project_stats.avg_time_to_prod_current_window',
'projects.archived_at',
] as (string | Raw<any>)[]; ] as (string | Raw<any>)[];
if (this.flagResolver.isEnabled('archiveProjects')) {
selectColumns.push(`${TABLE}.archived_at`);
}
const groupByColumns = [ const groupByColumns = [
'projects.id', 'projects.id',
'project_stats.avg_time_to_prod_current_window', 'project_stats.avg_time_to_prod_current_window',

View File

@ -83,7 +83,7 @@ beforeAll(async () => {
const config = createTestConfig({ const config = createTestConfig({
getLogger, getLogger,
experimental: { experimental: {
flags: { archiveProjects: true, useProjectReadModel: true }, flags: { useProjectReadModel: true },
}, },
}); });
eventService = createEventsService(db.rawDatabase, config); eventService = createEventsService(db.rawDatabase, config);

View File

@ -500,7 +500,6 @@ export default class ProjectService {
} }
private async validateActiveProject(projectId: string) { private async validateActiveProject(projectId: string) {
if (this.flagResolver.isEnabled('archiveProjects')) {
const hasActiveProject = const hasActiveProject =
await this.projectStore.hasActiveProject(projectId); await this.projectStore.hasActiveProject(projectId);
if (!hasActiveProject) { if (!hasActiveProject) {
@ -509,7 +508,6 @@ export default class ProjectService {
); );
} }
} }
}
async changeProject( async changeProject(
newProjectId: string, newProjectId: string,
@ -1552,9 +1550,7 @@ export default class ProjectService {
health: project.health || 0, health: project.health || 0,
favorite: favorite, favorite: favorite,
updatedAt: project.updatedAt, updatedAt: project.updatedAt,
...(this.flagResolver.isEnabled('archiveProjects') archivedAt: project.archivedAt,
? { archivedAt: project.archivedAt }
: {}),
createdAt: project.createdAt, createdAt: project.createdAt,
onboardingStatus, onboardingStatus,
environments, environments,

View File

@ -12,7 +12,7 @@ let environmentStore: IEnvironmentStore;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('project_store_serial', getLogger, { db = await dbInit('project_store_serial', getLogger, {
experimental: { flags: { archiveProjects: true } }, experimental: { flags: {} },
}); });
stores = db.stores; stores = db.stores;
projectStore = stores.projectStore; projectStore = stores.projectStore;

View File

@ -132,13 +132,11 @@ class ProjectStore implements IProjectStore {
.leftJoin('project_stats', 'project_stats.project', 'projects.id') .leftJoin('project_stats', 'project_stats.project', 'projects.id')
.orderBy('projects.name', 'asc'); .orderBy('projects.name', 'asc');
if (this.flagResolver.isEnabled('archiveProjects')) {
if (query?.archived === true) { if (query?.archived === true) {
projects = projects.whereNot(`${TABLE}.archived_at`, null); projects = projects.whereNot(`${TABLE}.archived_at`, null);
} else { } else {
projects = projects.where(`${TABLE}.archived_at`, null); projects = projects.where(`${TABLE}.archived_at`, null);
} }
}
if (query?.id) { if (query?.id) {
projects = projects.where(`${TABLE}.id`, query.id); projects = projects.where(`${TABLE}.id`, query.id);
@ -154,12 +152,9 @@ class ProjectStore implements IProjectStore {
'project_settings.default_stickiness', 'project_settings.default_stickiness',
'project_settings.project_mode', 'project_settings.project_mode',
'project_stats.avg_time_to_prod_current_window', 'project_stats.avg_time_to_prod_current_window',
'projects.archived_at',
] as (string | Raw<any>)[]; ] as (string | Raw<any>)[];
if (this.flagResolver.isEnabled('archiveProjects')) {
selectColumns.push(`${TABLE}.archived_at`);
}
let groupByColumns = [ let groupByColumns = [
'projects.id', 'projects.id',
'project_settings.default_stickiness', 'project_settings.default_stickiness',
@ -237,9 +232,7 @@ class ProjectStore implements IProjectStore {
.where(query) .where(query)
.orderBy('name', 'asc'); .orderBy('name', 'asc');
if (this.flagResolver.isEnabled('archiveProjects')) {
projects = projects.where(`${TABLE}.archived_at`, null); projects = projects.where(`${TABLE}.archived_at`, null);
}
const rows = await projects; const rows = await projects;
@ -247,10 +240,7 @@ class ProjectStore implements IProjectStore {
} }
async get(id: string): Promise<IProject> { async get(id: string): Promise<IProject> {
let extraColumns: string[] = []; const extraColumns: string[] = ['archived_at'];
if (this.flagResolver.isEnabled('archiveProjects')) {
extraColumns = ['archived_at'];
}
return this.db return this.db
.first([...COLUMNS, ...SETTINGS_COLUMNS, ...extraColumns]) .first([...COLUMNS, ...SETTINGS_COLUMNS, ...extraColumns])
@ -634,8 +624,7 @@ class ProjectStore implements IProjectStore {
async getApplicationsByProject( async getApplicationsByProject(
params: IProjectApplicationsSearchParams, params: IProjectApplicationsSearchParams,
): Promise<IProjectApplications> { ): Promise<IProjectApplications> {
const { project, limit, sortOrder, sortBy, searchParams, offset } = const { project, limit, sortOrder, searchParams, offset } = params;
params;
const validatedSortOrder = const validatedSortOrder =
sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc'; sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc';
const query = this.db const query = this.db
@ -741,9 +730,7 @@ class ProjectStore implements IProjectStore {
async count(): Promise<number> { async count(): Promise<number> {
let count = this.db.from(TABLE).count('*'); let count = this.db.from(TABLE).count('*');
if (this.flagResolver.isEnabled('archiveProjects')) {
count = count.where(`${TABLE}.archived_at`, null); count = count.where(`${TABLE}.archived_at`, null);
}
return count.then((res) => Number(res[0].count)); return count.then((res) => Number(res[0].count));
} }
@ -766,9 +753,7 @@ class ProjectStore implements IProjectStore {
this.db.raw(`COALESCE(${SETTINGS_TABLE}.project_mode, 'open')`), this.db.raw(`COALESCE(${SETTINGS_TABLE}.project_mode, 'open')`),
); );
if (this.flagResolver.isEnabled('archiveProjects')) {
query = query.where(`${TABLE}.archived_at`, null); query = query.where(`${TABLE}.archived_at`, null);
}
const result: ProjectModeCount[] = await query; const result: ProjectModeCount[] = await query;

View File

@ -56,7 +56,6 @@ export type IFlagKey =
| 'extendedMetrics' | 'extendedMetrics'
| 'removeUnsafeInlineStyleSrc' | 'removeUnsafeInlineStyleSrc'
| 'originMiddleware' | 'originMiddleware'
| 'archiveProjects'
| 'projectListImprovements' | 'projectListImprovements'
| 'useProjectReadModel' | 'useProjectReadModel'
| 'addonUsageMetrics' | 'addonUsageMetrics'
@ -281,10 +280,6 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_ORIGIN_MIDDLEWARE, process.env.UNLEASH_EXPERIMENTAL_ORIGIN_MIDDLEWARE,
false, false,
), ),
archiveProjects: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_ARCHIVE_PROJECTS,
false,
),
projectListImprovements: parseEnvVarBoolean( projectListImprovements: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_PROJECT_LIST_IMPROVEMENTS, process.env.UNLEASH_EXPERIMENTAL_PROJECT_LIST_IMPROVEMENTS,
false, false,