-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[x-license] Add support for plan version #13459
Changes from all commits
190a18e
1706016
93a4e9a
8b4369e
f815941
3ccba21
46cf0d5
da18b3f
ac5e804
300e0b5
e62b985
f4b0ed5
b5cec8b
88056dd
508c785
c6e7a17
1a26ce8
e572ab4
c67d0f9
001a411
4fffe6a
c04fa6b
f19b30e
a0da60e
a19da9d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -9,9 +9,10 @@ describe('License: generateLicense', () => { | |||||
orderNumber: 'MUI-123', | ||||||
scope: 'pro', | ||||||
licensingModel: 'subscription', | ||||||
planVersion: 'initial', | ||||||
}), | ||||||
).to.equal( | ||||||
'b2b2ea9c6fd846e11770da3c795d6f63Tz1NVUktMTIzLEU9MTU5MTcyMzg3OTA2MixTPXBybyxMTT1zdWJzY3JpcHRpb24sS1Y9Mg==', | ||||||
'e8fad422a82720084ec67dd693f08056Tz1NVUktMTIzLEU9MTU5MTcyMzg3OTA2MixTPXBybyxMTT1zdWJzY3JpcHRpb24sUFY9aW5pdGlhbCxLVj0y', | ||||||
); | ||||||
}); | ||||||
|
||||||
|
@@ -22,9 +23,10 @@ describe('License: generateLicense', () => { | |||||
orderNumber: 'MUI-123', | ||||||
scope: 'premium', | ||||||
licensingModel: 'subscription', | ||||||
planVersion: 'initial', | ||||||
}), | ||||||
).to.equal( | ||||||
'ac8d20b4ecd1f919157f3713f8ba1651Tz1NVUktMTIzLEU9MTU5MTcyMzg3OTA2MixTPXByZW1pdW0sTE09c3Vic2NyaXB0aW9uLEtWPTI=', | ||||||
'8ca0384bfb92ec214d4cd72483f5110bTz1NVUktMTIzLEU9MTU5MTcyMzg3OTA2MixTPXByZW1pdW0sTE09c3Vic2NyaXB0aW9uLFBWPWluaXRpYWwsS1Y9Mg==', | ||||||
); | ||||||
}); | ||||||
|
||||||
|
@@ -35,9 +37,10 @@ describe('License: generateLicense', () => { | |||||
orderNumber: 'MUI-123', | ||||||
scope: 'pro', | ||||||
licensingModel: 'subscription', | ||||||
planVersion: 'initial', | ||||||
}), | ||||||
).to.equal( | ||||||
'b2b2ea9c6fd846e11770da3c795d6f63Tz1NVUktMTIzLEU9MTU5MTcyMzg3OTA2MixTPXBybyxMTT1zdWJzY3JpcHRpb24sS1Y9Mg==', | ||||||
'e8fad422a82720084ec67dd693f08056Tz1NVUktMTIzLEU9MTU5MTcyMzg3OTA2MixTPXBybyxMTT1zdWJzY3JpcHRpb24sUFY9aW5pdGlhbCxLVj0y', | ||||||
); | ||||||
}); | ||||||
|
||||||
|
@@ -48,9 +51,38 @@ describe('License: generateLicense', () => { | |||||
orderNumber: 'MUI-123', | ||||||
scope: 'pro', | ||||||
licensingModel: 'perpetual', | ||||||
planVersion: 'initial', | ||||||
}), | ||||||
).to.equal( | ||||||
'b16edd8e6bc83293a723779a259f520cTz1NVUktMTIzLEU9MTU5MTcyMzg3OTA2MixTPXBybyxMTT1wZXJwZXR1YWwsS1Y9Mg==', | ||||||
'aaf2e3c60b06199962fbbab985843d97Tz1NVUktMTIzLEU9MTU5MTcyMzg3OTA2MixTPXBybyxMTT1wZXJwZXR1YWwsUFY9aW5pdGlhbCxLVj0y', | ||||||
); | ||||||
}); | ||||||
|
||||||
it('should generate subscription Pro license when `planVersion: "Q3-2024"`', () => { | ||||||
expect( | ||||||
generateLicense({ | ||||||
expiryDate: new Date(1591723879062), | ||||||
orderNumber: 'MUI-123', | ||||||
scope: 'pro', | ||||||
licensingModel: 'subscription', | ||||||
planVersion: 'Q3-2024', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm used to:
Suggested change
I wonder if we should change this. But I guess it's up to the license key generator logic. |
||||||
}), | ||||||
).to.equal( | ||||||
'4adf08e54d606215809064d1d31b6b39Tz1NVUktMTIzLEU9MTU5MTcyMzg3OTA2MixTPXBybyxMTT1zdWJzY3JpcHRpb24sUFY9UTMtMjAyNCxLVj0y', | ||||||
); | ||||||
}); | ||||||
|
||||||
it('should generate subscription Premium license when `planVersion: "Q3-2024"`', () => { | ||||||
expect( | ||||||
generateLicense({ | ||||||
expiryDate: new Date(1591723879062), | ||||||
orderNumber: 'MUI-123', | ||||||
scope: 'premium', | ||||||
licensingModel: 'subscription', | ||||||
planVersion: 'Q3-2024', | ||||||
}), | ||||||
).to.equal( | ||||||
'b76c2067275b3b566fcae1d28ad23c91Tz1NVUktMTIzLEU9MTU5MTcyMzg3OTA2MixTPXByZW1pdW0sTE09c3Vic2NyaXB0aW9uLFBWPVEzLTIwMjQsS1Y9Mg==', | ||||||
); | ||||||
}); | ||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ import { | |
LicenseInfo, | ||
generateLicense, | ||
Unstable_LicenseInfoProvider as LicenseInfoProvider, | ||
MuiCommercialPackageName, | ||
} from '@mui/x-license'; | ||
import { sharedLicenseStatuses } from './useLicenseVerifier'; | ||
import { generateReleaseInfo } from '../verifyLicense'; | ||
|
@@ -14,9 +15,9 @@ const oneDayInMS = 1000 * 60 * 60 * 24; | |
const releaseDate = new Date(3000, 0, 0, 0, 0, 0, 0); | ||
const RELEASE_INFO = generateReleaseInfo(releaseDate); | ||
|
||
function TestComponent() { | ||
const licesenStatus = useLicenseVerifier('x-date-pickers-pro', RELEASE_INFO); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, I wrote licesen 🤪 |
||
return <div data-testid="status">Status: {licesenStatus.status}</div>; | ||
function TestComponent(props: { packageName?: MuiCommercialPackageName }) { | ||
const licenseStatus = useLicenseVerifier(props.packageName || 'x-date-pickers-pro', RELEASE_INFO); | ||
return <div data-testid="status">Status: {licenseStatus.status}</div>; | ||
} | ||
|
||
describe('useLicenseVerifier', function test() { | ||
|
@@ -63,6 +64,7 @@ describe('useLicenseVerifier', function test() { | |
licensingModel: 'perpetual', | ||
orderNumber: '12345', | ||
scope: 'pro', | ||
planVersion: 'initial', | ||
}); | ||
|
||
LicenseInfo.setLicenseKey(''); | ||
|
@@ -88,6 +90,7 @@ describe('useLicenseVerifier', function test() { | |
orderNumber: 'MUI-123', | ||
scope: 'pro', | ||
licensingModel: 'subscription', | ||
planVersion: 'initial', | ||
}); | ||
LicenseInfo.setLicenseKey(expiredLicenseKey); | ||
|
||
|
@@ -105,5 +108,81 @@ describe('useLicenseVerifier', function test() { | |
]); | ||
expect(actualErrorMsg).to.match(/MUI X: Expired license key/); | ||
}); | ||
|
||
it('should throw if the license is not covering charts and tree-view', () => { | ||
// Avoid Karma "Invalid left-hand side in assignment" SyntaxError | ||
// eslint-disable-next-line no-useless-concat | ||
process.env['NODE_' + 'ENV'] = 'development'; | ||
|
||
const licenseKey = generateLicense({ | ||
expiryDate: new Date(3001, 0, 0, 0, 0, 0, 0), | ||
orderNumber: 'MUI-123', | ||
scope: 'pro', | ||
licensingModel: 'subscription', | ||
planVersion: 'initial', | ||
}); | ||
|
||
LicenseInfo.setLicenseKey(licenseKey); | ||
|
||
expect(() => { | ||
render(<TestComponent packageName={'x-charts-pro'} />); | ||
}).to.toErrorDev(['MUI X: Product not covered by plan.']); | ||
|
||
expect(() => { | ||
render(<TestComponent packageName={'x-tree-view-pro'} />); | ||
}).to.toErrorDev(['MUI X: Product not covered by plan.']); | ||
}); | ||
|
||
it('should not throw if the license is covering charts and tree-view', () => { | ||
// Avoid Karma "Invalid left-hand side in assignment" SyntaxError | ||
// eslint-disable-next-line no-useless-concat | ||
process.env['NODE_' + 'ENV'] = 'development'; | ||
|
||
const licenseKey = generateLicense({ | ||
expiryDate: new Date(3001, 0, 0, 0, 0, 0, 0), | ||
orderNumber: 'MUI-123', | ||
scope: 'pro', | ||
licensingModel: 'subscription', | ||
planVersion: 'Q3-2024', | ||
}); | ||
|
||
LicenseInfo.setLicenseKey(licenseKey); | ||
|
||
expect(() => { | ||
render(<TestComponent packageName={'x-charts-pro'} />); | ||
michelengelen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}).not.toErrorDev(); | ||
|
||
expect(() => { | ||
render(<TestComponent packageName={'x-tree-view-pro'} />); | ||
}).not.toErrorDev(); | ||
}); | ||
|
||
it('should not throw for existing pro and premium packages', () => { | ||
// Avoid Karma "Invalid left-hand side in assignment" SyntaxError | ||
// eslint-disable-next-line no-useless-concat | ||
process.env['NODE_' + 'ENV'] = 'development'; | ||
|
||
const licenseKey = generateLicense({ | ||
expiryDate: new Date(3001, 0, 0, 0, 0, 0, 0), | ||
orderNumber: 'MUI-123', | ||
scope: 'premium', | ||
licensingModel: 'subscription', | ||
planVersion: 'Q3-2024', | ||
}); | ||
|
||
LicenseInfo.setLicenseKey(licenseKey); | ||
|
||
expect(() => { | ||
render(<TestComponent packageName={'x-data-grid-pro'} />); | ||
}).not.toErrorDev(); | ||
|
||
expect(() => { | ||
render(<TestComponent packageName={'x-data-grid-premium'} />); | ||
}).not.toErrorDev(); | ||
|
||
expect(() => { | ||
render(<TestComponent packageName={'x-date-pickers-pro'} />); | ||
}).not.toErrorDev(); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,3 +1,20 @@ | ||||||
export const LICENSE_SCOPES = ['pro', 'premium'] as const; | ||||||
export const PRODUCT_SCOPES = ['data-grid', 'date-pickers', 'charts', 'tree-view'] as const; | ||||||
export const PLAN_VERSIONS = ['initial', 'Q3-2024'] as const; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not convinced about "initial". To me, what we have today is
Suggested change
the last time we changed the plan. |
||||||
|
||||||
export type LicenseScope = (typeof LICENSE_SCOPES)[number]; | ||||||
export type ProductScope = (typeof PRODUCT_SCOPES)[number]; | ||||||
export type PlanVersion = (typeof PLAN_VERSIONS)[number]; | ||||||
|
||||||
export const extractProductScope = (packageName: string): ProductScope => { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A small detail, default function convention we use is
Suggested change
|
||||||
// extract the part between "x-" and "-pro"/"-premium" | ||||||
const regex = /x-(.*?)(-pro|-premium)?$/; | ||||||
const match = packageName.match(regex); | ||||||
return match![1] as ProductScope; | ||||||
michelengelen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
}; | ||||||
|
||||||
export const extractAcceptedScopes = (packageName: string): readonly LicenseScope[] => { | ||||||
return packageName.includes('premium') | ||||||
? LICENSE_SCOPES.filter((scope) => scope.includes('premium')) | ||||||
: LICENSE_SCOPES; | ||||||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like these two errors
OutOfScope
+ProductNotCovered
are the same error. Should we merge them? As for the need to show different error messages, I think this should be done through the meta data.If we want to keep them distinct, I would make their name match, e.g.
OutOfScope -> PlanScopeNotCovered
ProductNotCovered -> ProductScopeNotCovered
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I advocated to change
ProductNotCovered
into a more specificNotAvailableInInitialProPlan
in #13568, so that we can display a more precise error in the doc.We could indeed rename
OutOfScope
which is super broad right now.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There will be more cases like this in the future of having a paid for one of the MUI X plans, and trying to use a different plan. I don't see creating a new error type each time scaling.
So It feels like this should be one error type and use the metadata to have a precise error in the console.
For the docs, I agree, I can see why we need to separate the cases: (a.) buying pro and using pro without working is a different confusion source than (b.) buying pro and trying to use premium.
But anyway, no strong push on my end, more of a feeling that in the code, it feels strange.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The status is only used to do some sort of switch and display a warning message.
So I don't see how metadata would be more or less scalable than a different status message.
Even if we end up with 20 different statuses, the big part is the switch of conditions.