Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: project default strategy #3644

Merged
merged 10 commits into from
Apr 28, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader';
import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell';
import { useEnvironmentsRef } from './hooks/useEnvironmentsRef';
import {
ProjectEnvironmentType,
useEnvironmentsRef,
} from './hooks/useEnvironmentsRef';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { FeatureToggleSwitch } from './FeatureToggleSwitch/FeatureToggleSwitch';
import { ActionsCell } from './ActionsCell/ActionsCell';
Expand Down Expand Up @@ -321,46 +324,53 @@ export const ProjectFeatureToggles = ({
sortType: 'date',
minWidth: 120,
},
...environments.map((name: string) => ({
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;
...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 (
<StyledSwitchContainer hasWarning={hasWarning}>
<FeatureToggleSwitch
value={value}
projectId={projectId}
featureName={feature?.name}
environmentName={name}
onToggle={onToggle}
/>
<ConditionallyRender
condition={hasWarning}
show={<VariantsWarningTooltip />}
/>
</StyledSwitchContainer>
);
},
sortType: 'boolean',
filterName: name,
filterParsing: (value: boolean) =>
value ? 'enabled' : 'disabled',
};
}),

return (
<StyledSwitchContainer hasWarning={hasWarning}>
<FeatureToggleSwitch
value={value}
projectId={projectId}
featureName={feature?.name}
environmentName={name}
onToggle={onToggle}
/>
<ConditionallyRender
condition={hasWarning}
show={<VariantsWarningTooltip />}
/>
</StyledSwitchContainer>
);
},
sortType: 'boolean',
filterName: name,
filterParsing: (value: boolean) =>
value ? 'enabled' : 'disabled',
})),
{
id: 'Actions',
maxWidth: 56,
Expand Down Expand Up @@ -477,7 +487,6 @@ export const ProjectFeatureToggles = ({
);

const getRowId = useCallback((row: any) => row.name, []);

const {
allColumns,
headerGroups,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
import { useRef } from 'react';

import { CreateFeatureStrategySchema } from 'openapi';
/**
* Don't revalidate if array content didn't change.
* Needed for `columns` memo optimization.
*/
export const useEnvironmentsRef = (environments: string[] = []) => {
const ref = useRef<string[]>(environments);

if (environments?.join('') !== ref.current?.join('')) {
ref.current = environments;
export type ProjectEnvironmentType = {
environment: string;
defaultStrategy: CreateFeatureStrategySchema | null;
};
export const useEnvironmentsRef = (
environments: Array<string | ProjectEnvironmentType> = []
): string[] => {
let names: string[];
if (
environments &&
environments.length > 0 &&
typeof environments[0] !== 'string'
) {
names = environments.map(
env => (env as ProjectEnvironmentType).environment
);
} else {
names = environments as string[];
}
const ref = useRef<Array<string>>(names);
if (names.join('') !== ref.current?.join('')) {
ref.current = names;
}

return ref.current;
Expand Down
53 changes: 50 additions & 3 deletions src/lib/db/project-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import {
IProjectSettings,
IProjectSettingsRow,
IProjectStore,
ProjectEnvironment,
} from '../types/stores/project-store';
import { DEFAULT_ENV } from '../util';
import metricsHelper from '../util/metrics-helper';
import { DB_TIME } from '../metric-events';
import EventEmitter from 'events';
import { Db } from './db';
import Raw = Knex.Raw;
import { CreateFeatureStrategySchema } from '../openapi';

const COLUMNS = [
'id',
Expand All @@ -35,6 +37,7 @@ const COLUMNS = [
const TABLE = 'projects';
const SETTINGS_COLUMNS = ['project_mode', 'default_stickiness'];
const SETTINGS_TABLE = 'project_settings';
const PROJECT_ENVIRONMENTS = 'project_environments';

export interface IEnvironmentProjectLink {
environmentName: string;
Expand Down Expand Up @@ -350,8 +353,8 @@ class ProjectStore implements IProjectStore {
.ignore();
}

async getEnvironmentsForProject(id: string): Promise<string[]> {
return this.db('project_environments')
async getEnvironmentsForProject(id: string): Promise<ProjectEnvironment[]> {
const rows = await this.db(PROJECT_ENVIRONMENTS)
.where({
project_id: id,
})
Expand All @@ -362,7 +365,12 @@ class ProjectStore implements IProjectStore {
)
.orderBy('environments.sort_order', 'asc')
.orderBy('project_environments.environment_name', 'asc')
.pluck('project_environments.environment_name');
.returning([
'project_environments.environment_name',
'project_environments.default_strategy',
]);

return rows.map(this.mapProjectEnvironmentRow);
}

async getMembersCount(): Promise<IProjectMembersCount[]> {
Expand Down Expand Up @@ -495,6 +503,32 @@ class ProjectStore implements IProjectStore {
.where({ project: projectId });
}

async getDefaultStrategy(
projectId: string,
environment: string,
): Promise<CreateFeatureStrategySchema | null> {
const rows = await this.db(PROJECT_ENVIRONMENTS)
.select('default_strategy')
.where({ project_id: projectId, environment_name: environment });

return rows.length > 0 ? rows[0].default_strategy : null;
}

async updateDefaultStrategy(
projectId: string,
environment: string,
strategy: CreateFeatureStrategySchema,
): Promise<CreateFeatureStrategySchema> {
const rows = await this.db(PROJECT_ENVIRONMENTS)
.update({
default_strategy: strategy,
})
.where({ project_id: projectId, environment_name: environment })
.returning('default_strategy');

return rows[0].default_strategy;
}

async count(): Promise<number> {
return this.db
.from(TABLE)
Expand Down Expand Up @@ -534,6 +568,19 @@ class ProjectStore implements IProjectStore {
defaultStickiness: row.default_stickiness || 'default',
};
}

mapProjectEnvironmentRow(row: {
environment_name: string;
default_strategy: CreateFeatureStrategySchema;
}): ProjectEnvironment {
return {
environment: row.environment_name,
defaultStrategy:
row.default_strategy === null
? undefined
: row.default_strategy,
};
}
}

export default ProjectStore;
18 changes: 9 additions & 9 deletions src/lib/openapi/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { OpenAPIV3 } from 'openapi-types';
import {
adminFeaturesQuerySchema,
addonCreateUpdateSchema,
addonParameterSchema,
addonSchema,
addonCreateUpdateSchema,
addonsSchema,
addonTypeSchema,
adminFeaturesQuerySchema,
apiTokenSchema,
apiTokensSchema,
applicationSchema,
Expand Down Expand Up @@ -35,8 +35,8 @@ import {
environmentsSchema,
eventSchema,
eventsSchema,
exportResultSchema,
exportQuerySchema,
exportResultSchema,
featureEnvironmentMetricsSchema,
featureEnvironmentSchema,
featureEventsSchema,
Expand All @@ -58,6 +58,9 @@ import {
healthOverviewSchema,
healthReportSchema,
idSchema,
importTogglesSchema,
importTogglesValidateItemSchema,
importTogglesValidateSchema,
instanceAdminStatsSchema,
legalValueSchema,
loginSchema,
Expand All @@ -79,20 +82,21 @@ import {
playgroundStrategySchema,
profileSchema,
projectEnvironmentSchema,
projectOverviewSchema,
projectSchema,
projectsSchema,
projectStatsSchema,
proxyClientSchema,
proxyFeatureSchema,
proxyFeaturesSchema,
publicSignupTokenCreateSchema,
projectStatsSchema,
publicSignupTokenSchema,
publicSignupTokensSchema,
publicSignupTokenUpdateSchema,
pushVariantsSchema,
resetPasswordSchema,
requestsPerSecondSchema,
requestsPerSecondSegmentedSchema,
resetPasswordSchema,
roleSchema,
sdkContextSchema,
searchEventsSchema,
Expand Down Expand Up @@ -131,10 +135,6 @@ import {
variantSchema,
variantsSchema,
versionSchema,
projectOverviewSchema,
importTogglesSchema,
importTogglesValidateSchema,
importTogglesValidateItemSchema,
} from './spec';
import { IServerOption } from '../types';
import { mapValues, omitKeys } from '../util';
Expand Down
8 changes: 6 additions & 2 deletions src/lib/openapi/spec/health-overview-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { constraintSchema } from './constraint-schema';
import { environmentSchema } from './environment-schema';
import { featureEnvironmentSchema } from './feature-environment-schema';
import { projectStatsSchema } from './project-stats-schema';
import { createFeatureStrategySchema } from './create-feature-strategy-schema';
import { projectEnvironmentSchema } from './project-environment-schema';

export const healthOverviewSchema = {
$id: '#/components/schemas/healthOverviewSchema',
Expand Down Expand Up @@ -47,7 +49,7 @@ export const healthOverviewSchema = {
environments: {
type: 'array',
items: {
type: 'string',
$ref: '#/components/schemas/projectEnvironmentSchema',
},
},
features: {
Expand All @@ -71,8 +73,10 @@ export const healthOverviewSchema = {
},
components: {
schemas: {
constraintSchema,
environmentSchema,
projectEnvironmentSchema,
createFeatureStrategySchema,
constraintSchema,
featureSchema,
featureEnvironmentSchema,
overrideSchema,
Expand Down
10 changes: 9 additions & 1 deletion src/lib/openapi/spec/project-environment-schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FromSchema } from 'json-schema-to-ts';
import { createFeatureStrategySchema } from './create-feature-strategy-schema';

export const projectEnvironmentSchema = {
$id: '#/components/schemas/projectEnvironmentSchema',
Expand All @@ -12,8 +13,15 @@ export const projectEnvironmentSchema = {
changeRequestsEnabled: {
type: 'boolean',
},
defaultStrategy: {
$ref: '#/components/schemas/createFeatureStrategySchema',
},
},
components: {
schemas: {
createFeatureStrategySchema,
},
},
components: {},
} as const;

export type ProjectEnvironmentSchema = FromSchema<
Expand Down