Просмотр исходного кода

[web] Migrate managed publishers to React dash (#11749)

* Migrate managed publishers to React dash

* Decaf cleanup + async/await PublishersGetter

* Continue migration of managed publishers to react dash

* Fix linting

* Add tests

* Decaf cleanup PublishersGetterTests

* Update PublishersGetter tests

* Rename component files to kebab-case

GitOrigin-RevId: cb1fe14d120457c965a9d23a8ddb2c2c92e1d5da
Alexandre Bourdin 3 лет назад
Родитель
Сommit
d6e9508aed

+ 39 - 29
services/web/app/src/Features/Publishers/PublishersGetter.js

@@ -1,35 +1,45 @@
-/* eslint-disable
-    camelcase,
-    n/handle-callback-err,
-    max-len,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-let PublishersGetter
+const Settings = require('@overleaf/settings')
+const logger = require('@overleaf/logger')
+const fetch = require('node-fetch')
+const { callbackify } = require('../../util/promises')
 const UserMembershipsHandler = require('../UserMembership/UserMembershipsHandler')
 const UserMembershipsHandler = require('../UserMembership/UserMembershipsHandler')
 const UserMembershipEntityConfigs = require('../UserMembership/UserMembershipEntityConfigs')
 const UserMembershipEntityConfigs = require('../UserMembership/UserMembershipEntityConfigs')
-const { promisifyAll } = require('../../util/promises')
-const logger = require('@overleaf/logger')
-const _ = require('underscore')
 
 
-module.exports = PublishersGetter = {
-  getManagedPublishers(user_id, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return UserMembershipsHandler.getEntitiesByUser(
-      UserMembershipEntityConfigs.publisher,
-      user_id,
-      (error, managedPublishers) => callback(error, managedPublishers)
+async function getManagedPublishers(userId) {
+  return await UserMembershipsHandler.promises.getEntitiesByUser(
+    UserMembershipEntityConfigs.publisher,
+    userId
+  )
+}
+
+async function fetchV1Data(publisher) {
+  const url = `${Settings.apis.v1.url}/api/v2/brands/${publisher.slug}`
+  const authorization = `Basic ${Buffer.from(
+    Settings.apis.v1.user + ':' + Settings.apis.v1.pass
+  ).toString('base64')}`
+  try {
+    const response = await fetch(url, {
+      headers: {
+        Authorization: authorization,
+      },
+      signal: AbortSignal.timeout(Settings.apis.v1.timeout),
+    })
+    const data = await response.json()
+
+    publisher.name = data?.name
+    publisher.partner = data?.partner
+  } catch (error) {
+    logger.err(
+      { model: 'Publisher', slug: publisher.slug, error },
+      '[fetchV1DataError]'
     )
     )
-  },
+  }
 }
 }
 
 
-module.exports.promises = promisifyAll(PublishersGetter)
+module.exports = {
+  getManagedPublishers: callbackify(getManagedPublishers),
+  promises: {
+    getManagedPublishers,
+    fetchV1Data,
+  },
+}

+ 4 - 0
services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js

@@ -191,6 +191,10 @@ async function buildUsersSubscriptionViewModel(user) {
   await Promise.all(
   await Promise.all(
     managedInstitutions.map(InstitutionsManager.promises.fetchV1Data)
     managedInstitutions.map(InstitutionsManager.promises.fetchV1Data)
   )
   )
+  managedPublishers = managedPublishers.map(serializeMongooseObject)
+  await Promise.all(
+    managedPublishers.map(PublishersGetter.promises.fetchV1Data)
+  )
 
 
   if (plan != null) {
   if (plan != null) {
     personalSubscription.plan = plan
     personalSubscription.plan = plan

+ 2 - 1
services/web/app/views/subscriptions/dashboard-react.pug

@@ -7,8 +7,9 @@ block head-scripts
 	script(type="text/javascript", nonce=scriptNonce, src="https://js.recurly.com/v4/recurly.js")
 	script(type="text/javascript", nonce=scriptNonce, src="https://js.recurly.com/v4/recurly.js")
 
 
 block append meta
 block append meta
-	meta(name="ol-managedInstitutions", data-type="json", content=managedInstitutions)
 	meta(name="ol-managedGroupSubscriptions", data-type="json" content=managedGroupSubscriptions)
 	meta(name="ol-managedGroupSubscriptions", data-type="json" content=managedGroupSubscriptions)
+	meta(name="ol-managedInstitutions", data-type="json", content=managedInstitutions)
+	meta(name="ol-managedPublishers", data-type="json" content=managedPublishers)
 	meta(name="ol-planCodesChangingAtTermEnd", data-type="json", content=planCodesChangingAtTermEnd)
 	meta(name="ol-planCodesChangingAtTermEnd", data-type="json", content=planCodesChangingAtTermEnd)
 	meta(name="ol-currentInstitutionsWithLicence", data-type="json" content=currentInstitutionsWithLicence)
 	meta(name="ol-currentInstitutionsWithLicence", data-type="json" content=currentInstitutionsWithLicence)
 	meta(name="ol-plans", data-type="json" content=plans)
 	meta(name="ol-plans", data-type="json" content=plans)

+ 2 - 0
services/web/frontend/extracted-translations.json

@@ -458,6 +458,7 @@
   "manage_labs_program_membership": "",
   "manage_labs_program_membership": "",
   "manage_members": "",
   "manage_members": "",
   "manage_newsletter": "",
   "manage_newsletter": "",
+  "manage_publisher_managers": "",
   "manage_sessions": "",
   "manage_sessions": "",
   "managers_management": "",
   "managers_management": "",
   "math_display": "",
   "math_display": "",
@@ -904,6 +905,7 @@
   "year": "",
   "year": "",
   "you_are_a_manager_and_member_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "",
   "you_are_a_manager_and_member_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "",
   "you_are_a_manager_of_commons_at_institution_x": "",
   "you_are_a_manager_of_commons_at_institution_x": "",
+  "you_are_a_manager_of_publisher_x": "",
   "you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "",
   "you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "",
   "you_are_on_x_plan_as_a_confirmed_member_of_institution_y": "",
   "you_are_on_x_plan_as_a_confirmed_member_of_institution_y": "",
   "you_can_now_log_in_sso": "",
   "you_can_now_log_in_sso": "",

+ 0 - 0
services/web/frontend/js/features/subscription/components/dashboard/managed_institution.tsx → services/web/frontend/js/features/subscription/components/dashboard/managed-institution.tsx


+ 1 - 1
services/web/frontend/js/features/subscription/components/dashboard/managed-institutions.tsx

@@ -1,5 +1,5 @@
 import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
 import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
-import ManagedInstitution from './managed_institution'
+import ManagedInstitution from './managed-institution'
 
 
 export type Institution = {
 export type Institution = {
   v1Id: number
   v1Id: number

+ 35 - 0
services/web/frontend/js/features/subscription/components/dashboard/managed-publisher.tsx

@@ -0,0 +1,35 @@
+import { Trans, useTranslation } from 'react-i18next'
+import { Publisher } from './managed-publishers'
+
+type ManagedPublisherProps = {
+  publisher: Publisher
+}
+
+export default function ManagedPublisher({ publisher }: ManagedPublisherProps) {
+  const { t } = useTranslation()
+
+  return (
+    <div>
+      <p>
+        <Trans
+          i18nKey="you_are_a_manager_of_publisher_x"
+          components={[<strong />]} // eslint-disable-line react/jsx-key
+          values={{
+            publisherName: publisher.name || '',
+          }}
+        />
+      </p>
+      <p>
+        <a href={`/publishers/${publisher.slug}/hub`}>
+          <i className="fa fa-fw fa-user-circle" /> {t('view_hub')}
+        </a>
+      </p>
+      <p>
+        <a href={`/manage/publishers/${publisher.slug}/managers`}>
+          <i className="fa fa-fw fa-users" /> {t('manage_publisher_managers')}
+        </a>
+      </p>
+      <hr />
+    </div>
+  )
+}

+ 28 - 0
services/web/frontend/js/features/subscription/components/dashboard/managed-publishers.tsx

@@ -0,0 +1,28 @@
+import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
+import ManagedPublisher from './managed-publisher'
+
+export type Publisher = {
+  slug: string
+  managerIds: string[]
+  name: string
+  partner: string
+}
+
+export default function ManagedPublishers() {
+  const { managedPublishers } = useSubscriptionDashboardContext()
+
+  if (!managedPublishers) {
+    return null
+  }
+
+  return (
+    <>
+      {managedPublishers.map(publisher => (
+        <ManagedPublisher
+          publisher={publisher}
+          key={`managed-publisher-${publisher.slug}`}
+        />
+      ))}
+    </>
+  )
+}

+ 2 - 0
services/web/frontend/js/features/subscription/components/dashboard/subscription-dashboard.tsx

@@ -1,6 +1,7 @@
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import InstitutionMemberships from './institution-memberships'
 import InstitutionMemberships from './institution-memberships'
 import FreePlan from './free-plan'
 import FreePlan from './free-plan'
+import ManagedPublishers from './managed-publishers'
 import PersonalSubscription from './personal-subscription'
 import PersonalSubscription from './personal-subscription'
 import ManagedGroupSubscriptions from './managed-group-subscriptions'
 import ManagedGroupSubscriptions from './managed-group-subscriptions'
 import ManagedInstitutions from './managed-institutions'
 import ManagedInstitutions from './managed-institutions'
@@ -22,6 +23,7 @@ function SubscriptionDashboard() {
             <PersonalSubscription />
             <PersonalSubscription />
             <ManagedGroupSubscriptions />
             <ManagedGroupSubscriptions />
             <ManagedInstitutions />
             <ManagedInstitutions />
+            <ManagedPublishers />
             <InstitutionMemberships />
             <InstitutionMemberships />
             {!hasDisplayedSubscription && <FreePlan />}
             {!hasDisplayedSubscription && <FreePlan />}
           </div>
           </div>

+ 8 - 2
services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx

@@ -15,8 +15,9 @@ import {
   Plan,
   Plan,
   PriceForDisplayData,
   PriceForDisplayData,
 } from '../../../../../types/subscription/plan'
 } from '../../../../../types/subscription/plan'
-import { Institution as ManagedInstitution } from '../components/dashboard/managed-institutions'
 import { Institution } from '../../../../../types/institution'
 import { Institution } from '../../../../../types/institution'
+import { Institution as ManagedInstitution } from '../components/dashboard/managed-institutions'
+import { Publisher as ManagedPublisher } from '../components/dashboard/managed-publishers'
 import getMeta from '../../../utils/meta'
 import getMeta from '../../../utils/meta'
 import {
 import {
   loadDisplayPriceWithTaxPromise,
   loadDisplayPriceWithTaxPromise,
@@ -40,6 +41,7 @@ type SubscriptionDashboardContextValue = {
   institutionMemberships?: Institution[]
   institutionMemberships?: Institution[]
   managedGroupSubscriptions: ManagedGroupSubscription[]
   managedGroupSubscriptions: ManagedGroupSubscription[]
   managedInstitutions: ManagedInstitution[]
   managedInstitutions: ManagedInstitution[]
+  managedPublishers: ManagedPublisher[]
   updateManagedInstitution: (institution: ManagedInstitution) => void
   updateManagedInstitution: (institution: ManagedInstitution) => void
   modalIdShown?: SubscriptionDashModalIds
   modalIdShown?: SubscriptionDashModalIds
   personalSubscription?: Subscription
   personalSubscription?: Subscription
@@ -106,13 +108,15 @@ export function SubscriptionDashboardProvider({
   const [managedInstitutions, setManagedInstitutions] = useState<
   const [managedInstitutions, setManagedInstitutions] = useState<
     ManagedInstitution[]
     ManagedInstitution[]
   >(getMeta('ol-managedInstitutions'))
   >(getMeta('ol-managedInstitutions'))
+  const managedPublishers = getMeta('ol-managedPublishers')
   const recurlyApiKey = getMeta('ol-recurlyApiKey')
   const recurlyApiKey = getMeta('ol-recurlyApiKey')
 
 
   const hasDisplayedSubscription =
   const hasDisplayedSubscription =
     institutionMemberships?.length > 0 ||
     institutionMemberships?.length > 0 ||
     personalSubscription ||
     personalSubscription ||
     managedGroupSubscriptions?.length > 0 ||
     managedGroupSubscriptions?.length > 0 ||
-    managedInstitutions?.length > 0
+    managedInstitutions?.length > 0 ||
+    managedPublishers?.length > 0
 
 
   useEffect(() => {
   useEffect(() => {
     if (!isRecurlyLoaded()) {
     if (!isRecurlyLoaded()) {
@@ -224,6 +228,7 @@ export function SubscriptionDashboardProvider({
       institutionMemberships,
       institutionMemberships,
       managedGroupSubscriptions,
       managedGroupSubscriptions,
       managedInstitutions,
       managedInstitutions,
+      managedPublishers,
       updateManagedInstitution,
       updateManagedInstitution,
       modalIdShown,
       modalIdShown,
       personalSubscription,
       personalSubscription,
@@ -255,6 +260,7 @@ export function SubscriptionDashboardProvider({
       institutionMemberships,
       institutionMemberships,
       managedGroupSubscriptions,
       managedGroupSubscriptions,
       managedInstitutions,
       managedInstitutions,
+      managedPublishers,
       updateManagedInstitution,
       updateManagedInstitution,
       modalIdShown,
       modalIdShown,
       personalSubscription,
       personalSubscription,

+ 3 - 0
services/web/locales/en.json

@@ -892,6 +892,7 @@
   "manage_labs_program_membership": "Manage Labs Program Membership",
   "manage_labs_program_membership": "Manage Labs Program Membership",
   "manage_members": "Manage members",
   "manage_members": "Manage members",
   "manage_newsletter": "Manage Your Newsletter Preferences",
   "manage_newsletter": "Manage Your Newsletter Preferences",
+  "manage_publisher_managers": "Manage publisher managers",
   "manage_sessions": "Manage Your Sessions",
   "manage_sessions": "Manage Your Sessions",
   "manage_subscription": "Manage Subscription",
   "manage_subscription": "Manage Subscription",
   "managers_cannot_remove_admin": "Admins cannot be removed",
   "managers_cannot_remove_admin": "Admins cannot be removed",
@@ -1160,6 +1161,7 @@
   "public": "Public",
   "public": "Public",
   "publish": "Publish",
   "publish": "Publish",
   "publish_as_template": "Manage Template",
   "publish_as_template": "Manage Template",
+  "publisher_account": "Publisher Account",
   "publishing": "Publishing",
   "publishing": "Publishing",
   "pull_github_changes_into_sharelatex": "Pull GitHub changes into __appName__",
   "pull_github_changes_into_sharelatex": "Pull GitHub changes into __appName__",
   "push_sharelatex_changes_to_github": "Push __appName__ changes to GitHub",
   "push_sharelatex_changes_to_github": "Push __appName__ changes to GitHub",
@@ -1682,6 +1684,7 @@
   "you_and_collaborators_get_access_to_info": "These features are available to you and your collaborators (other Overleaf users that you invite to your projects).",
   "you_and_collaborators_get_access_to_info": "These features are available to you and your collaborators (other Overleaf users that you invite to your projects).",
   "you_are_a_manager_and_member_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are a <1>manager</1> and <1>member</1> of the <0>__planName__</0> group subscription <1>__groupName__</1> administered by <1>__adminEmail__</1>",
   "you_are_a_manager_and_member_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are a <1>manager</1> and <1>member</1> of the <0>__planName__</0> group subscription <1>__groupName__</1> administered by <1>__adminEmail__</1>",
   "you_are_a_manager_of_commons_at_institution_x": "You are a <0>manager</0> of the Overleaf Commons subscription at <0>__institutionName__</0>",
   "you_are_a_manager_of_commons_at_institution_x": "You are a <0>manager</0> of the Overleaf Commons subscription at <0>__institutionName__</0>",
+  "you_are_a_manager_of_publisher_x": "You are a <0>manager</0> of <0>__publisherName__</0>",
   "you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are a <1>manager</1> of the <0>__planName__</0> group subscription <1>__groupName__</1> administered by <1>__adminEmail__</1>",
   "you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are a <1>manager</1> of the <0>__planName__</0> group subscription <1>__groupName__</1> administered by <1>__adminEmail__</1>",
   "you_are_about_to_upgrade": "You are about to upgrade to the __planName__",
   "you_are_about_to_upgrade": "You are about to upgrade to the __planName__",
   "you_are_on_x_plan_as_a_confirmed_member_of_institution_y": "You are on our <0>__planName__</0> plan as a <1>confirmed member</1> of <1>__institutionName__</1>",
   "you_are_on_x_plan_as_a_confirmed_member_of_institution_y": "You are on our <0>__planName__</0> plan as a <1>confirmed member</1> of <1>__institutionName__</1>",

+ 85 - 0
services/web/test/frontend/features/subscription/components/dashboard/managed-publishers.test.tsx

@@ -0,0 +1,85 @@
+import { expect } from 'chai'
+import { render, screen } from '@testing-library/react'
+import { SubscriptionDashboardProvider } from '../../../../../../frontend/js/features/subscription/context/subscription-dashboard-context'
+import fetchMock from 'fetch-mock'
+import ManagedPublishers, {
+  Publisher,
+} from '../../../../../../frontend/js/features/subscription/components/dashboard/managed-publishers'
+
+const userId = 'fff999fff999'
+const publisher1 = {
+  slug: 'pub-1',
+  managerIds: [],
+  name: 'Pub 1',
+  partner: 'p1',
+}
+const publisher2 = {
+  slug: 'pub-2',
+  managerIds: [],
+  name: 'Pub 2',
+  partner: 'p2',
+}
+const managedPublishers: Publisher[] = [publisher1, publisher2]
+
+describe('<ManagedPublishers />', function () {
+  beforeEach(function () {
+    window.metaAttributesCache = new Map()
+    window.metaAttributesCache.set('ol-managedPublishers', managedPublishers)
+    window.user_id = userId
+  })
+
+  afterEach(function () {
+    window.metaAttributesCache = new Map()
+    delete window.user_id
+    fetchMock.reset()
+  })
+
+  it('renders all managed publishers', function () {
+    render(
+      <SubscriptionDashboardProvider>
+        <ManagedPublishers />
+      </SubscriptionDashboardProvider>
+    )
+
+    const elements = screen.getAllByText('You are a', {
+      exact: false,
+    })
+    expect(elements.length).to.equal(2)
+    expect(elements[0].textContent).to.equal('You are a manager of Pub 1')
+    expect(elements[1].textContent).to.equal('You are a manager of Pub 2')
+
+    const viewHubLinks = screen.getAllByText('View hub')
+    expect(viewHubLinks.length).to.equal(2)
+    expect(viewHubLinks[0].getAttribute('href')).to.equal(
+      '/publishers/pub-1/hub'
+    )
+    expect(viewHubLinks[1].getAttribute('href')).to.equal(
+      '/publishers/pub-2/hub'
+    )
+
+    const manageGroupManagersLinks = screen.getAllByText(
+      'Manage publisher managers'
+    )
+    expect(manageGroupManagersLinks.length).to.equal(2)
+    expect(manageGroupManagersLinks[0].getAttribute('href')).to.equal(
+      '/manage/publishers/pub-1/managers'
+    )
+    expect(manageGroupManagersLinks[1].getAttribute('href')).to.equal(
+      '/manage/publishers/pub-2/managers'
+    )
+  })
+
+  it('renders nothing when there are no publishers', function () {
+    window.metaAttributesCache.set('ol-managedPublishers', undefined)
+
+    render(
+      <SubscriptionDashboardProvider>
+        <ManagedPublishers />
+      </SubscriptionDashboardProvider>
+    )
+    const elements = screen.queryAllByText('You are a', {
+      exact: false,
+    })
+    expect(elements.length).to.equal(0)
+  })
+})

+ 22 - 32
services/web/test/unit/src/Publishers/PublishersGetterTests.js

@@ -1,19 +1,6 @@
-/* eslint-disable
-    n/handle-callback-err,
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
 const SandboxedModule = require('sandboxed-module')
 const SandboxedModule = require('sandboxed-module')
-const { expect } = require('chai')
 const sinon = require('sinon')
 const sinon = require('sinon')
+const { expect } = require('chai')
 const modulePath = require('path').join(
 const modulePath = require('path').join(
   __dirname,
   __dirname,
   '../../../../app/src/Features/Publishers/PublishersGetter.js'
   '../../../../app/src/Features/Publishers/PublishersGetter.js'
@@ -27,38 +14,41 @@ describe('PublishersGetter', function () {
       fetchV1Data: sinon.stub(),
       fetchV1Data: sinon.stub(),
     }
     }
 
 
+    this.UserMembershipsHandler = {
+      promises: {
+        getEntitiesByUser: sinon.stub().resolves([this.publisher]),
+      },
+    }
+    this.UserMembershipEntityConfigs = {
+      publisher: {
+        modelName: 'Publisher',
+        canCreate: true,
+        fields: {
+          primaryKey: 'slug',
+        },
+      },
+    }
+
     this.PublishersGetter = SandboxedModule.require(modulePath, {
     this.PublishersGetter = SandboxedModule.require(modulePath, {
       requires: {
       requires: {
         '../User/UserGetter': this.UserGetter,
         '../User/UserGetter': this.UserGetter,
-        '../UserMembership/UserMembershipsHandler':
-          (this.UserMembershipsHandler = {
-            getEntitiesByUser: sinon
-              .stub()
-              .callsArgWith(2, null, [this.publisher]),
-          }),
+        '../UserMembership/UserMembershipsHandler': this.UserMembershipsHandler,
         '../UserMembership/UserMembershipEntityConfigs':
         '../UserMembership/UserMembershipEntityConfigs':
-          (this.UserMembershipEntityConfigs = {
-            publisher: {
-              modelName: 'Publisher',
-              canCreate: true,
-              fields: {
-                primaryKey: 'slug',
-              },
-            },
-          }),
+          this.UserMembershipEntityConfigs,
       },
       },
     })
     })
 
 
-    return (this.userId = '12345abcde')
+    this.userId = '12345abcde'
   })
   })
 
 
   describe('getManagedPublishers', function () {
   describe('getManagedPublishers', function () {
     it('fetches v1 data before returning publisher list', function (done) {
     it('fetches v1 data before returning publisher list', function (done) {
-      return this.PublishersGetter.getManagedPublishers(
+      this.PublishersGetter.getManagedPublishers(
         this.userId,
         this.userId,
         (error, publishers) => {
         (error, publishers) => {
+          expect(error).to.be.null
           publishers.length.should.equal(1)
           publishers.length.should.equal(1)
-          return done()
+          done()
         }
         }
       )
       )
     })
     })