twentyhq / twenty

Building a modern alternative to Salesforce, powered by the community.
https://twenty.com
Other
19.93k stars 2.21k forks source link

Cache invalidation and missing metadata after user logs in #7030

Closed BOHEUS closed 3 weeks ago

BOHEUS commented 1 month ago

Reproducible on demo and locally after pulling newest changes which introduced need of using Redis for caching

Scenario:

  1. Log in (when logging in for the first time, there's error about missing metadata of objects)

Actual: No records are visible across all objects, some settings are not working (like changing theme in Settings > Appearance), no members are visible in Settings > Members Screencast from 2024-09-14 13-26-05.webm

Expected: App must be usable like earlier

Workaround: Change workspace (if account has access to at least 2 workspaces) or refresh the page Note: It seems like generated metadata after logging in creates a cache, which is invalidated from the start, so when user changes the workspace, previous cache is properly invalidated Note 2: There are no related errors in devtools or server's logs, the only thing related to this problem is log from server after user logs in

[Nest] 258950  - 09/14/2024, 2:03:20 PM     LOG [TypeDefinitionsGenerator] Generating composite type objects: [LINK, LINKS, CURRENCY, FULL_NAME, ADDRESS, ACTOR, EMAILS, PHONES]
[Nest] 258950  - 09/14/2024, 2:03:20 PM     LOG [TypeDefinitionsGenerator] Generating metadata objects: [activityTarget, activityTarget, activityTarget, activity, activity, activity, apiKey, apiKey, apiKey, auditLog, auditLog, auditLog, attachment, attachment, attachment, blocklist, blocklist, blocklist, calendarEvent, calendarEvent, calendarEvent, calendarChannel, calendarChannel, calendarChannel, calendarEventParticipant, calendarEventParticipant, calendarEventParticipant, calendarChannelEventAssociation, calendarChannelEventAssociation, calendarChannelEventAssociation, viewFilter, viewFilter, viewFilter, comment, comment, comment, company, company, company, connectedAccount, connectedAccount, connectedAccount, favorite, favorite, favorite, opportunity, opportunity, opportunity, person, person, person, timelineActivity, timelineActivity, timelineActivity, viewField, viewField, viewField, viewSort, viewSort, viewSort, view, view, view, webhook, webhook, webhook, workspaceMember, workspaceMember, workspaceMember, messageThread, messageThread, messageThread, message, message, message, messageChannel, messageChannel, messageChannel, noteTarget, noteTarget, noteTarget, messageParticipant, messageParticipant, messageParticipant, messageChannelMessageAssociation, messageChannelMessageAssociation, messageChannelMessageAssociation, note, note, note, task, task, task, taskTarget, taskTarget, taskTarget, listing, listing, listing]
charlesBochet commented 1 month ago

@BOHEUS I think you don't need to change workspace, just refresh one more time.

It looks to be scoped to local contribution only. I think it's a step we are missing at the end of the workspace seed : we should also generate cache

BOHEUS commented 1 month ago

You're right @charlesBochet, refreshing the page solves the problem until user relogs

charlesBochet commented 1 month ago

And then if the user relogs, you shouldn't face the issue as the cache is there, right?

So I would say it's only an issue on first login

BOHEUS commented 1 month ago

As I relog, there's still issue and I have to refresh the page if I want to use the app Screencast from 2024-09-14 17-16-05.webm

charlesBochet commented 1 month ago

Interesting, could you share the error you have in the logs / network tab?

BOHEUS commented 1 month ago

Sure, I checked Console in devtools and there are no errors, there's error in Network thanks to CORS image image Here's the payload of request

query CombinedFindManyRecords($filterView: ViewFilterInput, $filterFavorite: FavoriteFilterInput, $orderByView: [ViewOrderByInput], $orderByFavorite: [FavoriteOrderByInput], $lastCursorView: String, $lastCursorFavorite: String, $limitView: Int, $limitFavorite: Int) {
  views(
    filter: $filterView
    orderBy: $orderByView
    first: $limitView
    after: $lastCursorView
  ) {
    edges {
      node {
        __typename
        id
        createdAt
        viewFilters {
          edges {
            node {
              __typename
              operand
              displayValue
              id
              updatedAt
              createdAt
              value
              viewId
              fieldMetadataId
            }
            __typename
          }
          __typename
        }
        updatedAt
        type
        isCompact
        position
        viewFields {
          edges {
            node {
              __typename
              isVisible
              viewId
              createdAt
              fieldMetadataId
              updatedAt
              size
              position
              id
            }
            __typename
          }
          __typename
        }
        kanbanFieldMetadataId
        name
        icon
        key
        objectMetadataId
        viewSorts {
          edges {
            node {
              __typename
              direction
              fieldMetadataId
              id
              updatedAt
              createdAt
              viewId
            }
            __typename
          }
          __typename
        }
      }
      cursor
      __typename
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
      __typename
    }
    totalCount
    __typename
  }
  favorites(
    filter: $filterFavorite
    orderBy: $orderByFavorite
    first: $limitFavorite
    after: $lastCursorFavorite
  ) {
    edges {
      node {
        __typename
        taskId
        myCustomObjectId
        workspaceMemberId
        workspaceMember {
          __typename
          userId
          updatedAt
          dateFormat
          id
          locale
          avatarUrl
          timeZone
          name {
            firstName
            lastName
            __typename
          }
          userEmail
          createdAt
          timeFormat
          colorScheme
        }
        companyId
        myCustomObject {
          __typename
          createdBy {
            source
            workspaceMemberId
            name
            __typename
          }
          position
          updatedAt
          name
          myCustomField
          id
          createdAt
        }
        updatedAt
        id
        opportunity {
          __typename
          companyId
          closeDate
          stage
          createdBy {
            source
            workspaceMemberId
            name
            __typename
          }
          id
          updatedAt
          name
          createdAt
          pointOfContactId
          amount {
            amountMicros
            currencyCode
            __typename
          }
          position
        }
        noteId
        note {
          __typename
          createdBy {
            source
            workspaceMemberId
            name
            __typename
          }
          position
          body
          updatedAt
          createdAt
          title
          id
        }
        personId
        task {
          __typename
          status
          assigneeId
          updatedAt
          body
          createdAt
          dueAt
          position
          id
          title
          createdBy {
            source
            workspaceMemberId
            name
            __typename
          }
        }
        opportunityId
        position
        createdAt
        company {
          __typename
          id
          visaSponsorship
          createdBy {
            source
            workspaceMemberId
            name
            __typename
          }
          domainName {
            primaryLinkUrl
            primaryLinkLabel
            secondaryLinks
            __typename
          }
          introVideo {
            primaryLinkUrl
            primaryLinkLabel
            secondaryLinks
            __typename
          }
          position
          annualRecurringRevenue {
            amountMicros
            currencyCode
            __typename
          }
          employees
          linkedinLink {
            primaryLinkUrl
            primaryLinkLabel
            secondaryLinks
            __typename
          }
          workPolicy
          address {
            addressStreet1
            addressStreet2
            addressCity
            addressState
            addressCountry
            addressPostcode
            addressLat
            addressLng
            __typename
          }
          name
          updatedAt
          xLink {
            primaryLinkUrl
            primaryLinkLabel
            secondaryLinks
            __typename
          }
          myCustomField
          createdAt
          accountOwnerId
          tagline
          idealCustomerProfile
        }
        person {
          __typename
          updatedAt
          myCustomObjectId
          whatsapp
          linkedinLink {
            primaryLinkUrl
            primaryLinkLabel
            secondaryLinks
            __typename
          }
          name {
            firstName
            lastName
            __typename
          }
          email
          position
          createdBy {
            source
            workspaceMemberId
            name
            __typename
          }
          avatarUrl
          jobTitle
          xLink {
            primaryLinkUrl
            primaryLinkLabel
            secondaryLinks
            __typename
          }
          performanceRating
          createdAt
          phone
          id
          city
          companyId
          intro
          workPrefereance
        }
      }
      cursor
      __typename
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
      __typename
    }
    totalCount
    __typename
  }
}

Also, here's error from server's logs

Exception Captured
  {
    operation: { name: 'GetCurrentUser', type: 'query' },
    document: 'query GetCurrentUser {\n' +
      '  currentUser {\n' +
      '    ...UserQueryFragment\n' +
      '    __typename\n' +
      '  }\n' +
      '}\n' +
      '\n' +
      'fragment UserQueryFragment on User {\n' +
      '  id\n' +
      '  firstName\n' +
      '  lastName\n' +
      '  email\n' +
      '  canImpersonate\n' +
      '  supportUserHash\n' +
      '  onboardingStatus\n' +
      '  workspaceMember {\n' +
      '    ...WorkspaceMemberQueryFragment\n' +
      '    __typename\n' +
      '  }\n' +
      '  workspaceMembers {\n' +
      '    ...WorkspaceMemberQueryFragment\n' +
      '    __typename\n' +
      '  }\n' +
      '  defaultWorkspace {\n' +
      '    id\n' +
      '    displayName\n' +
      '    logo\n' +
      '    domainName\n' +
      '    inviteHash\n' +
      '    allowImpersonation\n' +
      '    activationStatus\n' +
      '    featureFlags {\n' +
      '      id\n' +
      '      key\n' +
      '      value\n' +
      '      workspaceId\n' +
      '      __typename\n' +
      '    }\n' +
      '    metadataVersion\n' +
      '    currentBillingSubscription {\n' +
      '      id\n' +
      '      status\n' +
      '      interval\n' +
      '      __typename\n' +
      '    }\n' +
      '    workspaceMembersCount\n' +
      '    __typename\n' +
      '  }\n' +
      '  workspaces {\n' +
      '    workspace {\n' +
      '      id\n' +
      '      logo\n' +
      '      displayName\n' +
      '      domainName\n' +
      '      __typename\n' +
      '    }\n' +
      '    __typename\n' +
      '  }\n' +
      '  userVars\n' +
      '  __typename\n' +
      '}\n' +
      '\n' +
      'fragment WorkspaceMemberQueryFragment on WorkspaceMember {\n' +
      '  id\n' +
      '  name {\n' +
      '    firstName\n' +
      '    lastName\n' +
      '    __typename\n' +
      '  }\n' +
      '  colorScheme\n' +
      '  avatarUrl\n' +
      '  locale\n' +
      '  timeZone\n' +
      '  dateFormat\n' +
      '  timeFormat\n' +
      '  __typename\n' +
      '}',
    user: undefined
  }
  [
    ForbiddenException: Forbidden resource
        at canActivateFn (/home/mk/Desktop/projects/twenty/node_modules/@nestjs/core/helpers/external-context-creator.js:157:23)
        at processTicksAndRejections (node:internal/process/task_queues:95:5)
        at target (/home/mk/Desktop/projects/twenty/node_modules/@nestjs/core/helpers/external-context-creator.js:73:31)
        at Object.currentUser (/home/mk/Desktop/projects/twenty/node_modules/@nestjs/core/helpers/external-proxy.js:9:24)
        at field.resolve (/home/mk/Desktop/projects/twenty/node_modules/@envelop/on-resolve/cjs/index.js:36:42)
        at /home/mk/Desktop/projects/twenty/node_modules/graphql-yoga/node_modules/@graphql-tools/executor/cjs/execution/promiseForObject.js:18:35
        at async Promise.all (index 0) {
      path: undefined,
      locations: undefined,
      extensions: { code: 'INTERNAL_SERVER_ERROR', response: 'Forbidden resource' }
    }
  ]

For comparison, that's the error from earlier version when unauthorized user tries to access the workspace

Exception Captured
  {
    operation: { name: 'GetCurrentUser', type: 'query' },
    document: 'query GetCurrentUser {\n' +
      '  currentUser {\n' +
      '    ...UserQueryFragment\n' +
      '    __typename\n' +
      '  }\n' +
      '}\n' +
      '\n' +
      'fragment UserQueryFragment on User {\n' +
      '  id\n' +
      '  firstName\n' +
      '  lastName\n' +
      '  email\n' +
      '  canImpersonate\n' +
      '  supportUserHash\n' +
      '  onboardingStatus\n' +
      '  workspaceMember {\n' +
      '    ...WorkspaceMemberQueryFragment\n' +
      '    __typename\n' +
      '  }\n' +
      '  workspaceMembers {\n' +
      '    ...WorkspaceMemberQueryFragment\n' +
      '    __typename\n' +
      '  }\n' +
      '  defaultWorkspace {\n' +
      '    id\n' +
      '    displayName\n' +
      '    logo\n' +
      '    domainName\n' +
      '    inviteHash\n' +
      '    allowImpersonation\n' +
      '    activationStatus\n' +
      '    featureFlags {\n' +
      '      id\n' +
      '      key\n' +
      '      value\n' +
      '      workspaceId\n' +
      '      __typename\n' +
      '    }\n' +
      '    metadataVersion\n' +
      '    currentBillingSubscription {\n' +
      '      id\n' +
      '      status\n' +
      '      interval\n' +
      '      __typename\n' +
      '    }\n' +
      '    workspaceMembersCount\n' +
      '    __typename\n' +
      '  }\n' +
      '  workspaces {\n' +
      '    workspace {\n' +
      '      id\n' +
      '      logo\n' +
      '      displayName\n' +
      '      domainName\n' +
      '      __typename\n' +
      '    }\n' +
      '    __typename\n' +
      '  }\n' +
      '  userVars\n' +
      '  __typename\n' +
      '}\n' +
      '\n' +
      'fragment WorkspaceMemberQueryFragment on WorkspaceMember {\n' +
      '  id\n' +
      '  name {\n' +
      '    firstName\n' +
      '    lastName\n' +
      '    __typename\n' +
      '  }\n' +
      '  colorScheme\n' +
      '  avatarUrl\n' +
      '  locale\n' +
      '  timeZone\n' +
      '  dateFormat\n' +
      '  timeFormat\n' +
      '  __typename\n' +
      '}',
    user: undefined
  }
  [
    UnauthorizedException: Unauthorized
        at assert (/home/mk/Desktop/projects/twenty/packages/twenty-server/src/utils/assert.ts:19:13)
        at JwtAuthGuard.handleRequest (/home/mk/Desktop/projects/twenty/packages/twenty-server/src/engine/guards/jwt.auth.guard.ts:24:11)
        at /home/mk/Desktop/projects/twenty/node_modules/@nestjs/passport/dist/auth.guard.js:50:128
        at /home/mk/Desktop/projects/twenty/node_modules/@nestjs/passport/dist/auth.guard.js:92:24
        at allFailed (/home/mk/Desktop/projects/twenty/node_modules/passport/lib/middleware/authenticate.js:114:18)
        at attempt (/home/mk/Desktop/projects/twenty/node_modules/passport/lib/middleware/authenticate.js:183:28)
        at JwtAuthStrategy.strategy.fail (/home/mk/Desktop/projects/twenty/node_modules/passport/lib/middleware/authenticate.js:314:9)
        at JwtAuthStrategy.JwtStrategy.authenticate (/home/mk/Desktop/projects/twenty/node_modules/passport-jwt/lib/strategy.js:96:21)
        at attempt (/home/mk/Desktop/projects/twenty/node_modules/passport/lib/middleware/authenticate.js:378:16)
        at authenticate (/home/mk/Desktop/projects/twenty/node_modules/passport/lib/middleware/authenticate.js:379:7) {
      path: undefined,
      locations: undefined,
      extensions: { code: 'INTERNAL_SERVER_ERROR', response: 'Unauthorized' }
    }
  ]
BOHEUS commented 3 weeks ago

@charlesBochet I do not reproduce the issue for the last 2-3 weeks and I'm not able to reproduce it even if I try to have a cache mismatch resulting in app's crash, do you think this issue can be closed?

charlesBochet commented 3 weeks ago

I think we are good since we switched to redis. Let's close it!

charlesBochet commented 3 weeks ago

/award 50

oss-gg[bot] commented 3 weeks ago

Awarding BOHEUS: 50 points 🕹️ Well done! Check out your new contribution on oss.gg/BOHEUS