Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/notification-services-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add optional `platformVersion` property to `NotificationServicesController` `FeatureAnnouncementEnv` type ([#6568](https://github.com/MetaMask/core/pull/6568))
- Filtering logic to filter feature annonucements by version number ([#6568](https://github.com/MetaMask/core/pull/6568))
- Add package `semver@^7.7.2` to handle semver version comparisons for announcement notification filtering ([#6568](https://github.com/MetaMask/core/pull/6568))

## [18.1.0]

### Added
Expand Down
2 changes: 2 additions & 0 deletions packages/notification-services-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"bignumber.js": "^9.1.2",
"firebase": "^11.2.0",
"loglevel": "^1.8.1",
"semver": "^7.7.2",
"uuid": "^8.3.2"
},
"devDependencies": {
Expand All @@ -126,6 +127,7 @@
"@metamask/profile-sync-controller": "^25.0.0",
"@types/jest": "^27.4.1",
"@types/readable-stream": "^2.3.0",
"@types/semver": "^7",
"contentful": "^10.15.0",
"deepmerge": "^4.2.2",
"jest": "^27.5.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ type FeatureAnnouncementEnv = {
spaceId: string;
accessToken: string;
platform: 'extension' | 'mobile';
platformVersion?: string;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jest.mock('@contentful/rich-text-html-renderer', () => ({
const featureAnnouncementsEnv = {
spaceId: ':space_id',
accessToken: ':access_token',
platform: 'extension',
platform: 'extension' as 'extension' | 'mobile',
};

describe('Feature Announcement Notifications', () => {
Expand All @@ -44,7 +44,7 @@ describe('Feature Announcement Notifications', () => {
await assertEnvEmpty({ platform: null as MockedType });
await assertEnvEmpty({ spaceId: null as MockedType });
await assertEnvEmpty({ accessToken: '' });
await assertEnvEmpty({ platform: '' });
await assertEnvEmpty({ platform: '' as MockedType });
await assertEnvEmpty({ spaceId: '' });
});

Expand Down Expand Up @@ -97,6 +97,106 @@ describe('Feature Announcement Notifications', () => {

expect(resultNotification.data).toBeDefined();
});

const testPlatforms = [
{
platform: 'extension' as const,
versionField: 'extensionMinimumVersionNumber' as const,
},
{
platform: 'mobile' as const,
versionField: 'mobileMinimumVersionNumber' as const,
},
];

describe.each(testPlatforms)(
'Feature Announcement $platform filtering',
({ platform, versionField }) => {
const arrangeAct = async (
minimumVersion: string | undefined,
platformVersion: string | undefined,
) => {
const apiResponse = createMockFeatureAnnouncementAPIResult();
if (apiResponse.items && apiResponse.items[0]) {
apiResponse.items[0].fields.extensionMinimumVersionNumber = undefined;
apiResponse.items[0].fields.mobileMinimumVersionNumber = undefined;
if (minimumVersion !== undefined) {
apiResponse.items[0].fields[versionField] = minimumVersion;
}
}

const mockEndpoint = mockFetchFeatureAnnouncementNotifications({
status: 200,
body: apiResponse,
});

const notifications = await getFeatureAnnouncementNotifications({
...featureAnnouncementsEnv,
platform,
platformVersion,
});

mockEndpoint.done();
return notifications;
};

const testCases = [
{
name: 'should show notifications when platform version meets minimum requirement',
minimumVersion: '1.0.0',
platformVersion: '2.0.0',
expectedLength: 1,
},
{
name: 'should show notifications when platform version equals minimum requirement',
minimumVersion: '1.0.0',
platformVersion: '1.0.0',
expectedLength: 1,
},
{
name: 'should hide notifications when platform version is below minimum requirement',
minimumVersion: '3.0.0',
platformVersion: '2.0.0',
expectedLength: 0,
},
{
name: 'should show notifications when no platform version is provided',
minimumVersion: '2.0.0',
platformVersion: undefined,
expectedLength: 1,
},
{
name: 'should show notifications when no minimum version is specified for the platform',
minimumVersion: undefined,
platformVersion: '1.0.0',
expectedLength: 1,
},
{
name: 'should handle invalid version strings gracefully',
minimumVersion: 'invalid-version',
platformVersion: '2.0.0',
expectedLength: 0,
},
{
name: 'should handle invalid platform version gracefully',
minimumVersion: '2.0.0',
platformVersion: 'invalid-version',
expectedLength: 0,
},
];

it.each(testCases)(
'$name',
async ({ minimumVersion, platformVersion, expectedLength }) => {
const notifications = await arrangeAct(
minimumVersion,
platformVersion,
);
expect(notifications).toHaveLength(expectedLength);
},
);
},
);
});

describe('getFeatureAnnouncementUrl', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { documentToHtmlString } from '@contentful/rich-text-html-renderer';
import type { Entry, Asset, EntryCollection } from 'contentful';
import { gte } from 'semver';

import { TRIGGER_TYPES } from '../constants/notification-schema';
import { processFeatureAnnouncement } from '../processors/process-feature-announcement';
Expand Down Expand Up @@ -27,7 +28,8 @@ export const FEATURE_ANNOUNCEMENT_URL = `${FEATURE_ANNOUNCEMENT_API}?access_toke
type Env = {
spaceId: string;
accessToken: string;
platform: string;
platform: 'extension' | 'mobile';
platformVersion?: string;
};

/**
Expand Down Expand Up @@ -148,7 +150,26 @@ const fetchFeatureAnnouncementNotifications = async (
return notification;
});

return rawNotifications;
const versionKey = {
extension: 'extensionMinimumVersionNumber',
mobile: 'mobileMinimumVersionNumber',
} as const;

const filteredRawNotifications = rawNotifications.filter((n) => {
const notificationVersion = n.data?.[versionKey[env.platform]];
if (!env.platformVersion || !notificationVersion) {
return true;
}

try {
return gte(env.platformVersion, notificationVersion);
} catch {
// something went wrong filtering, do not show notif
return false;
}
});

return filteredRawNotifications;
};

/**
Expand Down
11 changes: 11 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4063,6 +4063,7 @@ __metadata:
"@metamask/utils": "npm:^11.4.2"
"@types/jest": "npm:^27.4.1"
"@types/readable-stream": "npm:^2.3.0"
"@types/semver": "npm:^7"
bignumber.js: "npm:^9.1.2"
contentful: "npm:^10.15.0"
deepmerge: "npm:^4.2.2"
Expand All @@ -4071,6 +4072,7 @@ __metadata:
jest-environment-jsdom: "npm:^27.5.1"
loglevel: "npm:^1.8.1"
nock: "npm:^13.3.1"
semver: "npm:^7.7.2"
ts-jest: "npm:^27.1.4"
typedoc: "npm:^0.24.8"
typedoc-plugin-missing-exports: "npm:^2.0.0"
Expand Down Expand Up @@ -13323,6 +13325,15 @@ __metadata:
languageName: node
linkType: hard

"semver@npm:^7.7.2":
version: 7.7.2
resolution: "semver@npm:7.7.2"
bin:
semver: bin/semver.js
checksum: 10/7a24cffcaa13f53c09ce55e05efe25cd41328730b2308678624f8b9f5fc3093fc4d189f47950f0b811ff8f3c3039c24a2c36717ba7961615c682045bf03e1dda
languageName: node
linkType: hard

"send@npm:0.19.0":
version: 0.19.0
resolution: "send@npm:0.19.0"
Expand Down
Loading