mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-13 21:53:43 +01:00
[web] Gate new reusable sharing links behind sharing-updates-new-link (#35412)
Ship the new share-project modal (behind sharing-updates) wired to the legacy token-based link sharing, and hide the new reusable-link functionality (anyone with the link / group links) behind a new sharing-updates-new-link split test. - With the flag off, the modal offers two options, "Only invited people" and "Via sharing links", both driven by publicAccessLevel; the reusable -link options, privileges dropdown and copy-sharing-link button are hidden. - Enable the flag in the acceptance tests that exercise those routes: a seeded split test in saas, splitTestOverrides in non-saas. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: thomas- <2176518+thomas-@users.noreply.github.com> GitOrigin-RevId: 3f34a2da5dbdc6f27ed389f9c25eea66143b8160
This commit is contained in:
committed by
Copybot
co-authored by
Claude Opus 4.8
copilot-swe-agent[bot]
thomas-
parent
e0d77271c2
commit
b8b78b6932
@@ -5,6 +5,7 @@ import CollaboratorsInviteController from './CollaboratorsInviteController.mjs'
|
||||
import { RateLimiter } from '../../infrastructure/RateLimiter.mjs'
|
||||
import RateLimiterMiddleware from '../Security/RateLimiterMiddleware.mjs'
|
||||
import AnalyticsRegistrationSourceMiddleware from '../Analytics/AnalyticsRegistrationSourceMiddleware.mjs'
|
||||
import SplitTestMiddleware from '../SplitTests/SplitTestMiddleware.mjs'
|
||||
|
||||
const rateLimiters = {
|
||||
inviteToProjectByProjectId: new RateLimiter(
|
||||
@@ -98,6 +99,9 @@ export default {
|
||||
webRouter.get(
|
||||
'/project/:Project_id/sharing-link',
|
||||
AuthenticationController.requireLogin(),
|
||||
SplitTestMiddleware.ensureSplitTestEnabledForUser(
|
||||
'sharing-updates-new-link'
|
||||
),
|
||||
AuthorizationMiddleware.ensureUserCanAdminProject,
|
||||
CollaboratorsInviteController.getSharingLink
|
||||
)
|
||||
@@ -105,6 +109,9 @@ export default {
|
||||
webRouter.post(
|
||||
'/project/:Project_id/sharing-link',
|
||||
AuthenticationController.requireLogin(),
|
||||
SplitTestMiddleware.ensureSplitTestEnabledForUser(
|
||||
'sharing-updates-new-link'
|
||||
),
|
||||
AuthorizationMiddleware.ensureUserCanAdminProject,
|
||||
CollaboratorsInviteController.updateSharingLink
|
||||
)
|
||||
@@ -155,6 +162,9 @@ export default {
|
||||
'collaboration',
|
||||
'project-invite'
|
||||
),
|
||||
SplitTestMiddleware.ensureSplitTestEnabledForUser(
|
||||
'sharing-updates-new-link'
|
||||
),
|
||||
RateLimiterMiddleware.rateLimit(rateLimiters.viewProjectInvite),
|
||||
CollaboratorsInviteController.viewSharingLink,
|
||||
AnalyticsRegistrationSourceMiddleware.clearSource()
|
||||
@@ -167,6 +177,9 @@ export default {
|
||||
'project-invite'
|
||||
),
|
||||
AuthenticationController.requireLogin(),
|
||||
SplitTestMiddleware.ensureSplitTestEnabledForUser(
|
||||
'sharing-updates-new-link'
|
||||
),
|
||||
RateLimiterMiddleware.rateLimit(rateLimiters.acceptProjectInvite),
|
||||
CollaboratorsInviteController.acceptInvite,
|
||||
AnalyticsRegistrationSourceMiddleware.clearSource()
|
||||
@@ -178,6 +191,9 @@ export default {
|
||||
'collaboration',
|
||||
'project-invite'
|
||||
),
|
||||
SplitTestMiddleware.ensureSplitTestEnabledForUser(
|
||||
'sharing-updates-new-link'
|
||||
),
|
||||
RateLimiterMiddleware.rateLimit(rateLimiters.validateSharingLink),
|
||||
CollaboratorsInviteController.validateSharingLink,
|
||||
AnalyticsRegistrationSourceMiddleware.clearSource()
|
||||
|
||||
@@ -498,6 +498,7 @@ const _ProjectController = {
|
||||
'overleaf-code',
|
||||
'export-docx',
|
||||
'sharing-updates',
|
||||
'sharing-updates-new-link',
|
||||
'export-markdown',
|
||||
'export-html',
|
||||
'command-palette',
|
||||
|
||||
@@ -2367,6 +2367,7 @@
|
||||
"verify_email_address_before_enabling_managed_users": "",
|
||||
"verify_your_email_address": "",
|
||||
"verifying_domain": "",
|
||||
"via_sharing_links": "",
|
||||
"via_sharing_links_legacy": "",
|
||||
"view": "",
|
||||
"view_all": "",
|
||||
|
||||
+133
-76
@@ -54,6 +54,10 @@ function ProjectAccess({
|
||||
const { isProjectOwner } = useEditorContext()
|
||||
const { activeProfessionalGroupSubscriptions } = getMeta('ol-user')
|
||||
const groupSharingEnabled = useFeatureFlag('group-link-sharing')
|
||||
// When the new reusable-link feature is off, the modal only offers the
|
||||
// legacy token-based link ("Via sharing links") alongside "Only invited
|
||||
// people", both driven directly by the project's publicAccessLevel.
|
||||
const newLinkEnabled = useFeatureFlag('sharing-updates-new-link')
|
||||
|
||||
const {
|
||||
monitorRequest,
|
||||
@@ -107,7 +111,31 @@ function ProjectAccess({
|
||||
.catch(debugConsole.error)
|
||||
}
|
||||
|
||||
// When the new reusable-link feature is off, both options map straight to a
|
||||
// project publicAccessLevel change (legacy token-based link sharing), without
|
||||
// touching the reusable sharing-link invite.
|
||||
const handleLegacyAccessChange = (newAccess: ProjectAccessType) => {
|
||||
const publicAccessLevel =
|
||||
newAccess === 'legacyLinkSharing' ? 'tokenBased' : 'private'
|
||||
|
||||
monitorRequest(() => setPublicAccessLevel(projectId, publicAccessLevel))
|
||||
.then(() => {
|
||||
setProjectAccess(newAccess)
|
||||
setSuccessActionMessage(t('access_updated'))
|
||||
sendMB('sharing-link-set-permissions', {
|
||||
project_id: projectId,
|
||||
access_level: publicAccessLevel,
|
||||
})
|
||||
})
|
||||
.catch(debugConsole.error)
|
||||
}
|
||||
|
||||
const onAccessSelect = (eventKey: ProjectAccessType) => {
|
||||
if (!newLinkEnabled) {
|
||||
// Legacy token-based sharing only: no reusable-link invite, no confirmation
|
||||
handleLegacyAccessChange(eventKey)
|
||||
return
|
||||
}
|
||||
if (
|
||||
projectAccess === 'legacyLinkSharing' &&
|
||||
eventKey !== 'legacyLinkSharing'
|
||||
@@ -167,7 +195,9 @@ function ProjectAccess({
|
||||
const [accessType, subscriptionId] = projectAccess.split('.')
|
||||
switch (accessType) {
|
||||
case 'legacyLinkSharing':
|
||||
return t('via_sharing_links_legacy')
|
||||
return newLinkEnabled
|
||||
? t('via_sharing_links_legacy')
|
||||
: t('via_sharing_links')
|
||||
case 'onlyInvitedPeople':
|
||||
return t('only_invited_people')
|
||||
case 'anyoneInXyzWithTheLink':
|
||||
@@ -223,7 +253,7 @@ function ProjectAccess({
|
||||
<MaterialIcon type="keyboard_arrow_down" />
|
||||
</OLDropdownToggle>
|
||||
<OLDropdownMenu>
|
||||
{projectAccess === 'legacyLinkSharing' && (
|
||||
{newLinkEnabled && projectAccess === 'legacyLinkSharing' && (
|
||||
<>
|
||||
<DropdownListItem className="d-flex align-items-center">
|
||||
<OLDropdownItem
|
||||
@@ -258,49 +288,72 @@ function ProjectAccess({
|
||||
{t('only_invited_people')}
|
||||
</OLDropdownItem>
|
||||
</DropdownListItem>
|
||||
{groupSharingEnabled &&
|
||||
activeProfessionalGroupSubscriptions &&
|
||||
activeProfessionalGroupSubscriptions.map(subscription => (
|
||||
<DropdownListItem
|
||||
className="d-flex align-items-center"
|
||||
key={subscription._id}
|
||||
{!newLinkEnabled && (
|
||||
<DropdownListItem className="d-flex align-items-center">
|
||||
<OLDropdownItem
|
||||
as="button"
|
||||
eventKey="legacyLinkSharing"
|
||||
leadingIcon={<MaterialIcon type="link" />}
|
||||
trailingIcon={
|
||||
projectAccess === 'legacyLinkSharing'
|
||||
? 'check'
|
||||
: undefined
|
||||
}
|
||||
active={projectAccess === 'legacyLinkSharing'}
|
||||
>
|
||||
{t('via_sharing_links')}
|
||||
</OLDropdownItem>
|
||||
</DropdownListItem>
|
||||
)}
|
||||
{newLinkEnabled && (
|
||||
<>
|
||||
{groupSharingEnabled &&
|
||||
activeProfessionalGroupSubscriptions &&
|
||||
activeProfessionalGroupSubscriptions.map(subscription => (
|
||||
<DropdownListItem
|
||||
className="d-flex align-items-center"
|
||||
key={subscription._id}
|
||||
>
|
||||
<OLDropdownItem
|
||||
as="button"
|
||||
eventKey={`anyoneInXyzWithTheLink.${subscription._id}`}
|
||||
leadingIcon={
|
||||
<MaterialIcon type="domain" unfilled />
|
||||
}
|
||||
trailingIcon={
|
||||
projectAccess ===
|
||||
`anyoneInXyzWithTheLink.${subscription._id}`
|
||||
? 'check'
|
||||
: undefined
|
||||
}
|
||||
active={
|
||||
projectAccess ===
|
||||
`anyoneInXyzWithTheLink.${subscription._id}`
|
||||
}
|
||||
>
|
||||
{t('anyone_in_x_with_the_link', {
|
||||
groupName: subscription.teamName || 'your group',
|
||||
})}
|
||||
</OLDropdownItem>
|
||||
</DropdownListItem>
|
||||
))}
|
||||
<DropdownListItem className="d-flex align-items-center gap-2">
|
||||
<OLDropdownItem
|
||||
as="button"
|
||||
eventKey={`anyoneInXyzWithTheLink.${subscription._id}`}
|
||||
leadingIcon={<MaterialIcon type="domain" unfilled />}
|
||||
eventKey="anyoneWithTheLink"
|
||||
leadingIcon={<MaterialIcon type="globe" unfilled />}
|
||||
trailingIcon={
|
||||
projectAccess ===
|
||||
`anyoneInXyzWithTheLink.${subscription._id}`
|
||||
projectAccess === 'anyoneWithTheLink'
|
||||
? 'check'
|
||||
: undefined
|
||||
}
|
||||
active={
|
||||
projectAccess ===
|
||||
`anyoneInXyzWithTheLink.${subscription._id}`
|
||||
}
|
||||
active={projectAccess === 'anyoneWithTheLink'}
|
||||
>
|
||||
{t('anyone_in_x_with_the_link', {
|
||||
groupName: subscription.teamName || 'your group',
|
||||
})}
|
||||
{t('anyone_with_the_link')}
|
||||
</OLDropdownItem>
|
||||
</DropdownListItem>
|
||||
))}
|
||||
<DropdownListItem className="d-flex align-items-center gap-2">
|
||||
<OLDropdownItem
|
||||
as="button"
|
||||
eventKey="anyoneWithTheLink"
|
||||
leadingIcon={<MaterialIcon type="globe" unfilled />}
|
||||
trailingIcon={
|
||||
projectAccess === 'anyoneWithTheLink'
|
||||
? 'check'
|
||||
: undefined
|
||||
}
|
||||
active={projectAccess === 'anyoneWithTheLink'}
|
||||
>
|
||||
{t('anyone_with_the_link')}
|
||||
</OLDropdownItem>
|
||||
</DropdownListItem>
|
||||
</>
|
||||
)}
|
||||
</OLDropdownMenu>
|
||||
</OLDropdown>
|
||||
{pendingAccess && (
|
||||
@@ -311,48 +364,52 @@ function ProjectAccess({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{projectAccess !== 'legacyLinkSharing' && privileges && (
|
||||
<OLDropdown align="end" onSelect={onPrivilegesChange}>
|
||||
<OLDropdownToggle
|
||||
variant="ghost"
|
||||
className="d-flex align-items-center gap-2 no-default-caret"
|
||||
>
|
||||
<MemberPrivileges privileges={privileges} />
|
||||
<MaterialIcon type="keyboard_arrow_down" />
|
||||
</OLDropdownToggle>
|
||||
<OLDropdownMenu>
|
||||
<DropdownMenuItem
|
||||
as="button"
|
||||
eventKey="readAndWrite"
|
||||
leadingIcon={<MaterialIcon type="edit" unfilled />}
|
||||
active={privileges === 'readAndWrite'}
|
||||
trailingIcon={
|
||||
privileges === 'readAndWrite' ? 'check' : undefined
|
||||
}
|
||||
{newLinkEnabled &&
|
||||
projectAccess !== 'legacyLinkSharing' &&
|
||||
privileges && (
|
||||
<OLDropdown align="end" onSelect={onPrivilegesChange}>
|
||||
<OLDropdownToggle
|
||||
variant="ghost"
|
||||
className="d-flex align-items-center gap-2 no-default-caret"
|
||||
>
|
||||
{t('editor')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
as="button"
|
||||
eventKey="review"
|
||||
leadingIcon={<MaterialIcon type="mode_comment" unfilled />}
|
||||
active={privileges === 'review'}
|
||||
trailingIcon={privileges === 'review' ? 'check' : undefined}
|
||||
>
|
||||
{t('reviewer')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
as="button"
|
||||
eventKey="readOnly"
|
||||
leadingIcon={<MaterialIcon type="visibility" unfilled />}
|
||||
active={privileges === 'readOnly'}
|
||||
trailingIcon={privileges === 'readOnly' ? 'check' : undefined}
|
||||
>
|
||||
{t('viewer')}
|
||||
</DropdownMenuItem>
|
||||
</OLDropdownMenu>
|
||||
</OLDropdown>
|
||||
)}
|
||||
<MemberPrivileges privileges={privileges} />
|
||||
<MaterialIcon type="keyboard_arrow_down" />
|
||||
</OLDropdownToggle>
|
||||
<OLDropdownMenu>
|
||||
<DropdownMenuItem
|
||||
as="button"
|
||||
eventKey="readAndWrite"
|
||||
leadingIcon={<MaterialIcon type="edit" unfilled />}
|
||||
active={privileges === 'readAndWrite'}
|
||||
trailingIcon={
|
||||
privileges === 'readAndWrite' ? 'check' : undefined
|
||||
}
|
||||
>
|
||||
{t('editor')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
as="button"
|
||||
eventKey="review"
|
||||
leadingIcon={<MaterialIcon type="mode_comment" unfilled />}
|
||||
active={privileges === 'review'}
|
||||
trailingIcon={privileges === 'review' ? 'check' : undefined}
|
||||
>
|
||||
{t('reviewer')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
as="button"
|
||||
eventKey="readOnly"
|
||||
leadingIcon={<MaterialIcon type="visibility" unfilled />}
|
||||
active={privileges === 'readOnly'}
|
||||
trailingIcon={
|
||||
privileges === 'readOnly' ? 'check' : undefined
|
||||
}
|
||||
>
|
||||
{t('viewer')}
|
||||
</DropdownMenuItem>
|
||||
</OLDropdownMenu>
|
||||
</OLDropdown>
|
||||
)}
|
||||
</ShareProjectModalRow>
|
||||
)}
|
||||
|
||||
|
||||
+6
-1
@@ -74,6 +74,10 @@ function ShareProjectModalContentInner({
|
||||
>) {
|
||||
const { t } = useTranslation()
|
||||
const isSharingUpdatesEnabled = useFeatureFlag('sharing-updates')
|
||||
// The copy-sharing-link button only applies to the new reusable links; legacy
|
||||
// token links are copied from their own inputs, so hide it when the new-link
|
||||
// feature is off.
|
||||
const isNewLinkEnabled = useFeatureFlag('sharing-updates-new-link')
|
||||
const [isInvitedPeopleScreen, setIsInvitedPeopleScreen] = useState(false)
|
||||
const { successActionMessage, projectAccess } = useShareProjectContext()
|
||||
const { isRestrictedTokenMember, isProjectOwner } = useEditorContext()
|
||||
@@ -136,7 +140,8 @@ function ShareProjectModalContentInner({
|
||||
<div className="d-flex flex-grow-1 flex-wrap gap-2">
|
||||
{isSharingUpdatesEnabled ? (
|
||||
<>
|
||||
{!isInvitedPeopleScreen &&
|
||||
{isNewLinkEnabled &&
|
||||
!isInvitedPeopleScreen &&
|
||||
projectAccess &&
|
||||
(projectAccess === 'onlyInvitedPeople' ||
|
||||
projectAccess.startsWith('anyoneInXyzWithTheLink') ||
|
||||
|
||||
+21
-1
@@ -97,6 +97,7 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||
|
||||
const { splitTestVariants } = useSplitTestContext()
|
||||
const isSharingUpdatesEnabled = useFeatureFlag('sharing-updates')
|
||||
const isNewLinkEnabled = useFeatureFlag('sharing-updates-new-link')
|
||||
|
||||
// show the new share modal if project owner
|
||||
// is over collaborator limit or has pending editors (once every 24 hours)
|
||||
@@ -155,6 +156,19 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||
return
|
||||
}
|
||||
|
||||
// When the new reusable-link feature is off, derive access purely from the
|
||||
// project's publicAccessLevel (legacy token-based sharing) and avoid the
|
||||
// reusable sharing-link endpoint, which is gated behind the same flag.
|
||||
if (!isNewLinkEnabled) {
|
||||
setSharingLinkData(null)
|
||||
setProjectAccess(
|
||||
publicAccessLevel === 'tokenBased'
|
||||
? 'legacyLinkSharing'
|
||||
: 'onlyInvitedPeople'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (publicAccessLevel === 'tokenBased') {
|
||||
setSharingLinkData(null)
|
||||
setProjectAccess('legacyLinkSharing')
|
||||
@@ -187,7 +201,13 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||
'generic_something_went_wrong'
|
||||
)
|
||||
}
|
||||
}, [publicAccessLevel, projectId, isSharingUpdatesEnabled, isProjectOwner])
|
||||
}, [
|
||||
publicAccessLevel,
|
||||
projectId,
|
||||
isSharingUpdatesEnabled,
|
||||
isNewLinkEnabled,
|
||||
isProjectOwner,
|
||||
])
|
||||
|
||||
// close the modal if not in flight
|
||||
const cancel = useCallback(() => {
|
||||
|
||||
@@ -3010,6 +3010,7 @@
|
||||
"verify_email_address_before_enabling_managed_users": "You need to verify your email address before enabling managed users.",
|
||||
"verify_your_email_address": "Verify your email address",
|
||||
"verifying_domain": "Verifying domain",
|
||||
"via_sharing_links": "Via sharing links",
|
||||
"via_sharing_links_legacy": "Via sharing links (legacy)",
|
||||
"via_support": "Via Support",
|
||||
"view": "View",
|
||||
|
||||
@@ -5,6 +5,10 @@ const base = require('./settings.test.defaults')
|
||||
module.exports = base.mergeWith({
|
||||
defaultFeatures: ServerCEDefaults.defaultFeatures,
|
||||
activeUserMetricInterval: 100,
|
||||
|
||||
splitTestOverrides: {
|
||||
'sharing-updates-new-link': 'enabled', // routes in acceptance tests
|
||||
},
|
||||
})
|
||||
|
||||
module.exports.mergeWith = function (overrides) {
|
||||
|
||||
@@ -6,10 +6,44 @@ import settings from '@overleaf/settings'
|
||||
import CollaboratorsEmailHandler from '../../../app/src/Features/Collaborators/CollaboratorsEmailHandler.mjs'
|
||||
import CollaboratorsInviteHelper from '../../../app/src/Features/Collaborators/CollaboratorsInviteHelper.mjs'
|
||||
import Features from '../../../app/src/infrastructure/Features.mjs'
|
||||
import { db } from '../../../app/src/infrastructure/mongodb.mjs'
|
||||
import { CacheFlow } from 'cache-flow'
|
||||
import sinon from 'sinon'
|
||||
|
||||
let generateTokenSpy
|
||||
|
||||
// The reusable sharing-link routes are gated behind the `sharing-updates-new-link`
|
||||
// split test. In saas, assignments come from the database, so seed a fully
|
||||
// rolled-out "enabled" version. In non-saas, splitTestOverrides in the test
|
||||
// settings handle it (see settings.test.defaults.js).
|
||||
const enableNewLinkSplitTest = async () => {
|
||||
await db.splittests.updateOne(
|
||||
{ name: 'sharing-updates-new-link' },
|
||||
{
|
||||
$set: {
|
||||
versions: [
|
||||
{
|
||||
versionNumber: 1,
|
||||
createdAt: new Date(),
|
||||
active: true,
|
||||
analyticsEnabled: false,
|
||||
phase: 'release',
|
||||
variants: [
|
||||
{
|
||||
name: 'enabled',
|
||||
rolloutPercent: 100,
|
||||
rolloutStripes: [{ start: 0, end: 100 }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{ upsert: true }
|
||||
)
|
||||
await CacheFlow.reset('split-test')
|
||||
}
|
||||
|
||||
const createInvite = (sendingUser, projectId, email, callback) => {
|
||||
sendingUser.getCsrfToken(err => {
|
||||
if (err) {
|
||||
@@ -947,6 +981,15 @@ describe('ProjectInviteTests', function () {
|
||||
})
|
||||
|
||||
describe('sharing link routes', function () {
|
||||
beforeEach(async function () {
|
||||
// In saas, split-test assignments come from the database. In non-saas the
|
||||
// splitTestOverrides in the test settings handle it, and the splittests
|
||||
// collection has no index on `name` (so a seed write hits notablescan).
|
||||
if (Features.hasFeature('saas')) {
|
||||
await enableNewLinkSplitTest()
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(function (done) {
|
||||
this.projectName = `sharing-link-test-${Math.random()}`
|
||||
createProject(this.sendingUser, this.projectName, (err, projectId) => {
|
||||
|
||||
+61
@@ -1042,6 +1042,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-splitTestVariants', {
|
||||
'sharing-updates': 'enabled',
|
||||
'sharing-updates-new-link': 'enabled',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1286,10 +1287,70 @@ describe('<ShareProjectModal/>', function () {
|
||||
expect(screen.queryByRole('link', { name: 'Give feedback' })).to.be.null
|
||||
})
|
||||
|
||||
describe('sharing-updates enabled, new sharing links disabled', function () {
|
||||
// The new reusable-link functionality is gated behind
|
||||
// `sharing-updates-new-link`. With only `sharing-updates` on, the modal
|
||||
// shows the new UI wired to legacy token-based link sharing: two options
|
||||
// ("Only invited people" / "Via sharing links") driven by publicAccessLevel.
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-splitTestVariants', {
|
||||
'sharing-updates': 'enabled',
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
window.metaAttributesCache.delete('ol-splitTestVariants')
|
||||
})
|
||||
|
||||
it('shows "Via sharing links" without the legacy label when `publicAccessLevel` is `tokenBased`', async function () {
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'tokenBased' })
|
||||
)
|
||||
|
||||
await screen.findByText('Via sharing links')
|
||||
expect(screen.queryByText('Via sharing links (legacy)')).to.be.null
|
||||
})
|
||||
|
||||
it('shows "Only invited people" when `publicAccessLevel` is `private`, without calling the sharing-link endpoint', async function () {
|
||||
// Deliberately not mocking /sharing-link: it must not be requested when
|
||||
// the new-link flag is off (fetch-mock throws on unmatched routes).
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'private' })
|
||||
)
|
||||
|
||||
await screen.findByText('Only invited people')
|
||||
// The copy-sharing-link button is for the new reusable links only.
|
||||
expect(screen.queryByRole('button', { name: 'Copy sharing link' })).to.be
|
||||
.null
|
||||
})
|
||||
|
||||
it('offers only the two legacy access options, not "Anyone with the link"', async function () {
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'tokenBased' })
|
||||
)
|
||||
|
||||
const toggle = await screen.findByRole('button', {
|
||||
name: 'Via sharing links',
|
||||
})
|
||||
await userEvent.click(toggle)
|
||||
|
||||
await screen.findByText('Only invited people')
|
||||
expect(screen.queryByText('Anyone with the link')).to.be.null
|
||||
})
|
||||
})
|
||||
|
||||
describe('with "sharing-updates" feature flag', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-splitTestVariants', {
|
||||
'sharing-updates': 'enabled',
|
||||
'sharing-updates-new-link': 'enabled',
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user