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

Fix/variant hashing pg10compat (#5218)

Co-authored-by: Ivar Conradi Østhus <ivar@getunleash.io>
Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com>
Co-authored-by: Simon Hornby <liquidwicked64@gmail.com>
This commit is contained in:
Christopher Kolstad 2023-10-30 14:41:37 +01:00 committed by GitHub
parent 1e613d8224
commit 264b32be79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 79 additions and 220 deletions

View File

@ -53,8 +53,6 @@ const UsersList = () => {
const [delUser, setDelUser] = useState<IUser>();
const { planUsers, isBillingUsers } = useUsersPlan(users);
const accessOverviewEnabled = useUiFlag('accessOverview');
const [searchValue, setSearchValue] = useState('');
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
@ -271,14 +269,6 @@ const UsersList = () => {
onChange={setSearchValue}
/>
<PageHeader.Divider />
<ConditionallyRender
condition={
isEnterprise() &&
Boolean(accessOverviewEnabled)
}
show={() => (
<>
<Tooltip
title='Exports user access information'
arrow
@ -288,9 +278,6 @@ const UsersList = () => {
<Download />
</IconButton>
</Tooltip>
</>
)}
/>
<Button
variant='contained'
color='primary'

View File

@ -65,7 +65,6 @@ export type UiFlags = {
doraMetrics?: boolean;
variantTypeNumber?: boolean;
privateProjects?: boolean;
accessOverview?: boolean;
dependentFeatures?: boolean;
banners?: boolean;
disableEnvsOnRevive?: boolean;

View File

@ -139,7 +139,6 @@
"knex": "^2.5.1",
"lodash.get": "^4.4.2",
"lodash.groupby": "^4.6.0",
"lodash.isequal": "^4.5.0",
"lodash.sortby": "^4.7.0",
"log4js": "^6.0.0",
"make-fetch-happen": "^11.0.0",

View File

@ -71,7 +71,6 @@ exports[`should create default config 1`] = `
},
"flagResolver": FlagResolver {
"experiments": {
"accessOverview": false,
"anonymiseEventLog": false,
"banners": false,
"caseInsensitiveInOperators": false,

View File

@ -1,67 +0,0 @@
import sortBy from 'lodash.sortby';
interface Difference {
index: (string | number)[];
reason: string;
valueA: any;
valueB: any;
}
export function deepDiff(arr1: any[], arr2: any[]): Difference[] | null {
const diff: Difference[] = [];
function compare(a: any, b: any, parentIndex: (string | number)[]): void {
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {
diff.push({
index: parentIndex,
reason: 'Different lengths',
valueA: a,
valueB: b,
});
} else {
const sortedA = sortBy(a, 'name');
const sortedB = sortBy(b, 'name');
for (let i = 0; i < sortedA.length; i++) {
compare(sortedA[i], sortedB[i], parentIndex.concat(i));
}
}
} else if (
typeof a === 'object' &&
a !== null &&
typeof b === 'object' &&
b !== null
) {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (
keysA.length !== keysB.length ||
!keysA.every((key) => keysB.includes(key))
) {
diff.push({
index: parentIndex,
reason: 'Different keys',
valueA: a,
valueB: b,
});
} else {
for (const key of keysA) {
compare(a[key], b[key], parentIndex.concat(key));
}
}
} else if (a !== b) {
diff.push({
index: parentIndex,
reason: 'Different values',
valueA: a,
valueB: b,
});
}
}
compare(arr1, arr2, []);
return diff.length > 0 ? diff : null;
}

View File

@ -102,8 +102,6 @@ import { IPrivateProjectChecker } from '../private-project/privateProjectChecker
import { IDependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model-type';
import EventService from '../../services/event-service';
import { DependentFeaturesService } from '../dependent-features/dependent-features-service';
import isEqual from 'lodash.isequal';
import { deepDiff } from './deep-diff';
interface IFeatureContext {
featureName: string;
@ -1058,22 +1056,6 @@ class FeatureToggleService {
await this.featureToggleStore.getPlaygroundFeatures(query),
]);
const equal = isEqual(
featuresFromClientStore,
featuresFromFeatureToggleStore,
);
if (!equal) {
const difference = deepDiff(
featuresFromClientStore,
featuresFromFeatureToggleStore,
);
this.logger.warn(
'getPlaygroundFeatures: features from client-feature-toggle-store is not equal to features from feature-toggle-store',
difference,
);
}
const features = this.flagResolver.isEnabled('separateAdminClientApi')
? featuresFromFeatureToggleStore
: featuresFromClientStore;
@ -1109,22 +1091,6 @@ class FeatureToggleService {
),
]);
const equal = isEqual(
featuresFromClientStore,
featuresFromFeatureToggleStore,
);
if (!equal) {
const difference = deepDiff(
featuresFromClientStore,
featuresFromFeatureToggleStore,
);
this.logger.warn(
'getFeatureToggles: features from client-feature-toggle-store is not equal to features from feature-toggle-store diff',
difference,
);
}
const features = this.flagResolver.isEnabled('separateAdminClientApi')
? featuresFromFeatureToggleStore
: featuresFromClientStore;

View File

@ -342,11 +342,17 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
let selectColumns = ['features_view.*'] as (string | Raw<any>)[];
if (this.flagResolver.isEnabled('useLastSeenRefactor')) {
query.leftJoin(
'last_seen_at_metrics',
query.leftJoin('last_seen_at_metrics', function () {
this.on(
'last_seen_at_metrics.environment',
'=',
'features_view.environment_name',
).andOn(
'last_seen_at_metrics.feature_name',
'=',
'features_view.name',
);
});
// Override feature view for now
selectColumns.push(
'last_seen_at_metrics.last_seen_at as env_last_seen_at',

View File

@ -1,79 +0,0 @@
import { deepDiff } from '../deep-diff'; // Import the deepDiff function
describe('deepDiff', () => {
test('should sort arrays by name before comparing', () => {
// Define two arrays that are identical except for the order of elements
const array1 = [
{ name: 'b', value: 2 },
{ name: 'a', value: 1 },
];
const array2 = [
{ name: 'a', value: 1 },
{ name: 'b', value: 2 },
];
// If the function correctly sorts before comparing, there should be no differences
const result = deepDiff(array1, array2);
// Assert that there is no difference
expect(result).toBeNull();
});
it('should return null for equal arrays', () => {
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
expect(deepDiff(arr1, arr2)).toBe(null);
});
it('should find differences in arrays with different lengths', () => {
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3, 4];
expect(deepDiff(arr1, arr2)).toEqual([
{
index: [],
reason: 'Different lengths',
valueA: arr1,
valueB: arr2,
},
]);
});
it('should find differences in arrays with different values', () => {
const arr1 = [1, 2, 3];
const arr2 = [1, 4, 3];
expect(deepDiff(arr1, arr2)).toEqual([
{
index: [1],
reason: 'Different values',
valueA: 2,
valueB: 4,
},
]);
});
it('should find differences in arrays with different keys in objects', () => {
const arr1 = [{ a: 1 }, { b: 2 }];
const arr2 = [{ a: 1 }, { c: 2 }];
expect(deepDiff(arr1, arr2)).toEqual([
{
index: [1],
reason: 'Different keys',
valueA: { b: 2 },
valueB: { c: 2 },
},
]);
});
it('should handle nested differences in objects', () => {
const arr1 = [{ a: { b: 1 } }, { c: { d: 2 } }];
const arr2 = [{ a: { b: 1 } }, { c: { d: 3 } }];
expect(deepDiff(arr1, arr2)).toEqual([
{
index: [1, 'c', 'd'],
reason: 'Different values',
valueA: 2,
valueB: 3,
},
]);
});
});

View File

@ -154,3 +154,53 @@ test('response should include last seen at per environment for multiple environm
expect(production.name).toBe('production');
expect(production.lastSeenAt).toEqual('2023-10-01T12:34:56.000Z');
});
test('response should include last seen at per environment correctly for a single toggle /api/admin/project/:projectId/features/:featureName', async () => {
const featureName = 'multiple-environment-last-seen-at-single-toggle';
await app.createFeature(featureName);
await setupLastSeenAtTest(`${featureName}1`);
await setupLastSeenAtTest(`${featureName}2`);
await setupLastSeenAtTest(`${featureName}3`);
await setupLastSeenAtTest(`${featureName}4`);
await setupLastSeenAtTest(`${featureName}5`);
await insertLastSeenAt(
featureName,
db.rawDatabase,
'default',
'2023-08-01 12:30:56',
);
await insertLastSeenAt(
featureName,
db.rawDatabase,
'development',
'2023-08-01 12:30:56',
);
await insertLastSeenAt(
featureName,
db.rawDatabase,
'production',
'2023-08-01 12:30:56',
);
const { body } = await app.request
.get(`/api/admin/projects/default/features/${featureName}`)
.expect(200);
expect(body.environments).toMatchObject([
{
name: 'default',
lastSeenAt: '2023-08-01T12:30:56.000Z',
},
{
name: 'development',
lastSeenAt: '2023-08-01T12:30:56.000Z',
},
{
name: 'production',
lastSeenAt: '2023-08-01T12:30:56.000Z',
},
]);
});

View File

@ -26,7 +26,6 @@ export type IFlagKey =
| 'featureNamingPattern'
| 'doraMetrics'
| 'variantTypeNumber'
| 'accessOverview'
| 'privateProjects'
| 'dependentFeatures'
| 'disableMetrics'
@ -137,10 +136,6 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_PRIVATE_PROJECTS,
false,
),
accessOverview: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_ACCESS_OVERVIEW,
false,
),
disableMetrics: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_DISABLE_METRICS,
false,

View File

@ -7,7 +7,7 @@ exports.up = function(db, cb) {
PRIMARY KEY (day, environment)
);
CREATE FUNCTION unleash_update_stat_environment_changes_counter() RETURNS trigger AS $unleash_update_changes_counter$
CREATE OR REPLACE FUNCTION unleash_update_stat_environment_changes_counter() RETURNS trigger AS $unleash_update_changes_counter$
BEGIN
IF NEW.environment IS NOT NULL THEN
INSERT INTO stat_environment_updates(day, environment, updates) SELECT DATE_TRUNC('Day', NEW.created_at), NEW.environment, 1 ON CONFLICT (day, environment) DO UPDATE SET updates = stat_environment_updates.updates + 1;
@ -19,7 +19,7 @@ exports.up = function(db, cb) {
CREATE TRIGGER unleash_update_stat_environment_changes
AFTER INSERT ON events
FOR EACH ROW EXECUTE FUNCTION unleash_update_stat_environment_changes_counter();
FOR EACH ROW EXECUTE PROCEDURE unleash_update_stat_environment_changes_counter();
`, cb);
};

View File

@ -42,7 +42,6 @@ process.nextTick(async () => {
doraMetrics: true,
variantTypeNumber: true,
privateProjects: true,
accessOverview: true,
dependentFeatures: true,
useLastSeenRefactor: true,
disableEnvsOnRevive: true,

View File

@ -326,10 +326,15 @@ export const insertLastSeenAt = async (
environment: string = 'default',
date: string = '2023-10-01 12:34:56',
): Promise<string> => {
try {
await db.raw(`INSERT INTO last_seen_at_metrics (feature_name, environment, last_seen_at)
VALUES ('${featureName}', '${environment}', '${date}');`);
return date;
} catch (err) {
console.log(err);
return Promise.resolve('');
}
};
export const insertFeatureEnvironmentsLastSeen = async (