diff --git a/.github/workflows/lf-production-deploy-new.yaml b/.github/workflows/lf-production-deploy-new.yaml index 1cf3a537bf..bdd5915e55 100644 --- a/.github/workflows/lf-production-deploy-new.yaml +++ b/.github/workflows/lf-production-deploy-new.yaml @@ -27,6 +27,10 @@ on: description: Deploy emails-worker service? required: true type: boolean + deploy_profiles_worker: + description: Deploy profiles-worker service? + required: true + type: boolean deploy_members_enrichment_worker: description: Deploy members-enrichment-worker service? required: true @@ -182,6 +186,27 @@ jobs: id: image run: echo "IMAGE=${{ steps.image-builder.outputs.image }}" >> $GITHUB_OUTPUT + build-and-push-profiles-worker: + runs-on: ubuntu-latest + if: ${{ inputs.deploy_profiles_worker }} + outputs: + image: ${{ steps.image.outputs.IMAGE }} + defaults: + run: + shell: bash + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - uses: ./.github/actions/build-docker-image + id: image-builder + with: + image: profiles-worker + + - name: Set docker image output + id: image + run: echo "IMAGE=${{ steps.image-builder.outputs.image }}" >> $GITHUB_OUTPUT + build-and-push-members-enrichment-worker: runs-on: ubuntu-latest if: ${{ inputs.deploy_members_enrichment_worker }} @@ -378,6 +403,24 @@ jobs: image: ${{ needs.build-and-push-emails-worker.outputs.image }} cluster: ${{ env.CROWD_CLUSTER }} + deploy-profiles-worker: + needs: build-and-push-profiles-worker + runs-on: ubuntu-latest + if: ${{ inputs.deploy_profiles_worker }} + defaults: + run: + shell: bash + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - uses: ./.github/actions/deploy-service + with: + service: profiles-worker + image: ${{ needs.build-and-push-profiles-worker.outputs.image }} + cluster: ${{ env.CROWD_CLUSTER }} + deploy-members-enrichment-worker: needs: build-and-push-members-enrichment-worker runs-on: ubuntu-latest diff --git a/.github/workflows/lf-staging-deploy-profiles-worker.yaml b/.github/workflows/lf-staging-deploy-profiles-worker.yaml new file mode 100644 index 0000000000..71840b25f6 --- /dev/null +++ b/.github/workflows/lf-staging-deploy-profiles-worker.yaml @@ -0,0 +1,60 @@ +name: LF Staging Deploy Profiles Worker + +on: + push: + branches: + - 'lf-staging/**' + - 'lf-staging-**' + paths: + - 'services/libs/**' + - 'services/apps/profiles_worker/**' + +env: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} + CROWD_CLUSTER: ${{ secrets.LF_STAGING_CLUSTER_NAME }} + CROWD_ROLE_ARN: ${{ secrets.LF_STAGING_CLUSTER_ROLE_ARN }} + AWS_ACCESS_KEY_ID: ${{ secrets.LF_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.LF_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.LF_AWS_REGION }} + SLACK_CHANNEL: deploys-lf-staging + SLACK_WEBHOOK: ${{ secrets.LF_STAGING_SLACK_CHANNEL_HOOK }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + outputs: + image: ${{ steps.image.outputs.IMAGE }} + defaults: + run: + shell: bash + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - uses: ./.github/actions/build-docker-image + id: image-builder + with: + image: profiles-worker + + - name: Set docker image output + id: image + run: echo "IMAGE=${{ steps.image-builder.outputs.image }}" >> $GITHUB_OUTPUT + + deploy-profiles-worker: + needs: build-and-push + runs-on: ubuntu-latest + defaults: + run: + shell: bash + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - uses: ./.github/actions/deploy-service + with: + service: profiles-worker + image: ${{ needs.build-and-push.outputs.image }} + cluster: ${{ env.CROWD_CLUSTER }} diff --git a/.github/workflows/production-deploy-new.yaml b/.github/workflows/production-deploy-new.yaml index 1ac79b1bcd..1f420bf5eb 100644 --- a/.github/workflows/production-deploy-new.yaml +++ b/.github/workflows/production-deploy-new.yaml @@ -27,6 +27,10 @@ on: description: Deploy emails-worker service? required: true type: boolean + deploy_profiles_worker: + description: Deploy profiles-worker service? + required: true + type: boolean deploy_members_enrichment_worker: description: Deploy members-enrichment-worker service? required: true @@ -182,6 +186,27 @@ jobs: id: image run: echo "IMAGE=${{ steps.image-builder.outputs.image }}" >> $GITHUB_OUTPUT + build-and-push-profiles-worker: + runs-on: ubuntu-latest + if: ${{ inputs.deploy_profiles_worker }} + outputs: + image: ${{ steps.image.outputs.IMAGE }} + defaults: + run: + shell: bash + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - uses: ./.github/actions/build-docker-image + id: image-builder + with: + image: profiles-worker + + - name: Set docker image output + id: image + run: echo "IMAGE=${{ steps.image-builder.outputs.image }}" >> $GITHUB_OUTPUT + build-and-push-members-enrichment-worker: runs-on: ubuntu-latest if: ${{ inputs.deploy_members_enrichment_worker }} @@ -378,6 +403,24 @@ jobs: image: ${{ needs.build-and-push-emails-worker.outputs.image }} cluster: ${{ env.CROWD_CLUSTER }} + deploy-profiles-worker: + needs: build-and-push-profiles-worker + runs-on: ubuntu-latest + if: ${{ inputs.deploy_profiles_worker }} + defaults: + run: + shell: bash + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - uses: ./.github/actions/deploy-service + with: + service: profiles-worker + image: ${{ needs.build-and-push-profiles-worker.outputs.image }} + cluster: ${{ env.CROWD_CLUSTER }} + deploy-members-enrichment-worker: needs: build-and-push-members-enrichment-worker runs-on: ubuntu-latest diff --git a/.github/workflows/staging-deploy-profiles-worker.yaml b/.github/workflows/staging-deploy-profiles-worker.yaml new file mode 100644 index 0000000000..871289b851 --- /dev/null +++ b/.github/workflows/staging-deploy-profiles-worker.yaml @@ -0,0 +1,60 @@ +name: Staging Deploy Profiles Worker + +on: + push: + branches: + - 'staging/**' + - 'staging-**' + paths: + - 'services/libs/**' + - 'services/apps/profiles_worker/**' + +env: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} + CROWD_CLUSTER: ${{ secrets.STAGING_CLUSTER_NAME }} + CROWD_ROLE_ARN: ${{ secrets.STAGING_CLUSTER_ROLE_ARN }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + SLACK_CHANNEL: deploys-staging + SLACK_WEBHOOK: ${{ secrets.STAGING_SLACK_CHANNEL_HOOK }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + outputs: + image: ${{ steps.image.outputs.IMAGE }} + defaults: + run: + shell: bash + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - uses: ./.github/actions/build-docker-image + id: image-builder + with: + image: profiles-worker + + - name: Set docker image output + id: image + run: echo "IMAGE=${{ steps.image-builder.outputs.image }}" >> $GITHUB_OUTPUT + + deploy-profiles-worker: + needs: build-and-push + runs-on: ubuntu-latest + defaults: + run: + shell: bash + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - uses: ./.github/actions/deploy-service + with: + service: profiles-worker + image: ${{ needs.build-and-push.outputs.image }} + cluster: ${{ env.CROWD_CLUSTER }} diff --git a/backend/src/database/repositories/__tests__/activityRepository.test.ts b/backend/src/database/repositories/__tests__/activityRepository.test.ts deleted file mode 100644 index 1dc727737f..0000000000 --- a/backend/src/database/repositories/__tests__/activityRepository.test.ts +++ /dev/null @@ -1,1765 +0,0 @@ -import { Error404 } from '@crowd/common' -import MemberRepository from '../memberRepository' -import SequelizeTestUtils from '../../utils/sequelizeTestUtils' -import ActivityRepository from '../activityRepository' -import { MemberAttributeName, PlatformType } from '@crowd/types' -import TaskRepository from '../taskRepository' -import MemberAttributeSettingsRepository from '../memberAttributeSettingsRepository' -import MemberAttributeSettingsService from '../../../services/memberAttributeSettingsService' -import { DEFAULT_MEMBER_ATTRIBUTES, UNKNOWN_ACTIVITY_TYPE_DISPLAY } from '@crowd/integrations' -import OrganizationRepository from '../organizationRepository' - -const db = null - -describe('ActivityRepository tests', () => { - beforeEach(async () => { - await SequelizeTestUtils.wipeDatabase(db) - }) - - afterAll((done) => { - // Closing the DB connection allows Jest to exit successfully. - SequelizeTestUtils.closeConnection(db) - done() - }) - - describe('create method', () => { - it('Should create the given activity succesfully', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - attributes: { - replies: 12, - }, - title: 'Title', - body: 'Here', - url: 'https://github.com', - channel: 'channel', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - } - - const activityCreated = await ActivityRepository.create(activity, mockIRepositoryOptions) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - activityCreated.createdAt = activityCreated.createdAt.toISOString().split('T')[0] - activityCreated.updatedAt = activityCreated.updatedAt.toISOString().split('T')[0] - delete activityCreated.member - delete activityCreated.objectMember - - const expectedActivityCreated = { - id: activityCreated.id, - attributes: activity.attributes, - body: 'Here', - type: 'activity', - title: 'Title', - url: 'https://github.com', - channel: 'channel', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - timestamp: new Date('2020-05-27T15:13:30Z'), - platform: PlatformType.GITHUB, - isContribution: true, - score: 1, - username: 'test', - objectMemberUsername: null, - memberId: memberCreated.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - tasks: [], - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parent: null, - parentId: null, - sourceId: activity.sourceId, - sourceParentId: null, - conversationId: null, - display: UNKNOWN_ACTIVITY_TYPE_DISPLAY, - organizationId: null, - organization: null, - } - - expect(activityCreated).toStrictEqual(expectedActivityCreated) - }) - - it('Should create a bare-bones activity succesfully', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - member: memberCreated.id, - username: 'test', - sourceId: '#sourceId1', - } - - const activityCreated = await ActivityRepository.create(activity, mockIRepositoryOptions) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - activityCreated.createdAt = activityCreated.createdAt.toISOString().split('T')[0] - activityCreated.updatedAt = activityCreated.updatedAt.toISOString().split('T')[0] - delete activityCreated.member - delete activityCreated.objectMember - - const expectedActivityCreated = { - id: activityCreated.id, - attributes: {}, - body: null, - title: null, - url: null, - channel: null, - sentiment: {}, - type: 'activity', - timestamp: new Date('2020-05-27T15:13:30Z'), - platform: PlatformType.GITHUB, - isContribution: false, - score: 2, - username: 'test', - objectMemberUsername: null, - memberId: memberCreated.id, - objectMemberId: null, - tasks: [], - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parent: null, - parentId: null, - sourceId: activityCreated.sourceId, - sourceParentId: null, - conversationId: null, - display: UNKNOWN_ACTIVITY_TYPE_DISPLAY, - organizationId: null, - organization: null, - } - - expect(activityCreated).toStrictEqual(expectedActivityCreated) - }) - - it('Should throw error when no platform given', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - attributes: { - replies: 12, - }, - body: 'Here', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - } - - await expect(() => - ActivityRepository.create(activity, mockIRepositoryOptions), - ).rejects.toThrow() - }) - - it('Should throw error when no type given', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity = { - platform: 'activity', - timestamp: '2020-05-27T15:13:30Z', - attributes: { - replies: 12, - }, - username: 'test', - body: 'Here', - isContribution: true, - member: memberCreated.id, - score: 1, - } - - await expect(() => - ActivityRepository.create(activity, mockIRepositoryOptions), - ).rejects.toThrow() - }) - - it('Should throw error when no timestamp given', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity = { - platform: PlatformType.GITHUB, - type: 'activity', - attributes: { - replies: 12, - }, - username: 'test', - body: 'Here', - isContribution: true, - member: memberCreated.id, - score: 1, - } - - await expect(() => - ActivityRepository.create(activity, mockIRepositoryOptions), - ).rejects.toThrow() - }) - - it('Should throw error when sentiment is incorrect', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - // Incomplete Object - await expect(() => - ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: { - positive: 1, - sentiment: 'positive', - score: 1, - }, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ), - ).rejects.toThrow() - - // No score - await expect(() => - ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: { - positive: 0.8, - negative: 0.2, - mixed: 0, - neutral: 0, - sentiment: 'positive', - }, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ), - ).rejects.toThrow() - - // Wrong Sentiment field - await expect(() => - ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: { - positive: 0.3, - negative: 0.2, - neutral: 0.5, - mixed: 0, - score: 0.1, - sentiment: 'smth', - }, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ), - ).rejects.toThrow() - - // Works with empty object - const created = await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: {}, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - expect(created.sentiment).toStrictEqual({}) - }) - - it('Should leave allowed HTML tags in body and title', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - body: '

This is some HTML

', - title: '

This is some Title HTML

', - url: 'https://github.com', - channel: 'channel', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - } - - const activityCreated = await ActivityRepository.create(activity, mockIRepositoryOptions) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - activityCreated.createdAt = activityCreated.createdAt.toISOString().split('T')[0] - activityCreated.updatedAt = activityCreated.updatedAt.toISOString().split('T')[0] - delete activityCreated.member - delete activityCreated.objectMember - - const expectedActivityCreated = { - id: activityCreated.id, - attributes: {}, - body: '

This is some HTML

', - type: 'activity', - title: '

This is some Title HTML

', - url: 'https://github.com', - channel: 'channel', - sentiment: {}, - timestamp: new Date('2020-05-27T15:13:30Z'), - platform: PlatformType.GITHUB, - isContribution: true, - score: 1, - tasks: [], - username: 'test', - objectMemberUsername: null, - memberId: memberCreated.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parent: null, - parentId: null, - sourceId: activity.sourceId, - sourceParentId: null, - conversationId: null, - display: UNKNOWN_ACTIVITY_TYPE_DISPLAY, - organizationId: null, - organization: null, - } - - expect(activityCreated).toStrictEqual(expectedActivityCreated) - }) - - it('Should remove script tags in body and title', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - body: "

Malicious

", - title: "

Malicious title

", - url: 'https://github.com', - channel: 'channel', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - } - - const activityCreated = await ActivityRepository.create(activity, mockIRepositoryOptions) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - activityCreated.createdAt = activityCreated.createdAt.toISOString().split('T')[0] - activityCreated.updatedAt = activityCreated.updatedAt.toISOString().split('T')[0] - delete activityCreated.member - delete activityCreated.objectMember - - const expectedActivityCreated = { - id: activityCreated.id, - attributes: {}, - body: '

Malicious

', - type: 'activity', - title: '

Malicious title

', - url: 'https://github.com', - channel: 'channel', - sentiment: {}, - tasks: [], - timestamp: new Date('2020-05-27T15:13:30Z'), - platform: PlatformType.GITHUB, - isContribution: true, - score: 1, - username: 'test', - objectMemberUsername: null, - memberId: memberCreated.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parent: null, - parentId: null, - sourceId: activity.sourceId, - sourceParentId: null, - conversationId: null, - display: UNKNOWN_ACTIVITY_TYPE_DISPLAY, - organizationId: null, - organization: null, - } - - expect(activityCreated).toStrictEqual(expectedActivityCreated) - }) - - it('Should create an activity with tasks succesfully', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const tasks1 = await TaskRepository.create( - { - name: 'task1', - }, - mockIRepositoryOptions, - ) - - const task2 = await TaskRepository.create( - { - name: 'task2', - }, - mockIRepositoryOptions, - ) - - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - attributes: { - replies: 12, - }, - title: 'Title', - body: 'Here', - url: 'https://github.com', - channel: 'channel', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - tasks: [tasks1.id, task2.id], - sourceId: '#sourceId1', - } - - const activityCreated = await ActivityRepository.create(activity, mockIRepositoryOptions) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - expect(activityCreated.tasks.length).toBe(2) - }) - - it('Should create an activity with an organization succesfully', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const org1 = await OrganizationRepository.create( - { - displayName: 'crowd.dev', - }, - mockIRepositoryOptions, - ) - - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - attributes: { - replies: 12, - }, - title: 'Title', - body: 'Here', - url: 'https://github.com', - channel: 'channel', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - organizationId: org1.id, - sourceId: '#sourceId1', - } - - const activityCreated = await ActivityRepository.create(activity, mockIRepositoryOptions) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - expect(activityCreated.organizationId).toEqual(org1.id) - }) - }) - - describe('findById method', () => { - it('Should successfully find created activity by id', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - } - - const activityCreated = await ActivityRepository.create(activity, mockIRepositoryOptions) - - const expectedActivityFound = { - id: activityCreated.id, - attributes: {}, - body: null, - title: null, - url: null, - channel: null, - sentiment: {}, - type: 'activity', - timestamp: new Date('2020-05-27T15:13:30Z'), - platform: PlatformType.GITHUB, - isContribution: true, - score: 1, - username: 'test', - objectMemberUsername: null, - memberId: memberCreated.id, - objectMemberId: null, - tasks: [], - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parent: null, - parentId: null, - sourceId: activity.sourceId, - sourceParentId: null, - conversationId: null, - display: UNKNOWN_ACTIVITY_TYPE_DISPLAY, - organizationId: null, - organization: null, - } - - const activityFound = await ActivityRepository.findById( - activityCreated.id, - mockIRepositoryOptions, - ) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - activityFound.createdAt = activityFound.createdAt.toISOString().split('T')[0] - activityFound.updatedAt = activityFound.updatedAt.toISOString().split('T')[0] - delete activityFound.member - delete activityFound.objectMember - - expect(activityFound).toStrictEqual(expectedActivityFound) - }) - - it('Should throw 404 error when no user found with given id', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const { randomUUID } = require('crypto') - - await expect(() => - ActivityRepository.findById(randomUUID(), mockIRepositoryOptions), - ).rejects.toThrowError(new Error404()) - }) - }) - - describe('filterIdsInTenant method', () => { - it('Should return the given ids of previously created activity entities', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity1Returned = await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - - const activity2Returned = await ActivityRepository.create( - { - type: 'activity-2', - timestamp: '2020-06-27T15:13:30Z', - platform: PlatformType.GITHUB, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId2', - }, - mockIRepositoryOptions, - ) - - const filterIdsReturned = await ActivityRepository.filterIdsInTenant( - [activity1Returned.id, activity2Returned.id], - mockIRepositoryOptions, - ) - - expect(filterIdsReturned).toStrictEqual([activity1Returned.id, activity2Returned.id]) - }) - - it('Should only return the ids of previously created activities and filter random uuids out', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity3Returned = await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - - const { randomUUID } = require('crypto') - - const filterIdsReturned = await ActivityRepository.filterIdsInTenant( - [activity3Returned.id, randomUUID(), randomUUID()], - mockIRepositoryOptions, - ) - - expect(filterIdsReturned).toStrictEqual([activity3Returned.id]) - }) - - it('Should return an empty array for an irrelevant tenant', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity4Returned = await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - - // create a new tenant and bind options to it - const mockIRepositoryOptionsIr = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const filterIdsReturned = await ActivityRepository.filterIdsInTenant( - [activity4Returned.id], - mockIRepositoryOptionsIr, - ) - - expect(filterIdsReturned).toStrictEqual([]) - }) - }) - - describe('Activities findOne method', () => { - it('Should return the created activity for a simple query', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activityReturned = await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - - const found = await ActivityRepository.findOne({ type: 'activity' }, mockIRepositoryOptions) - - expect(found.id).toStrictEqual(activityReturned.id) - }) - - it('Should return the activity for a complex query', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activityReturned = await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - attributes: { - thread: true, - }, - body: 'Here', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - - const found = await ActivityRepository.findOne( - { 'attributes.thread': true }, - mockIRepositoryOptions, - ) - - expect(found.id).toStrictEqual(activityReturned.id) - }) - - it('Should return null when non-existent', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - attributes: { - replies: 12, - }, - body: 'Here', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - - expect( - await ActivityRepository.findOne({ type: 'notype' }, mockIRepositoryOptions), - ).toBeNull() - }) - }) - - describe('update method', () => { - it('Should succesfully update previously created activity - simple', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activityReturned = await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - attributes: { - replies: 12, - }, - body: 'Here', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - - const updateFields = { - type: 'activity-new', - platform: PlatformType.GITHUB, - } - - const updatedActivity = await ActivityRepository.update( - activityReturned.id, - updateFields, - mockIRepositoryOptions, - ) - - // check updatedAt field looks ok or not. Should be greater than createdAt - expect(updatedActivity.updatedAt.getTime()).toBeGreaterThan( - updatedActivity.createdAt.getTime(), - ) - - updatedActivity.createdAt = updatedActivity.createdAt.toISOString().split('T')[0] - updatedActivity.updatedAt = updatedActivity.updatedAt.toISOString().split('T')[0] - delete updatedActivity.member - delete updatedActivity.objectMember - - const expectedActivityUpdated = { - id: activityReturned.id, - body: activityReturned.body, - channel: null, - title: null, - sentiment: {}, - url: null, - attributes: activityReturned.attributes, - type: 'activity-new', - timestamp: new Date('2020-05-27T15:13:30Z'), - platform: PlatformType.GITHUB, - isContribution: true, - score: 1, - username: 'test', - objectMemberUsername: null, - memberId: memberCreated.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - tasks: [], - parent: null, - parentId: null, - sourceId: activityReturned.sourceId, - sourceParentId: null, - conversationId: null, - display: UNKNOWN_ACTIVITY_TYPE_DISPLAY, - organizationId: null, - organization: null, - } - - expect(updatedActivity).toStrictEqual(expectedActivityUpdated) - }) - - it('Should succesfully update previously created activity - with member relation', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const memberCreated2 = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test2', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activityReturned = await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - attributes: { - replies: 12, - }, - body: 'Here', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - - const updateFields = { - type: 'activity-new', - platform: PlatformType.GITHUB, - body: 'There', - title: 'Title', - channel: 'Channel', - url: 'https://www.google.com', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - username: 'test2', - member: memberCreated2.id, - } - - const updatedActivity = await ActivityRepository.update( - activityReturned.id, - updateFields, - mockIRepositoryOptions, - ) - - // check updatedAt field looks ok or not. Should be greater than createdAt - expect(updatedActivity.updatedAt.getTime()).toBeGreaterThan( - updatedActivity.createdAt.getTime(), - ) - - updatedActivity.createdAt = updatedActivity.createdAt.toISOString().split('T')[0] - updatedActivity.updatedAt = updatedActivity.updatedAt.toISOString().split('T')[0] - delete updatedActivity.member - delete updatedActivity.objectMember - - const expectedActivityUpdated = { - id: activityReturned.id, - attributes: activityReturned.attributes, - body: updateFields.body, - channel: updateFields.channel, - title: updateFields.title, - sentiment: updateFields.sentiment, - url: updateFields.url, - type: 'activity-new', - timestamp: new Date('2020-05-27T15:13:30Z'), - tasks: [], - platform: PlatformType.GITHUB, - isContribution: true, - score: 1, - username: 'test2', - objectMemberUsername: null, - memberId: memberCreated2.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parent: null, - parentId: null, - sourceId: activityReturned.sourceId, - sourceParentId: null, - conversationId: null, - display: UNKNOWN_ACTIVITY_TYPE_DISPLAY, - organizationId: null, - organization: null, - } - - expect(updatedActivity).toStrictEqual(expectedActivityUpdated) - }) - - it('Should succesfully update tasks of an activity', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activityReturned = await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - attributes: { - replies: 12, - }, - body: 'Here', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - - const tasks1 = await TaskRepository.create( - { - name: 'task1', - }, - mockIRepositoryOptions, - ) - - const task2 = await TaskRepository.create( - { - name: 'task2', - }, - mockIRepositoryOptions, - ) - - const updateFields = { - tasks: [tasks1.id, task2.id], - } - - const updatedActivity = await ActivityRepository.update( - activityReturned.id, - updateFields, - mockIRepositoryOptions, - ) - - expect(updatedActivity.tasks).toHaveLength(2) - expect(updatedActivity.tasks[0].id).toBe(tasks1.id) - expect(updatedActivity.tasks[1].id).toBe(task2.id) - }) - - it('Should update body and title with allowed HTML tags', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activityReturned = await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - attributes: { - replies: 12, - }, - body: 'Here', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - - const updateFields = { - body: '

This is some HTML

', - title: '

This is some Title HTML

', - } - - const updatedActivity = await ActivityRepository.update( - activityReturned.id, - updateFields, - mockIRepositoryOptions, - ) - - expect(updatedActivity.body).toBe('

This is some HTML

') - expect(updatedActivity.title).toBe('

This is some Title HTML

') - }) - - it('Should sanitize body and title from non-allowed HTML tags', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activityReturned = await ActivityRepository.create( - { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - attributes: { - replies: 12, - }, - body: 'Here', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - }, - mockIRepositoryOptions, - ) - - const updateFields = { - body: "

Malicious

", - title: "

Malicious title

", - } - - const updatedActivity = await ActivityRepository.update( - activityReturned.id, - updateFields, - mockIRepositoryOptions, - ) - - expect(updatedActivity.body).toBe('

Malicious

') - expect(updatedActivity.title).toBe('

Malicious title

') - }) - - it('Should update an activity with an organization succesfully', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const org1 = await OrganizationRepository.create( - { - displayName: 'crowd.dev', - }, - mockIRepositoryOptions, - ) - - const org2 = await OrganizationRepository.create( - { - displayName: 'tesla', - }, - mockIRepositoryOptions, - ) - - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - attributes: { - replies: 12, - }, - title: 'Title', - body: 'Here', - url: 'https://github.com', - channel: 'channel', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - organizationId: org1.id, - sourceId: '#sourceId1', - } - - const activityCreated = await ActivityRepository.create(activity, mockIRepositoryOptions) - - const activityUpdated = await ActivityRepository.update( - activityCreated.id, - { organizationId: org2.id }, - mockIRepositoryOptions, - ) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - expect(activityUpdated.organizationId).toEqual(org2.id) - }) - }) - - describe('filter tests', () => { - it('Positive sentiment filter and sort', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity1 = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId1', - } - - const activity2 = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: { - positive: 0.55, - negative: 0.0, - neutral: 0.45, - mixed: 0.0, - label: 'neutral', - sentiment: 0.55, - }, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId2', - } - - const activityCreated1 = await ActivityRepository.create(activity1, mockIRepositoryOptions) - await ActivityRepository.create(activity2, mockIRepositoryOptions) - - // Control - expect( - (await ActivityRepository.findAndCountAll({ filter: {} }, mockIRepositoryOptions)).count, - ).toBe(2) - - // Filter by how positive activities are - const filteredActivities = await ActivityRepository.findAndCountAll( - { filter: { positiveSentimentRange: [0.6, 1] } }, - mockIRepositoryOptions, - ) - - expect(filteredActivities.count).toBe(1) - expect(filteredActivities.rows[0].id).toBe(activityCreated1.id) - - // Filter by whether activities are positive or not - const filteredActivities2 = await ActivityRepository.findAndCountAll( - { filter: { sentimentLabel: 'positive' } }, - mockIRepositoryOptions, - ) - - expect(filteredActivities2.count).toBe(1) - expect(filteredActivities2.rows[0].id).toBe(activityCreated1.id) - - // No filter, but sorting - const filteredActivities3 = await ActivityRepository.findAndCountAll( - { filter: {}, orderBy: 'sentiment.positive_DESC' }, - mockIRepositoryOptions, - ) - expect(filteredActivities3.count).toBe(2) - expect(filteredActivities3.rows[0].sentiment.positive).toBeGreaterThan( - filteredActivities3.rows[1].sentiment.positive, - ) - }) - it('Negative sentiment filter and sort', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const memberCreated = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity1 = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId1', - } - - const activity2 = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: { - positive: 0.01, - negative: 0.55, - neutral: 0.55, - mixed: 0.0, - label: 'negative', - sentiment: -0.54, - }, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId2', - } - - await ActivityRepository.create(activity1, mockIRepositoryOptions) - const activityCreated2 = await ActivityRepository.create(activity2, mockIRepositoryOptions) - - // Control - expect( - (await ActivityRepository.findAndCountAll({ filter: {} }, mockIRepositoryOptions)).count, - ).toBe(2) - - // Filter by how positive activities are - const filteredActivities = await ActivityRepository.findAndCountAll( - { filter: { negativeSentimentRange: [0.5, 1] } }, - mockIRepositoryOptions, - ) - - expect(filteredActivities.count).toBe(1) - expect(filteredActivities.rows[0].id).toBe(activityCreated2.id) - - // Filter by whether activities are positive or not - const filteredActivities2 = await ActivityRepository.findAndCountAll( - { filter: { sentimentLabel: 'negative' } }, - mockIRepositoryOptions, - ) - - expect(filteredActivities2.count).toBe(1) - expect(filteredActivities2.rows[0].id).toBe(activityCreated2.id) - - // No filter, but sorting - const filteredActivities3 = await ActivityRepository.findAndCountAll( - { filter: {}, orderBy: 'sentiment.negative_DESC' }, - mockIRepositoryOptions, - ) - expect(filteredActivities3.count).toBe(2) - expect(filteredActivities3.rows[0].sentiment.negative).toBeGreaterThan( - filteredActivities3.rows[1].sentiment.negative, - ) - }) - - it('Overall sentiment filter and sort', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const memberCreated = await MemberRepository.create( - { - username: { - github: 'test', - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity1 = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId1', - } - - const activity2 = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: { - positive: 0.55, - negative: 0.0, - neutral: 0.45, - mixed: 0.0, - label: 'neutral', - sentiment: 0.55, - }, - username: 'test', - member: memberCreated.id, - sourceId: '#sourceId2', - } - - const activityCreated1 = await ActivityRepository.create(activity1, mockIRepositoryOptions) - await ActivityRepository.create(activity2, mockIRepositoryOptions) - - // Control - expect( - (await ActivityRepository.findAndCountAll({ filter: {} }, mockIRepositoryOptions)).count, - ).toBe(2) - - // Filter by how positive activities are - const filteredActivities = await ActivityRepository.findAndCountAll( - { filter: { sentimentRange: [0.6, 1] } }, - mockIRepositoryOptions, - ) - - expect(filteredActivities.count).toBe(1) - expect(filteredActivities.rows[0].id).toBe(activityCreated1.id) - - // No filter, but sorting - const filteredActivities3 = await ActivityRepository.findAndCountAll( - { filter: {}, orderBy: 'sentiment_DESC' }, - mockIRepositoryOptions, - ) - expect(filteredActivities3.count).toBe(2) - expect(filteredActivities3.rows[0].sentiment.positive).toBeGreaterThan( - filteredActivities3.rows[1].sentiment.positive, - ) - }) - - it('Member related attributes filters', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const mas = new MemberAttributeSettingsService(mockIRepositoryOptions) - - await mas.createPredefined(DEFAULT_MEMBER_ATTRIBUTES) - - const memberAttributeSettings = ( - await MemberAttributeSettingsRepository.findAndCountAll({}, mockIRepositoryOptions) - ).rows - - const memberCreated1 = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'test', - }, - displayName: 'Anil', - attributes: { - [MemberAttributeName.IS_TEAM_MEMBER]: { - default: true, - [PlatformType.CROWD]: true, - }, - [MemberAttributeName.LOCATION]: { - default: 'Berlin', - [PlatformType.GITHUB]: 'Berlin', - [PlatformType.SLACK]: 'Turkey', - }, - }, - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const memberCreated2 = await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: 'Michael', - }, - displayName: 'Michael', - attributes: { - [MemberAttributeName.IS_TEAM_MEMBER]: { - default: false, - [PlatformType.CROWD]: false, - }, - [MemberAttributeName.LOCATION]: { - default: 'Scranton', - [PlatformType.GITHUB]: 'Scranton', - [PlatformType.SLACK]: 'New York', - }, - }, - joinedAt: '2020-05-27T15:13:30Z', - }, - mockIRepositoryOptions, - ) - - const activity1 = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - username: 'test', - member: memberCreated1.id, - sourceId: '#sourceId1', - } - - const activity2 = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - sentiment: { - positive: 0.55, - negative: 0.0, - neutral: 0.45, - mixed: 0.0, - label: 'neutral', - sentiment: 0.55, - }, - username: 'Michael', - member: memberCreated2.id, - sourceId: '#sourceId2', - } - - const activityCreated1 = await ActivityRepository.create(activity1, mockIRepositoryOptions) - const activityCreated2 = await ActivityRepository.create(activity2, mockIRepositoryOptions) - - // Control - expect( - (await ActivityRepository.findAndCountAll({ filter: {} }, mockIRepositoryOptions)).count, - ).toBe(2) - - // Filter by member.isTeamMember - let filteredActivities = await ActivityRepository.findAndCountAll( - { - advancedFilter: { - member: { - isTeamMember: { - not: false, - }, - }, - }, - attributesSettings: memberAttributeSettings, - }, - mockIRepositoryOptions, - ) - - expect(filteredActivities.count).toBe(1) - expect(filteredActivities.rows[0].id).toBe(activityCreated1.id) - - filteredActivities = await ActivityRepository.findAndCountAll( - { - advancedFilter: { - member: { - 'attributes.location.slack': 'New York', - }, - }, - attributesSettings: memberAttributeSettings, - }, - mockIRepositoryOptions, - ) - - expect(filteredActivities.count).toBe(1) - expect(filteredActivities.rows[0].id).toBe(activityCreated2.id) - }) - }) -}) diff --git a/backend/src/database/repositories/__tests__/memberEnrichmentCacheRepository.test.ts b/backend/src/database/repositories/__tests__/memberEnrichmentCacheRepository.test.ts deleted file mode 100644 index 8b2438a340..0000000000 --- a/backend/src/database/repositories/__tests__/memberEnrichmentCacheRepository.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { randomUUID } from 'crypto' - -import MemberRepository from '../memberRepository' -import SequelizeTestUtils from '../../utils/sequelizeTestUtils' -import { PlatformType } from '@crowd/types' -import MemberEnrichmentCacheRepository from '../memberEnrichmentCacheRepository' -import { generateUUIDv1 } from '@crowd/common' - -const db = null - -describe('MemberEnrichmentCacheRepository tests', () => { - beforeEach(async () => { - await SequelizeTestUtils.wipeDatabase(db) - }) - - afterAll((done) => { - // Closing the DB connection allows Jest to exit successfully. - SequelizeTestUtils.closeConnection(db) - done() - }) - - describe('upsert method', () => { - it('Should create non existing item successfully', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member2add = { - username: { - [PlatformType.GITHUB]: { - username: 'michael_scott', - }, - }, - displayName: 'Member 1', - email: 'michael@dd.com', - score: 10, - attributes: {}, - joinedAt: '2020-05-27T15:13:30Z', - } - - const member = await MemberRepository.create(member2add, mockIRepositoryOptions) - - const enrichmentData = { - enrichmentField1: 'string', - enrichmentField2: 24, - arrayEnrichmentField: [1, 2, 3], - } - - const cache = await MemberEnrichmentCacheRepository.upsert( - member.id, - enrichmentData, - mockIRepositoryOptions, - ) - - expect(cache.memberId).toEqual(member.id) - expect(cache.data).toStrictEqual(enrichmentData) - }) - - it('Should update the data of existing cache item with incoming data', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member2add = { - username: { - [PlatformType.GITHUB]: { - username: 'michael_scott', - }, - }, - displayName: 'Member 1', - email: 'michael@dd.com', - score: 10, - attributes: {}, - joinedAt: '2020-05-27T15:13:30Z', - } - - const member = await MemberRepository.create(member2add, mockIRepositoryOptions) - - const enrichmentData = { - enrichmentField1: 'string', - enrichmentField2: 24, - arrayEnrichmentField: [1, 2, 3], - } - - let cache = await MemberEnrichmentCacheRepository.upsert( - member.id, - enrichmentData, - mockIRepositoryOptions, - ) - - const newerEnrichmentData = { - enrichmentField1: 'anotherString', - enrichmentField2: 99, - arrayEnrichmentField: ['a', 'b', 'c'], - } - - // should overwrite with new cache data - cache = await MemberEnrichmentCacheRepository.upsert( - member.id, - newerEnrichmentData, - mockIRepositoryOptions, - ) - - expect(cache.memberId).toEqual(member.id) - expect(cache.data).toStrictEqual(newerEnrichmentData) - - // when we send an empty object, it shouldn't overwrite - cache = await MemberEnrichmentCacheRepository.upsert(member.id, {}, mockIRepositoryOptions) - - expect(cache.memberId).toEqual(member.id) - expect(cache.data).toStrictEqual(newerEnrichmentData) - }) - }) - - describe('findByMemberId method', () => { - it('Should find enrichment cache by memberId', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member2add = { - username: { - [PlatformType.GITHUB]: { - username: 'michael_scott', - }, - }, - displayName: 'Member 1', - email: 'michael@dd.com', - score: 10, - attributes: {}, - joinedAt: '2020-05-27T15:13:30Z', - } - - const member = await MemberRepository.create(member2add, mockIRepositoryOptions) - - const enrichmentData = { - enrichmentField1: 'string', - enrichmentField2: 24, - arrayEnrichmentField: [1, 2, 3], - } - - await MemberEnrichmentCacheRepository.upsert( - member.id, - enrichmentData, - mockIRepositoryOptions, - ) - - const cache = await MemberEnrichmentCacheRepository.findByMemberId( - member.id, - mockIRepositoryOptions, - ) - - expect(cache.memberId).toEqual(member.id) - expect(cache.data).toEqual(enrichmentData) - }) - - it('Should return null for non-existing cache entry', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const cache = await MemberEnrichmentCacheRepository.findByMemberId( - randomUUID(), - mockIRepositoryOptions, - ) - expect(cache).toBeNull() - }) - }) -}) diff --git a/backend/src/database/repositories/__tests__/memberRepository.test.ts b/backend/src/database/repositories/__tests__/memberRepository.test.ts deleted file mode 100644 index 13945324b8..0000000000 --- a/backend/src/database/repositories/__tests__/memberRepository.test.ts +++ /dev/null @@ -1,3959 +0,0 @@ -import { v4 as uuid } from 'uuid' -import moment from 'moment' - -import { Error404 } from '@crowd/common' -import { PlatformType, SegmentStatus } from '@crowd/types' -import { generateUUIDv1 } from '@crowd/common' -import SequelizeTestUtils from '../../utils/sequelizeTestUtils' -import MemberRepository from '../memberRepository' -import NoteRepository from '../noteRepository' -import OrganizationRepository from '../organizationRepository' -import TagRepository from '../tagRepository' -import TaskRepository from '../taskRepository' -import lodash from 'lodash' -import SegmentRepository from '../segmentRepository' -import { populateSegments } from '../../utils/segmentTestUtils' -import MemberService from '../../../services/memberService' -import OrganizationService from '../../../services/organizationService' - -const db = null - -function mapUsername(data: any): any { - const username = {} - Object.keys(data).forEach((platform) => { - const usernameData = data[platform] - - if (Array.isArray(usernameData)) { - username[platform] = [] - if (usernameData.length > 0) { - for (const entry of usernameData) { - if (typeof entry === 'string') { - username[platform].push(entry) - } else if (typeof entry === 'object') { - username[platform].push((entry as any).username) - } else { - throw new Error('Invalid username type') - } - } - } - } else if (typeof usernameData === 'object') { - username[platform] = [usernameData.username] - } else if (typeof usernameData === 'string') { - username[platform] = [usernameData] - } else { - throw new Error('Invalid username type') - } - }) - return username -} - -describe('MemberRepository tests', () => { - beforeEach(async () => { - await SequelizeTestUtils.wipeDatabase(db) - }) - - afterAll((done) => { - // Closing the DB connection allows Jest to exit successfully. - SequelizeTestUtils.closeConnection(db) - done() - }) - - describe('create method', () => { - it('Should create the given member succesfully', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member2add = { - username: { - [PlatformType.GITHUB]: { - username: 'anil_github', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - emails: ['lala@l.com'], - score: 10, - attributes: { - [PlatformType.GITHUB]: { - name: 'Quoc-Anh Nguyen', - isHireable: true, - url: 'https://github.com/imcvampire', - websiteUrl: 'https://imcvampire.js.org/', - bio: 'Lazy geek', - location: 'Helsinki, Finland', - actions: [ - { - score: 2, - timestamp: '2021-05-27T15:13:30Z', - }, - ], - }, - [PlatformType.TWITTER]: { - profile_url: 'https://twitter.com/imcvampire', - url: 'https://twitter.com/imcvampire', - }, - }, - joinedAt: '2020-05-27T15:13:30Z', - } - - const cloned = lodash.cloneDeep(member2add) - const memberCreated = await MemberRepository.create(cloned, mockIRepositoryOptions) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const expectedMemberCreated = { - id: memberCreated.id, - username: mapUsername(member2add.username), - attributes: member2add.attributes, - displayName: member2add.displayName, - emails: member2add.emails, - score: member2add.score, - identities: ['github'], - lastEnriched: null, - enrichedBy: [], - contributions: null, - organizations: [], - notes: [], - tasks: [], - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segments: mockIRepositoryOptions.currentSegments, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - activeOn: [], - activityTypes: [], - reach: { total: -1 }, - joinedAt: new Date('2020-05-27T15:13:30Z'), - tags: [], - noMerge: [], - toMerge: [], - activityCount: 0, - activeDaysCount: 0, - lastActive: null, - averageSentiment: 0, - numberOfOpenSourceContributions: 0, - lastActivity: null, - affiliations: [], - manuallyCreated: false, - } - expect(memberCreated).toStrictEqual(expectedMemberCreated) - }) - - it('Should create succesfully but return without relations when doPopulateRelations=false', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member2add = { - username: { - [PlatformType.GITHUB]: { - username: 'anil_github', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - emails: ['lala@l.com'], - score: 10, - attributes: { - [PlatformType.GITHUB]: { - name: 'Quoc-Anh Nguyen', - isHireable: true, - url: 'https://github.com/imcvampire', - websiteUrl: 'https://imcvampire.js.org/', - bio: 'Lazy geek', - location: 'Helsinki, Finland', - actions: [ - { - score: 2, - timestamp: '2021-05-27T15:13:30Z', - }, - ], - }, - [PlatformType.TWITTER]: { - profile_url: 'https://twitter.com/imcvampire', - url: 'https://twitter.com/imcvampire', - }, - }, - joinedAt: '2020-05-27T15:13:30Z', - } - - const cloned = lodash.cloneDeep(member2add) - const memberCreated = await MemberRepository.create(cloned, mockIRepositoryOptions, false) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const expectedMemberCreated = { - id: memberCreated.id, - username: mapUsername(member2add.username), - displayName: member2add.displayName, - attributes: member2add.attributes, - emails: member2add.emails, - lastEnriched: null, - enrichedBy: [], - contributions: null, - score: member2add.score, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segments: mockIRepositoryOptions.currentSegments, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - reach: { total: -1 }, - organizations: [], - joinedAt: new Date('2020-05-27T15:13:30Z'), - affiliations: [], - manuallyCreated: false, - } - expect(memberCreated).toStrictEqual(expectedMemberCreated) - }) - - it('Should succesfully create member with only mandatory username and joinedAt fields', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member2add = { - username: { - [PlatformType.GITHUB]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - } - - const cloned = lodash.cloneDeep(member2add) - const memberCreated = await MemberRepository.create(cloned, mockIRepositoryOptions) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const expectedMemberCreated = { - id: memberCreated.id, - username: mapUsername(member2add.username), - displayName: member2add.displayName, - organizations: [], - attributes: {}, - identities: ['github'], - emails: [], - lastEnriched: null, - enrichedBy: [], - contributions: null, - score: -1, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segments: mockIRepositoryOptions.currentSegments, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - activeOn: [], - activityTypes: [], - reach: { total: -1 }, - joinedAt: new Date('2020-05-27T15:13:30Z'), - notes: [], - tasks: [], - tags: [], - noMerge: [], - toMerge: [], - activityCount: 0, - activeDaysCount: 0, - averageSentiment: 0, - numberOfOpenSourceContributions: 0, - lastActive: null, - lastActivity: null, - affiliations: [], - manuallyCreated: false, - } - - expect(memberCreated).toStrictEqual(expectedMemberCreated) - }) - - it('Should throw error when no username given', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - // no username field, should reject the promise with - // sequelize unique constraint - const member2add = { - joinedAt: '2020-05-27T15:13:30Z', - emails: ['test@crowd.dev'], - } - - await expect(() => - MemberRepository.create(member2add, mockIRepositoryOptions), - ).rejects.toThrow() - }) - - it('Should throw error when no joinedAt given', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - // no username field, should reject the promise with - // sequelize unique constraint - const member2add = { - username: { - [PlatformType.GITHUB]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - emails: ['test@crowd.dev'], - } - - await expect(() => - MemberRepository.create(member2add, mockIRepositoryOptions), - ).rejects.toThrow() - }) - - it('Should succesfully create member with notes', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const notes1 = await NoteRepository.create( - { - body: 'note1', - }, - mockIRepositoryOptions, - ) - - const notes2 = await NoteRepository.create( - { - body: 'note2', - }, - mockIRepositoryOptions, - ) - - const member2add = { - username: { - [PlatformType.SLACK]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - notes: [notes1.id, notes2.id], - } - - const memberCreated = await MemberRepository.create(member2add, mockIRepositoryOptions) - expect(memberCreated.notes).toHaveLength(2) - expect(memberCreated.notes[0].id).toEqual(notes1.id) - expect(memberCreated.notes[1].id).toEqual(notes2.id) - }) - - it('Should succesfully create member with tasks', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const tasks1 = await TaskRepository.create( - { - name: 'task1', - }, - mockIRepositoryOptions, - ) - - const task2 = await TaskRepository.create( - { - name: 'task2', - }, - mockIRepositoryOptions, - ) - - const member2add = { - username: { - [PlatformType.DISCORD]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - tasks: [tasks1.id, task2.id], - } - - const memberCreated = await MemberRepository.create(member2add, mockIRepositoryOptions) - expect(memberCreated.tasks).toHaveLength(2) - expect(memberCreated.tasks.find((t) => t.id === tasks1.id)).not.toBeUndefined() - expect(memberCreated.tasks.find((t) => t.id === task2.id)).not.toBeUndefined() - }) - - it('Should succesfully create member with organization affiliations', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const segmentRepo = new SegmentRepository(mockIRepositoryOptions) - - const segment1 = await segmentRepo.create({ - name: 'Crowd.dev - Segment1', - url: '', - parentName: 'Crowd.dev - Segment1', - grandparentName: 'Crowd.dev - Segment1', - slug: 'crowd.dev-1', - parentSlug: 'crowd.dev-1', - grandparentSlug: 'crowd.dev-1', - status: SegmentStatus.ACTIVE, - sourceId: null, - sourceParentId: null, - }) - - const segment2 = await segmentRepo.create({ - name: 'Crowd.dev - Segment2', - url: '', - parentName: 'Crowd.dev - Segment2', - grandparentName: 'Crowd.dev - Segment2', - slug: 'crowd.dev-2', - parentSlug: 'crowd.dev-2', - grandparentSlug: 'crowd.dev-2', - status: SegmentStatus.ACTIVE, - sourceId: null, - sourceParentId: null, - }) - - const org1 = await OrganizationRepository.create( - { - displayName: 'crowd.dev', - }, - mockIRepositoryOptions, - ) - - const member2add = { - username: { - [PlatformType.DISCORD]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - affiliations: [ - { - segmentId: segment1.id, - organizationId: org1.id, - }, - { - segmentId: segment2.id, - organizationId: null, - }, - ], - } - - const memberCreated = await MemberRepository.create(member2add, mockIRepositoryOptions) - expect(memberCreated.affiliations).toHaveLength(2) - expect( - memberCreated.affiliations.filter((a) => a.segmentId === segment1.id)[0].organizationId, - ).toEqual(org1.id) - expect( - memberCreated.affiliations.filter((a) => a.segmentId === segment2.id)[0].organizationId, - ).toBeNull() - }) - }) - - describe('findById method', () => { - it('Should successfully find created member by id', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member2add = { - username: { - [PlatformType.GITHUB]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - } - - const cloned = lodash.cloneDeep(member2add) - const memberCreated = await MemberRepository.create(cloned, mockIRepositoryOptions) - - const expectedMemberFound = { - id: memberCreated.id, - username: mapUsername(member2add.username), - displayName: member2add.displayName, - identities: ['github'], - attributes: {}, - emails: [], - lastEnriched: null, - enrichedBy: [], - contributions: null, - score: -1, - importHash: null, - organizations: [], - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segments: mockIRepositoryOptions.currentSegments, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - activeOn: [], - activityTypes: [], - reach: { total: -1 }, - notes: [], - tasks: [], - joinedAt: new Date('2020-05-27T15:13:30Z'), - tags: [], - noMerge: [], - toMerge: [], - activityCount: 0, - activeDaysCount: 0, - averageSentiment: 0, - numberOfOpenSourceContributions: 0, - lastActive: null, - lastActivity: null, - affiliations: [], - manuallyCreated: false, - } - - const memberById = await MemberRepository.findById(memberCreated.id, mockIRepositoryOptions) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - memberById.createdAt = memberById.createdAt.toISOString().split('T')[0] - memberById.updatedAt = memberById.updatedAt.toISOString().split('T')[0] - - expect(memberById).toStrictEqual(expectedMemberFound) - }) - - it('Should return a plain object when called with doPopulateRelations false', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member2add = { - username: { - [PlatformType.GITHUB]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - } - - const cloned = lodash.cloneDeep(member2add) - const memberCreated = await MemberRepository.create(cloned, mockIRepositoryOptions) - - const expectedMemberFound = { - id: memberCreated.id, - username: mapUsername(member2add.username), - displayName: member2add.displayName, - lastEnriched: null, - enrichedBy: [], - contributions: null, - attributes: {}, - emails: [], - score: -1, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segments: mockIRepositoryOptions.currentSegments, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - reach: { total: -1 }, - organizations: [], - joinedAt: new Date('2020-05-27T15:13:30Z'), - affiliations: [], - manuallyCreated: false, - } - - const memberById = await MemberRepository.findById( - memberCreated.id, - mockIRepositoryOptions, - true, - false, - ) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - memberById.createdAt = memberById.createdAt.toISOString().split('T')[0] - memberById.updatedAt = memberById.updatedAt.toISOString().split('T')[0] - - expect(memberById).toStrictEqual(expectedMemberFound) - }) - - it('Should throw 404 error when no member found with given id', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const { randomUUID } = require('crypto') - - await expect(() => - MemberRepository.findById(randomUUID(), mockIRepositoryOptions), - ).rejects.toThrowError(new Error404()) - }) - }) - - describe('filterIdsInTenant method', () => { - it('Should return the given ids of previously created member entities', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const member1 = { - username: { - [PlatformType.GITHUB]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - } - const member2 = { - username: { - [PlatformType.GITHUB]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'some-other-name', - joinedAt: '2020-05-28T15:13:30Z', - } - - const member1Returned = await MemberRepository.create(member1, mockIRepositoryOptions) - const member2Returned = await MemberRepository.create(member2, mockIRepositoryOptions) - - const filterIdsReturned = await MemberRepository.filterIdsInTenant( - [member1Returned.id, member2Returned.id], - mockIRepositoryOptions, - ) - - expect(filterIdsReturned).toStrictEqual([member1Returned.id, member2Returned.id]) - }) - - it('Should only return the ids of previously created members and filter random uuids out', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = { - username: { - [PlatformType.GITHUB]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-29T15:14:30Z', - } - - const member1Returned = await MemberRepository.create(member1, mockIRepositoryOptions) - - const { randomUUID } = require('crypto') - - const filterIdsReturned = await MemberRepository.filterIdsInTenant( - [member1Returned.id, randomUUID(), randomUUID()], - mockIRepositoryOptions, - ) - - expect(filterIdsReturned).toStrictEqual([member1Returned.id]) - }) - - it('Should return an empty array for an irrelevant tenant', async () => { - let mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = { - username: { - [PlatformType.GITHUB]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-04-29T15:14:30Z', - } - - const member1Returned = await MemberRepository.create(member1, mockIRepositoryOptions) - - // create a new tenant and bind options to it - mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const filterIdsReturned = await MemberRepository.filterIdsInTenant( - [member1Returned.id], - mockIRepositoryOptions, - ) - - expect(filterIdsReturned).toStrictEqual([]) - }) - }) - - describe('memberExists method', () => { - it('Should return the created member for a simple query', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const member1 = { - username: { - [PlatformType.TWITTER]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - emails: ['joan@crowd.dev'], - } - const member1Returned = await MemberRepository.create(member1, mockIRepositoryOptions) - - const found = await MemberRepository.memberExists( - 'test1', - PlatformType.TWITTER, - mockIRepositoryOptions, - ) - - expect(found).toStrictEqual(member1Returned) - }) - - it('Should a plain object when called with doPopulateRelations false', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const member1 = { - username: { - [PlatformType.TWITTER]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - emails: ['joan@crowd.dev'], - } - const member1Returned = await MemberRepository.create(member1, mockIRepositoryOptions) - delete member1Returned.toMerge - delete member1Returned.noMerge - delete member1Returned.tags - delete member1Returned.activities - delete member1Returned.notes - delete member1Returned.tasks - delete member1Returned.lastActive - delete member1Returned.activityCount - delete member1Returned.averageSentiment - delete member1Returned.lastActivity - delete member1Returned.activeOn - delete member1Returned.identities - delete member1Returned.activityTypes - delete member1Returned.activeDaysCount - delete member1Returned.numberOfOpenSourceContributions - delete member1Returned.affiliations - delete member1Returned.manuallyCreated - member1Returned.segments = member1Returned.segments.map((s) => s.id) - - const found = await MemberRepository.memberExists( - 'test1', - PlatformType.TWITTER, - mockIRepositoryOptions, - false, - ) - - expect(found).toStrictEqual(member1Returned) - }) - - it('Should return null when non-existent at platform level', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = { - username: { - [PlatformType.TWITTER]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - emails: ['joan@crowd.dev'], - } - await MemberRepository.create(member1, mockIRepositoryOptions) - - await expect(() => - MemberRepository.memberExists('test1', PlatformType.GITHUB, mockIRepositoryOptions), - ) - }) - - it('Should return null when non-existent at username level', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = { - username: { - [PlatformType.TWITTER]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - emails: ['joan@crowd.dev'], - } - await MemberRepository.create(member1, mockIRepositoryOptions) - - const memberExists = await MemberRepository.memberExists( - 'test2', - PlatformType.TWITTER, - mockIRepositoryOptions, - ) - - expect(memberExists).toBeNull() - }) - }) - - describe('findAndCountAll method', () => { - it('is successfully finding and counting all members, sortedBy activitiesCount DESC', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - const member2 = await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - const member3 = await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await mockIRepositoryOptions.database.activity.bulkCreate([ - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member1.id, - username: member1.username[PlatformType.SLACK], - sourceId: '#sourceId1', - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member2.id, - username: member2.username[PlatformType.SLACK], - sourceId: '#sourceId2', - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member2.id, - username: member2.username[PlatformType.SLACK], - sourceId: '#sourceId3', - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member3.id, - username: member3.username[PlatformType.SLACK], - sourceId: '#sourceId4', - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member3.id, - username: member3.username[PlatformType.SLACK], - sourceId: '#sourceId5', - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member3.id, - username: member3.username[PlatformType.SLACK], - sourceId: '#sourceId6', - }, - ]) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAll( - { filter: {}, orderBy: 'activityCount_DESC' }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(3) - expect(members.rows[0].activityCount).toEqual('3') - expect(members.rows[1].activityCount).toEqual('2') - expect(members.rows[2].activityCount).toEqual('1') - }) - - it('is successfully finding and counting all members, sortedBy numberOfOpenSourceContributions DESC', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test1' }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - contributions: [ - { - id: 112529472, - url: 'https://github.com/bachman/pied-piper', - topics: ['compression', 'data', 'middle-out', 'Java'], - summary: 'Pied Piper: 10 commits in 1 day', - numberCommits: 10, - lastCommitDate: '2023-03-10', - firstCommitDate: '2023-03-01', - }, - { - id: 112529473, - url: 'https://github.com/bachman/aviato', - topics: ['Python', 'Django'], - summary: 'Aviato: 5 commits in 1 day', - numberCommits: 5, - lastCommitDate: '2023-02-25', - firstCommitDate: '2023-02-20', - }, - { - id: 112529476, - url: 'https://github.com/bachman/erlichbot', - topics: ['Python', 'Slack API'], - summary: 'ErlichBot: 2 commits in 1 day', - numberCommits: 2, - lastCommitDate: '2023-01-25', - firstCommitDate: '2023-01-24', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test2' }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - contributions: [ - { - id: 112529473, - url: 'https://github.com/bighead/silicon-valley', - topics: ['TV Shows', 'Comedy', 'Startups'], - summary: 'Silicon Valley: 50 commits in 2 weeks', - numberCommits: 50, - lastCommitDate: '02/01/2023', - firstCommitDate: '01/17/2023', - }, - { - id: 112529474, - url: 'https://github.com/bighead/startup-ideas', - topics: ['Ideas', 'Startups'], - summary: 'Startup Ideas: 20 commits in 1 week', - numberCommits: 20, - lastCommitDate: '03/01/2023', - firstCommitDate: '02/22/2023', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test3' }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAll( - { filter: {}, orderBy: 'numberOfOpenSourceContributions_DESC' }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(3) - expect(members.rows[0].numberOfOpenSourceContributions).toEqual(3) - expect(members.rows[1].numberOfOpenSourceContributions).toEqual(2) - expect(members.rows[2].numberOfOpenSourceContributions).toEqual(0) - }) - - it('is successfully finding and counting all members, numberOfOpenSourceContributions range gte than 3 and less or equal to 6', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test1' }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - contributions: [ - { - id: 112529472, - url: 'https://github.com/bachman/pied-piper', - topics: ['compression', 'data', 'middle-out', 'Java'], - summary: 'Pied Piper: 10 commits in 1 day', - numberCommits: 10, - lastCommitDate: '2023-03-10', - firstCommitDate: '2023-03-01', - }, - { - id: 112529473, - url: 'https://github.com/bachman/aviato', - topics: ['Python', 'Django'], - summary: 'Aviato: 5 commits in 1 day', - numberCommits: 5, - lastCommitDate: '2023-02-25', - firstCommitDate: '2023-02-20', - }, - { - id: 112529476, - url: 'https://github.com/bachman/erlichbot', - topics: ['Python', 'Slack API'], - summary: 'ErlichBot: 2 commits in 1 day', - numberCommits: 2, - lastCommitDate: '2023-01-25', - firstCommitDate: '2023-01-24', - }, - { - id: 112529473, - url: 'https://github.com/bighead/silicon-valley', - topics: ['TV Shows', 'Comedy', 'Startups'], - summary: 'Silicon Valley: 50 commits in 2 weeks', - numberCommits: 50, - lastCommitDate: '02/01/2023', - firstCommitDate: '01/17/2023', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test2' }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - contributions: [ - { - id: 112529473, - url: 'https://github.com/bighead/silicon-valley', - topics: ['TV Shows', 'Comedy', 'Startups'], - summary: 'Silicon Valley: 50 commits in 2 weeks', - numberCommits: 50, - lastCommitDate: '02/01/2023', - firstCommitDate: '01/17/2023', - }, - { - id: 112529474, - url: 'https://github.com/bighead/startup-ideas', - topics: ['Ideas', 'Startups'], - summary: 'Startup Ideas: 20 commits in 1 week', - numberCommits: 20, - lastCommitDate: '03/01/2023', - firstCommitDate: '02/22/2023', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test3' }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAll( - { filter: { numberOfOpenSourceContributionsRange: [3, 6] } }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(1) - expect(members.rows[0].numberOfOpenSourceContributions).toEqual(4) - }) - - it('is successfully finding and counting all members, numberOfOpenSourceContributions range gte 2', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test1' }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - contributions: [ - { - id: 112529472, - url: 'https://github.com/bachman/pied-piper', - topics: ['compression', 'data', 'middle-out', 'Java'], - summary: 'Pied Piper: 10 commits in 1 day', - numberCommits: 10, - lastCommitDate: '2023-03-10', - firstCommitDate: '2023-03-01', - }, - { - id: 112529473, - url: 'https://github.com/bachman/aviato', - topics: ['Python', 'Django'], - summary: 'Aviato: 5 commits in 1 day', - numberCommits: 5, - lastCommitDate: '2023-02-25', - firstCommitDate: '2023-02-20', - }, - { - id: 112529476, - url: 'https://github.com/bachman/erlichbot', - topics: ['Python', 'Slack API'], - summary: 'ErlichBot: 2 commits in 1 day', - numberCommits: 2, - lastCommitDate: '2023-01-25', - firstCommitDate: '2023-01-24', - }, - { - id: 112529473, - url: 'https://github.com/bighead/silicon-valley', - topics: ['TV Shows', 'Comedy', 'Startups'], - summary: 'Silicon Valley: 50 commits in 2 weeks', - numberCommits: 50, - lastCommitDate: '02/01/2023', - firstCommitDate: '01/17/2023', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test2' }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - contributions: [ - { - id: 112529473, - url: 'https://github.com/bighead/silicon-valley', - topics: ['TV Shows', 'Comedy', 'Startups'], - summary: 'Silicon Valley: 50 commits in 2 weeks', - numberCommits: 50, - lastCommitDate: '02/01/2023', - firstCommitDate: '01/17/2023', - }, - { - id: 112529474, - url: 'https://github.com/bighead/startup-ideas', - topics: ['Ideas', 'Startups'], - summary: 'Startup Ideas: 20 commits in 1 week', - numberCommits: 20, - lastCommitDate: '03/01/2023', - firstCommitDate: '02/22/2023', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test3' }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAll( - { - filter: { numberOfOpenSourceContributionsRange: [2] }, - orderBy: 'numberOfOpenSourceContributions_DESC', - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(2) - expect(members.rows[0].numberOfOpenSourceContributions).toEqual(4) - expect(members.rows[1].numberOfOpenSourceContributions).toEqual(2) - }) - - it('is successfully finding and counting all members, and tags [nodejs, vuejs]', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const nodeTag = await mockIRepositoryOptions.database.tag.create({ - name: 'nodejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - const vueTag = await mockIRepositoryOptions.database.tag.create({ - name: 'vuejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - - await MemberRepository.create( - { - username: { - [PlatformType.TWITTER]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.TWITTER]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - tags: [nodeTag.id, vueTag.id], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAll( - { filter: { tags: [nodeTag.id, vueTag.id] } }, - mockIRepositoryOptions, - ) - const member2 = members.rows.find((m) => m.username[PlatformType.TWITTER][0] === 'test2') - expect(members.rows.length).toEqual(1) - expect(member2.tags[0].name).toEqual('nodejs') - expect(member2.tags[1].name).toEqual('vuejs') - }) - - it('is successfully finding and counting all members, and tags [nodejs]', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const nodeTag = await mockIRepositoryOptions.database.tag.create({ - name: 'nodejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - const vueTag = await mockIRepositoryOptions.database.tag.create({ - name: 'vuejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - - await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - tags: [nodeTag.id], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - tags: [nodeTag.id, vueTag.id], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAll( - { filter: { tags: [nodeTag.id] } }, - mockIRepositoryOptions, - ) - const member1 = members.rows.find((m) => m.username[PlatformType.GITHUB][0] === 'test1') - const member2 = members.rows.find((m) => m.username[PlatformType.GITHUB][0] === 'test2') - - expect(members.rows.length).toEqual(2) - expect(member1.tags[0].name).toEqual('nodejs') - expect(member1.tags[0].name).toEqual('nodejs') - expect(member2.tags[1].name).toEqual('vuejs') - }) - - it('is successfully finding and counting all members, and organisations [crowd.dev, pied piper]', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const crowd = await mockIRepositoryOptions.database.organization.create({ - displayName: 'crowd.dev', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - await OrganizationRepository.addIdentity( - crowd.id, - { - name: 'crowd.dev', - url: 'https://crowd.dev', - platform: 'crowd', - }, - mockIRepositoryOptions, - ) - - const pp = await mockIRepositoryOptions.database.organization.create({ - displayName: 'pied piper', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - - await OrganizationRepository.addIdentity( - pp.id, - { - name: 'pied piper', - url: 'https://piedpiper.com', - platform: 'crowd', - }, - mockIRepositoryOptions, - ) - - await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - organizations: [crowd.id, pp.id], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAll( - { filter: { organizations: [crowd.id, pp.id] } }, - mockIRepositoryOptions, - ) - const member2 = members.rows.find((m) => m.username[PlatformType.SLACK][0] === 'test2') - expect(members.rows.length).toEqual(1) - expect(member2.organizations[0].displayName).toEqual('crowd.dev') - expect(member2.organizations[1].displayName).toEqual('pied piper') - }) - - it('is successfully finding and counting all members, and scoreRange is gte than 1 and less or equal to 6', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const user1 = { - username: { - [PlatformType.SLACK]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - } - const user2 = { - username: { - [PlatformType.SLACK]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - } - const user3 = { - username: { - [PlatformType.SLACK]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '7', - joinedAt: new Date(), - } - await MemberRepository.create(user1, mockIRepositoryOptions) - await MemberRepository.create(user2, mockIRepositoryOptions) - await MemberRepository.create(user3, mockIRepositoryOptions) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAll( - { filter: { scoreRange: [1, 6] } }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(2) - expect(members.rows.find((m) => m.username[PlatformType.SLACK][0] === 'test1').score).toEqual( - 1, - ) - expect(members.rows.find((m) => m.username[PlatformType.SLACK][0] === 'test2').score).toEqual( - 6, - ) - }) - - it('is successfully finding and counting all members, and scoreRange is gte than 7', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const user1 = { - username: { - [PlatformType.DISCORD]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - } - const user2 = { - username: { - [PlatformType.DISCORD]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - } - const user3 = { - username: { - [PlatformType.DISCORD]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - } - await MemberRepository.create(user1, mockIRepositoryOptions) - await MemberRepository.create(user2, mockIRepositoryOptions) - await MemberRepository.create(user3, mockIRepositoryOptions) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAll( - { filter: { scoreRange: [7] } }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(1) - for (const member of members.rows) { - expect(member.score).toBeGreaterThanOrEqual(7) - } - }) - - it('is successfully find and counting members with various filters, computed attributes, and full options (filter, limit, offset and orderBy)', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const nodeTag = await mockIRepositoryOptions.database.tag.create({ - name: 'nodejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - const vueTag = await mockIRepositoryOptions.database.tag.create({ - name: 'vuejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - - const member1 = await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - tags: [nodeTag.id], - reach: { - total: 15, - }, - }, - mockIRepositoryOptions, - ) - const member2 = await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - tags: [nodeTag.id, vueTag.id], - reach: { - total: 55, - }, - }, - mockIRepositoryOptions, - ) - const member3 = await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - tags: [vueTag.id], - reach: { - total: 124, - }, - }, - mockIRepositoryOptions, - ) - - await mockIRepositoryOptions.database.activity.bulkCreate([ - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-10'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member1.id, - username: member1.username[PlatformType.SLACK], - sourceId: '#sourceId1', - sentiment: { - positive: 0.55, - negative: 0.0, - neutral: 0.45, - mixed: 0.0, - label: 'positive', - sentiment: 0.1, - }, - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-11'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member2.id, - username: member2.username[PlatformType.SLACK], - sourceId: '#sourceId2', - sentiment: { - positive: 0.01, - negative: 0.55, - neutral: 0.55, - mixed: 0.0, - label: 'negative', - sentiment: -0.54, - }, - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-12'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member2.id, - username: member2.username[PlatformType.SLACK], - sourceId: '#sourceId3', - sentiment: { - positive: 0.94, - negative: 0.0, - neutral: 0.06, - mixed: 0.0, - label: 'positive', - sentiment: 0.94, - }, - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-13'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member3.id, - username: member3.username[PlatformType.SLACK], - sourceId: '#sourceId4', - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.42, - }, - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-14'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member3.id, - username: member3.username[PlatformType.SLACK], - sourceId: '#sourceId5', - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.41, - }, - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-15'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member3.id, - username: member3.username[PlatformType.SLACK], - sourceId: '#sourceId6', - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.18, - }, - }, - ]) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - let members = await MemberRepository.findAndCountAll( - { - filter: {}, - limit: 15, - offset: 0, - orderBy: 'activityCount_DESC', - }, - mockIRepositoryOptions, - ) - expect(members.rows.length).toEqual(3) - expect(members.rows[0].activityCount).toEqual('3') - expect(members.rows[0].lastActive.toISOString()).toEqual('2022-09-15T00:00:00.000Z') - - expect(members.rows[1].activityCount).toEqual('2') - expect(members.rows[1].lastActive.toISOString()).toEqual('2022-09-12T00:00:00.000Z') - - expect(members.rows[2].activityCount).toEqual('1') - expect(members.rows[2].tags[0].name).toEqual('nodejs') - expect(members.rows[2].lastActive.toISOString()).toEqual('2022-09-10T00:00:00.000Z') - - expect(members.rows[1].tags.map((i) => i.name).sort()).toEqual(['nodejs', 'vuejs']) - expect(members.rows[0].tags[0].name).toEqual('vuejs') - - // filter and order by reach - members = await MemberRepository.findAndCountAll( - { - filter: { - reachRange: [55], - }, - limit: 15, - offset: 0, - orderBy: 'reach_DESC', - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(2) - expect(members.rows[0].id).toEqual(member3.id) - expect(members.rows[1].id).toEqual(member2.id) - - // filter and sort by activity count - members = await MemberRepository.findAndCountAll( - { - filter: { - activityCountRange: [2], - }, - limit: 15, - offset: 0, - orderBy: 'activityCount_DESC', - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(2) - expect(members.rows.map((i) => i.id)).toEqual([member3.id, member2.id]) - - // filter and sort by lastActive - members = await MemberRepository.findAndCountAll( - { - filter: { - lastActiveRange: ['2022-09-11'], - }, - limit: 15, - offset: 0, - orderBy: 'lastActive_DESC', - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(2) - expect(members.rows.map((i) => i.id)).toEqual([member3.id, member2.id]) - - // filter and sort by averageSentiment (member1.avgSentiment = 0.1, member2.avgSentiment = 0.2, member3.avgSentiment = 0.34) - members = await MemberRepository.findAndCountAll( - { - filter: { - averageSentimentRange: [0.2], - }, - limit: 15, - offset: 0, - orderBy: 'averageSentiment_ASC', - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(2) - expect(members.rows.map((i) => i.id)).toEqual([member2.id, member3.id]) - }) - }) - - describe('findAndCountAllv2 method', () => { - it('is successfully finding and counting all members, sortedBy activitiesCount DESC', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test1' }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - const member2 = await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test2' }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - const member3 = await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test3' }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await mockIRepositoryOptions.database.activity.bulkCreate([ - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - username: 'test1', - memberId: member1.id, - sourceId: '#sourceId1', - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - username: 'test2', - memberId: member2.id, - sourceId: '#sourceId2', - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - username: 'test2', - memberId: member2.id, - sourceId: '#sourceId3', - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - username: 'test3', - memberId: member3.id, - sourceId: '#sourceId4', - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - username: 'test3', - memberId: member3.id, - sourceId: '#sourceId5', - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date(), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - username: 'test3', - memberId: member3.id, - sourceId: '#sourceId6', - }, - ]) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAllv2( - { filter: {}, orderBy: 'activityCount_DESC' }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(3) - expect(members.rows[0].activityCount).toEqual('3') - expect(members.rows[1].activityCount).toEqual('2') - expect(members.rows[2].activityCount).toEqual('1') - }) - - it('is successfully finding and counting all members, sortedBy numberOfOpenSourceContributions DESC', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test1' }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - contributions: [ - { - id: 112529472, - url: 'https://github.com/bachman/pied-piper', - topics: ['compression', 'data', 'middle-out', 'Java'], - summary: 'Pied Piper: 10 commits in 1 day', - numberCommits: 10, - lastCommitDate: '2023-03-10', - firstCommitDate: '2023-03-01', - }, - { - id: 112529473, - url: 'https://github.com/bachman/aviato', - topics: ['Python', 'Django'], - summary: 'Aviato: 5 commits in 1 day', - numberCommits: 5, - lastCommitDate: '2023-02-25', - firstCommitDate: '2023-02-20', - }, - { - id: 112529476, - url: 'https://github.com/bachman/erlichbot', - topics: ['Python', 'Slack API'], - summary: 'ErlichBot: 2 commits in 1 day', - numberCommits: 2, - lastCommitDate: '2023-01-25', - firstCommitDate: '2023-01-24', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test2' }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - contributions: [ - { - id: 112529473, - url: 'https://github.com/bighead/silicon-valley', - topics: ['TV Shows', 'Comedy', 'Startups'], - summary: 'Silicon Valley: 50 commits in 2 weeks', - numberCommits: 50, - lastCommitDate: '02/01/2023', - firstCommitDate: '01/17/2023', - }, - { - id: 112529474, - url: 'https://github.com/bighead/startup-ideas', - topics: ['Ideas', 'Startups'], - summary: 'Startup Ideas: 20 commits in 1 week', - numberCommits: 20, - lastCommitDate: '03/01/2023', - firstCommitDate: '02/22/2023', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test3' }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAllv2( - { filter: {}, orderBy: 'numberOfOpenSourceContributions_DESC' }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(3) - expect(members.rows[0].numberOfOpenSourceContributions).toEqual(3) - expect(members.rows[1].numberOfOpenSourceContributions).toEqual(2) - expect(members.rows[2].numberOfOpenSourceContributions).toEqual(0) - }) - - it('is successfully finding and counting all members, numberOfOpenSourceContributions range gte 3', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - await MemberRepository.create( - { - username: { - [PlatformType.TWITTER]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - contributions: [ - { - id: 112529472, - url: 'https://github.com/bachman/pied-piper', - topics: ['compression', 'data', 'middle-out', 'Java'], - summary: 'Pied Piper: 10 commits in 1 day', - numberCommits: 10, - lastCommitDate: '2023-03-10', - firstCommitDate: '2023-03-01', - }, - { - id: 112529473, - url: 'https://github.com/bachman/aviato', - topics: ['Python', 'Django'], - summary: 'Aviato: 5 commits in 1 day', - numberCommits: 5, - lastCommitDate: '2023-02-25', - firstCommitDate: '2023-02-20', - }, - { - id: 112529476, - url: 'https://github.com/bachman/erlichbot', - topics: ['Python', 'Slack API'], - summary: 'ErlichBot: 2 commits in 1 day', - numberCommits: 2, - lastCommitDate: '2023-01-25', - firstCommitDate: '2023-01-24', - }, - { - id: 112529473, - url: 'https://github.com/bighead/silicon-valley', - topics: ['TV Shows', 'Comedy', 'Startups'], - summary: 'Silicon Valley: 50 commits in 2 weeks', - numberCommits: 50, - lastCommitDate: '02/01/2023', - firstCommitDate: '01/17/2023', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.TWITTER]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - contributions: [ - { - id: 112529473, - url: 'https://github.com/bighead/silicon-valley', - topics: ['TV Shows', 'Comedy', 'Startups'], - summary: 'Silicon Valley: 50 commits in 2 weeks', - numberCommits: 50, - lastCommitDate: '02/01/2023', - firstCommitDate: '01/17/2023', - }, - { - id: 112529474, - url: 'https://github.com/bighead/startup-ideas', - topics: ['Ideas', 'Startups'], - summary: 'Startup Ideas: 20 commits in 1 week', - numberCommits: 20, - lastCommitDate: '03/01/2023', - firstCommitDate: '02/22/2023', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.TWITTER]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAllv2( - { - filter: { - and: [ - { - and: [ - { - numberOfOpenSourceContributions: { - gte: 3, - }, - }, - ], - }, - ], - }, - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(1) - expect(members.rows[0].numberOfOpenSourceContributions).toEqual(4) - }) - - it('is successfully finding and counting all members, numberOfOpenSourceContributions range gte 2 and sort by asc', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test1' }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - contributions: [ - { - id: 112529472, - url: 'https://github.com/bachman/pied-piper', - topics: ['compression', 'data', 'middle-out', 'Java'], - summary: 'Pied Piper: 10 commits in 1 day', - numberCommits: 10, - lastCommitDate: '2023-03-10', - firstCommitDate: '2023-03-01', - }, - { - id: 112529473, - url: 'https://github.com/bachman/aviato', - topics: ['Python', 'Django'], - summary: 'Aviato: 5 commits in 1 day', - numberCommits: 5, - lastCommitDate: '2023-02-25', - firstCommitDate: '2023-02-20', - }, - { - id: 112529476, - url: 'https://github.com/bachman/erlichbot', - topics: ['Python', 'Slack API'], - summary: 'ErlichBot: 2 commits in 1 day', - numberCommits: 2, - lastCommitDate: '2023-01-25', - firstCommitDate: '2023-01-24', - }, - { - id: 112529473, - url: 'https://github.com/bighead/silicon-valley', - topics: ['TV Shows', 'Comedy', 'Startups'], - summary: 'Silicon Valley: 50 commits in 2 weeks', - numberCommits: 50, - lastCommitDate: '02/01/2023', - firstCommitDate: '01/17/2023', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test2' }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - contributions: [ - { - id: 112529473, - url: 'https://github.com/bighead/silicon-valley', - topics: ['TV Shows', 'Comedy', 'Startups'], - summary: 'Silicon Valley: 50 commits in 2 weeks', - numberCommits: 50, - lastCommitDate: '02/01/2023', - firstCommitDate: '01/17/2023', - }, - { - id: 112529474, - url: 'https://github.com/bighead/startup-ideas', - topics: ['Ideas', 'Startups'], - summary: 'Startup Ideas: 20 commits in 1 week', - numberCommits: 20, - lastCommitDate: '03/01/2023', - firstCommitDate: '02/22/2023', - }, - ], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { [PlatformType.TWITTER]: 'test3' }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - const members = await MemberRepository.findAndCountAllv2( - { - filter: { - and: [ - { - and: [ - { - numberOfOpenSourceContributions: { - gte: 2, - }, - }, - ], - }, - ], - }, - orderBy: 'numberOfOpenSourceContributions_ASC', - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(2) - expect(members.rows[0].numberOfOpenSourceContributions).toEqual(2) - expect(members.rows[1].numberOfOpenSourceContributions).toEqual(4) - }) - - it('is successfully finding and counting all members, and tags [nodejs, vuejs]', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const nodeTag = await mockIRepositoryOptions.database.tag.create({ - name: 'nodejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - const vueTag = await mockIRepositoryOptions.database.tag.create({ - name: 'vuejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - - await MemberRepository.create( - { - username: { - [PlatformType.TWITTER]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.TWITTER]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - tags: [nodeTag.id, vueTag.id], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAllv2( - { - filter: { - and: [ - { - tags: { - contains: [nodeTag.id, vueTag.id], - }, - }, - ], - }, - }, - mockIRepositoryOptions, - ) - const member2 = members.rows.find((m) => m.username[PlatformType.TWITTER].includes('test2')) - expect(members.rows.length).toEqual(1) - expect(member2.tags.map((t) => t.name)).toEqual(expect.arrayContaining(['nodejs', 'vuejs'])) - }) - - it('is successfully finding and counting all members, and tags [nodejs]', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const nodeTag = await mockIRepositoryOptions.database.tag.create({ - name: 'nodejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - const vueTag = await mockIRepositoryOptions.database.tag.create({ - name: 'vuejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - - await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - tags: [nodeTag.id], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - tags: [nodeTag.id, vueTag.id], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.GITHUB]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAllv2( - { - filter: { - and: [ - { - tags: { - contains: [nodeTag.id], - }, - }, - ], - }, - }, - mockIRepositoryOptions, - ) - const member1 = members.rows.find((m) => m.username[PlatformType.GITHUB].includes('test1')) - const member2 = members.rows.find((m) => m.username[PlatformType.GITHUB].includes('test2')) - - expect(members.rows.length).toEqual(2) - expect(member1.tags[0].name).toEqual('nodejs') - expect(member2.tags.map((t) => t.name)).toEqual(expect.arrayContaining(['nodejs', 'vuejs'])) - }) - - it('is successfully finding and counting all members, and organisations [crowd.dev, pied piper]', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const crowd = await OrganizationRepository.create( - { - identities: [ - { - name: 'crowd.dev', - url: 'https://crowd.dev', - platform: 'crowd', - }, - ], - displayName: 'crowd.dev', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }, - mockIRepositoryOptions, - ) - const pp = await OrganizationRepository.create( - { - identities: [ - { - name: 'pied piper', - url: 'https://piedpiper.com', - platform: 'crowd', - }, - ], - displayName: 'pied piper', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }, - mockIRepositoryOptions, - ) - - await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - organizations: [crowd.id, pp.id], - }, - mockIRepositoryOptions, - ) - await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAllv2( - { - filter: { - and: [ - { - organizations: { - contains: [crowd.id, pp.id], - }, - }, - ], - }, - }, - mockIRepositoryOptions, - ) - const member2 = members.rows.find((m) => m.username[PlatformType.SLACK].includes('test2')) - expect(members.rows.length).toEqual(1) - expect(member2.organizations.map((o) => o.displayName)).toEqual( - expect.arrayContaining(['crowd.dev', 'pied piper']), - ) - }) - - it('is successfully finding and counting all members, and scoreRange is gte than 7', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const user1 = { - username: { - [PlatformType.DISCORD]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - } - const user2 = { - username: { - [PlatformType.DISCORD]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - } - const user3 = { - username: { - [PlatformType.DISCORD]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - } - await MemberRepository.create(user1, mockIRepositoryOptions) - await MemberRepository.create(user2, mockIRepositoryOptions) - await MemberRepository.create(user3, mockIRepositoryOptions) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - const members = await MemberRepository.findAndCountAllv2( - { - filter: { - and: [ - { - and: [ - { - score: { - gte: 7, - }, - }, - ], - }, - ], - }, - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(1) - for (const member of members.rows) { - expect(member.score).toBeGreaterThanOrEqual(7) - } - }) - - it('is successfully find and counting members with various filters, computed attributes, and full options (filter, limit, offset and orderBy)', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const nodeTag = await mockIRepositoryOptions.database.tag.create({ - name: 'nodejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - const vueTag = await mockIRepositoryOptions.database.tag.create({ - name: 'vuejs', - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - }) - - const member1 = await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - tags: [nodeTag.id], - reach: { - total: 15, - }, - }, - mockIRepositoryOptions, - ) - const member2 = await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - score: '6', - joinedAt: new Date(), - tags: [nodeTag.id, vueTag.id], - reach: { - total: 55, - }, - }, - mockIRepositoryOptions, - ) - const member3 = await MemberRepository.create( - { - username: { - [PlatformType.SLACK]: { - username: 'test3', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 3', - score: '7', - joinedAt: new Date(), - tags: [vueTag.id], - reach: { - total: 124, - }, - }, - mockIRepositoryOptions, - ) - - await mockIRepositoryOptions.database.activity.bulkCreate([ - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-10'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member1.id, - username: member1.username[PlatformType.SLACK], - sourceId: '#sourceId1', - sentiment: { - positive: 0.55, - negative: 0.0, - neutral: 0.45, - mixed: 0.0, - label: 'positive', - sentiment: 0.1, - }, - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-11'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member2.id, - username: member2.username[PlatformType.SLACK], - sourceId: '#sourceId2', - sentiment: { - positive: 0.01, - negative: 0.55, - neutral: 0.55, - mixed: 0.0, - label: 'negative', - sentiment: -0.54, - }, - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-12'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member2.id, - username: member2.username[PlatformType.SLACK], - sourceId: '#sourceId3', - sentiment: { - positive: 0.94, - negative: 0.0, - neutral: 0.06, - mixed: 0.0, - label: 'positive', - sentiment: 0.94, - }, - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-13'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member3.id, - username: member3.username[PlatformType.SLACK], - sourceId: '#sourceId4', - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.42, - }, - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-14'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member3.id, - username: member3.username[PlatformType.SLACK], - sourceId: '#sourceId5', - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.41, - }, - }, - { - type: 'message', - platform: PlatformType.SLACK, - timestamp: new Date('2022-09-15'), - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - memberId: member3.id, - username: member3.username[PlatformType.SLACK], - sourceId: '#sourceId6', - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.18, - }, - }, - ]) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - let members = await MemberRepository.findAndCountAllv2( - { - filter: {}, - limit: 15, - offset: 0, - orderBy: 'activityCount_DESC', - }, - mockIRepositoryOptions, - ) - expect(members.rows.length).toEqual(3) - expect(members.rows[0].activityCount).toEqual('3') - expect(members.rows[0].lastActive.toISOString()).toEqual('2022-09-15T00:00:00.000Z') - - expect(members.rows[1].activityCount).toEqual('2') - expect(members.rows[1].lastActive.toISOString()).toEqual('2022-09-12T00:00:00.000Z') - - expect(members.rows[2].activityCount).toEqual('1') - expect(members.rows[2].tags[0].name).toEqual('nodejs') - expect(members.rows[2].lastActive.toISOString()).toEqual('2022-09-10T00:00:00.000Z') - - expect(members.rows[1].tags.map((i) => i.name).sort()).toEqual(['nodejs', 'vuejs']) - expect(members.rows[0].tags[0].name).toEqual('vuejs') - - // filter and order by reach - members = await MemberRepository.findAndCountAllv2( - { - filter: { - and: [ - { - and: [ - { - reach: { - gte: 55, - }, - }, - ], - }, - ], - }, - limit: 15, - offset: 0, - orderBy: 'reach_DESC', - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(2) - expect(members.rows[0].id).toEqual(member3.id) - expect(members.rows[1].id).toEqual(member2.id) - - // filter and sort by activity count - members = await MemberRepository.findAndCountAllv2( - { - filter: { - and: [ - { - and: [ - { - activityCount: { - gte: 2, - }, - }, - ], - }, - ], - }, - limit: 15, - offset: 0, - orderBy: 'activityCount_DESC', - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(2) - expect(members.rows.map((i) => i.id)).toEqual([member3.id, member2.id]) - - // filter and sort by lastActive - members = await MemberRepository.findAndCountAllv2( - { - filter: { - and: [ - { - and: [ - { - lastActive: { - gte: '2022-09-11', - }, - }, - ], - }, - ], - }, - limit: 15, - offset: 0, - orderBy: 'lastActive_DESC', - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(2) - expect(members.rows.map((i) => i.id)).toEqual([member3.id, member2.id]) - - // filter and sort by averageSentiment (member1.avgSentiment = 0.1, member2.avgSentiment = 0.2, member3.avgSentiment = 0.34) - members = await MemberRepository.findAndCountAllv2( - { - filter: { - and: [ - { - and: [ - { - averageSentiment: { - gte: 0.2, - }, - }, - ], - }, - ], - }, - limit: 15, - offset: 0, - orderBy: 'averageSentiment_ASC', - }, - mockIRepositoryOptions, - ) - - expect(members.rows.length).toEqual(2) - expect(members.rows.map((i) => i.id)).toEqual([member2.id, member3.id]) - }) - }) - - describe('update method', () => { - it('Should succesfully update previously created member', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = { - username: { - [PlatformType.DISCORD]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: '2021-05-27T15:14:30Z', - } - let cloned = lodash.cloneDeep(member1) - const returnedMember = await MemberRepository.create(cloned, mockIRepositoryOptions) - - const updateFields = { - username: { - [PlatformType.GITHUB]: { - username: 'anil_github', - integrationId: generateUUIDv1(), - }, - }, - emails: ['lala@l.com'], - score: 10, - attributes: { - [PlatformType.GITHUB]: { - name: 'Quoc-Anh Nguyen', - isHireable: true, - url: 'https://github.com/imcvampire', - websiteUrl: 'https://imcvampire.js.org/', - bio: 'Lazy geek', - location: 'Helsinki, Finland', - actions: [ - { - score: 2, - timestamp: '2021-05-27T15:13:30Z', - }, - ], - }, - [PlatformType.TWITTER]: { - profile_url: 'https://twitter.com/imcvampire', - url: 'https://twitter.com/imcvampire', - }, - }, - joinedAt: '2021-06-27T15:14:30Z', - location: 'Istanbul', - } - - cloned = lodash.cloneDeep(updateFields) - const updatedMember = await MemberRepository.update( - returnedMember.id, - cloned, - mockIRepositoryOptions, - ) - - // check updatedAt field looks ok or not. Should be greater than createdAt - expect(updatedMember.updatedAt.getTime()).toBeGreaterThan(updatedMember.createdAt.getTime()) - - updatedMember.createdAt = updatedMember.createdAt.toISOString().split('T')[0] - updatedMember.updatedAt = updatedMember.updatedAt.toISOString().split('T')[0] - - const expectedMemberCreated = { - id: returnedMember.id, - username: mapUsername({ - ...updateFields.username, - ...member1.username, - }), - identities: ['discord', 'github'], - displayName: returnedMember.displayName, - attributes: updateFields.attributes, - emails: updateFields.emails, - score: updateFields.score, - lastEnriched: null, - enrichedBy: [], - contributions: null, - organizations: [], - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segments: mockIRepositoryOptions.currentSegments, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - reach: { total: -1 }, - notes: [], - tasks: [], - activeOn: [], - activityTypes: [], - joinedAt: new Date(updateFields.joinedAt), - tags: [], - noMerge: [], - toMerge: [], - activityCount: 0, - activeDaysCount: 0, - averageSentiment: 0, - numberOfOpenSourceContributions: 0, - lastActive: null, - lastActivity: null, - affiliations: [], - manuallyCreated: false, - } - - expect(updatedMember).toStrictEqual(expectedMemberCreated) - }) - - it('Should update successfuly but return without relations when doPopulateRelations=false', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = { - username: { - [PlatformType.DISCORD]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: '2021-05-27T15:14:30Z', - } - const returnedMember = await MemberRepository.create(member1, mockIRepositoryOptions) - - const updateFields = { - username: { - [PlatformType.GITHUB]: { - username: 'anil_github', - integrationId: generateUUIDv1(), - }, - }, - emails: ['lala@l.com'], - score: 10, - attributes: { - [PlatformType.GITHUB]: { - name: 'Quoc-Anh Nguyen', - isHireable: true, - url: 'https://github.com/imcvampire', - websiteUrl: 'https://imcvampire.js.org/', - bio: 'Lazy geek', - location: 'Helsinki, Finland', - actions: [ - { - score: 2, - timestamp: '2021-05-27T15:13:30Z', - }, - ], - }, - [PlatformType.TWITTER]: { - profile_url: 'https://twitter.com/imcvampire', - url: 'https://twitter.com/imcvampire', - }, - }, - joinedAt: '2021-06-27T15:14:30Z', - location: 'Istanbul', - } - - const updatedMember = await MemberRepository.update( - returnedMember.id, - updateFields, - mockIRepositoryOptions, - false, - ) - - // check updatedAt field looks ok or not. Should be greater than createdAt - expect(updatedMember.updatedAt.getTime()).toBeGreaterThan(updatedMember.createdAt.getTime()) - - updatedMember.createdAt = updatedMember.createdAt.toISOString().split('T')[0] - updatedMember.updatedAt = updatedMember.updatedAt.toISOString().split('T')[0] - - const expectedMemberCreated = { - id: returnedMember.id, - username: mapUsername({ - [PlatformType.DISCORD]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - [PlatformType.GITHUB]: { - username: 'anil_github', - integrationId: generateUUIDv1(), - }, - }), - displayName: returnedMember.displayName, - attributes: updateFields.attributes, - lastEnriched: null, - enrichedBy: [], - organizations: [], - contributions: null, - emails: updateFields.emails, - score: updateFields.score, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segments: mockIRepositoryOptions.currentSegments, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - reach: { total: -1 }, - joinedAt: new Date(updateFields.joinedAt), - affiliations: [], - manuallyCreated: false, - } - - expect(updatedMember).toStrictEqual(expectedMemberCreated) - }) - - it('Should successfully update member with given tags', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const tag1 = await TagRepository.create({ name: 'tag1' }, mockIRepositoryOptions) - const tag2 = await TagRepository.create({ name: 'tag2' }, mockIRepositoryOptions) - const tag3 = await TagRepository.create({ name: 'tag3' }, mockIRepositoryOptions) - - // Create member with tag3 - let member1 = await MemberRepository.create( - { - username: { - [PlatformType.DISCORD]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - tags: [tag3.id], - }, - mockIRepositoryOptions, - ) - - // When feeding tags attribute to update, update method will overwrite the member's tags with new given tags - // member1 is expected to have [tag1,tag2] after update - member1 = await MemberRepository.update( - member1.id, - { tags: [tag1.id, tag2.id] }, - mockIRepositoryOptions, - ) - - member1.createdAt = member1.createdAt.toISOString().split('T')[0] - member1.updatedAt = member1.updatedAt.toISOString().split('T')[0] - - member1.tags = member1.tags.map((i) => i.get({ plain: true })) - - // strip members field from tags created to expect. - // we won't be returning second level relationships. - const { members: _tag1Members, ...tag1Plain } = tag1 - const { members: _tag2Members, ...tag2Plain } = tag2 - - const expectedMemberCreated = { - id: member1.id, - username: member1.username, - displayName: member1.displayName, - identities: ['discord'], - attributes: {}, - emails: member1.emails, - score: member1.score, - organizations: [], - lastEnriched: null, - enrichedBy: [], - contributions: null, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segments: mockIRepositoryOptions.currentSegments, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - reach: { total: -1 }, - notes: [], - tasks: [], - activeOn: [], - activityTypes: [], - joinedAt: new Date(member1.joinedAt), - tags: [tag1Plain, tag2Plain], - noMerge: [], - toMerge: [], - activityCount: 0, - activeDaysCount: 0, - averageSentiment: 0, - numberOfOpenSourceContributions: 0, - lastActive: null, - lastActivity: null, - affiliations: [], - manuallyCreated: false, - } - - expect(member1).toStrictEqual(expectedMemberCreated) - }) - - it('Should successfully update member with given organizations', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const org1 = await OrganizationRepository.create( - { - displayName: 'crowd.dev', - identities: [{ name: 'crowd.dev', url: 'https://crowd.dev', platform: 'crowd' }], - }, - mockIRepositoryOptions, - ) - const org2 = await OrganizationRepository.create( - { - displayName: 'pied piper', - identities: [{ name: 'pied piper', url: 'https://piedpiper.com', platform: 'crowd' }], - }, - mockIRepositoryOptions, - ) - const org3 = await OrganizationRepository.create( - { - displayName: 'hooli', - identities: [{ name: 'hooli', url: 'https://hooli.com', platform: 'crowd' }], - }, - mockIRepositoryOptions, - ) - - // Create member with tag3 - let member1 = await MemberRepository.create( - { - username: { - [PlatformType.DISCORD]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: new Date(), - organizations: [org3.id], - }, - mockIRepositoryOptions, - ) - - // When feeding organizations attribute to update, update method will overwrite the member's organizations with new given orgs - // member1 is expected to have [org1,org2] after update - member1 = await MemberRepository.update( - member1.id, - { organizations: [org1.id, org2.id], organizationsReplace: true }, - mockIRepositoryOptions, - ) - - member1.createdAt = member1.createdAt.toISOString().split('T')[0] - member1.updatedAt = member1.updatedAt.toISOString().split('T')[0] - - member1.organizations = member1.organizations.map((i) => - SequelizeTestUtils.objectWithoutKey(i.get({ plain: true }), ['memberOrganizations']), - ) - - // // sort member organizations by createdAt - // member1.organizations.sort((a, b) => { - // return a.createdAt < b.createdAt ? -1 : 1 - // }) - - // strip members field from tags created to expect. - // we won't be returning second level relationships. - const { memberCount: _tag1Members, ...org1Plain } = org1 - const { memberCount: _tag2Members, ...org2Plain } = org2 - - const expectedMemberCreated = { - id: member1.id, - username: member1.username, - displayName: member1.displayName, - identities: ['discord'], - attributes: {}, - emails: member1.emails, - score: member1.score, - tags: [], - lastEnriched: null, - enrichedBy: [], - contributions: null, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segments: mockIRepositoryOptions.currentSegments, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - activeOn: [], - activityTypes: [], - reach: { total: -1 }, - joinedAt: new Date(member1.joinedAt), - organizations: [ - SequelizeTestUtils.objectWithoutKey(org1Plain, [ - 'lastActive', - 'identities', - 'activeOn', - 'joinedAt', - 'activityCount', - 'segments', - 'weakIdentities', - ]), - SequelizeTestUtils.objectWithoutKey(org2Plain, [ - 'lastActive', - 'identities', - 'activeOn', - 'joinedAt', - 'activityCount', - 'segments', - 'weakIdentities', - ]), - ], - noMerge: [], - toMerge: [], - notes: [], - tasks: [], - activityCount: 0, - activeDaysCount: 0, - averageSentiment: 0, - numberOfOpenSourceContributions: 0, - lastActive: null, - lastActivity: null, - affiliations: [], - manuallyCreated: false, - } - - member1.organizations = member1.organizations.sort((a, b) => { - if (a.displayName < b.displayName) { - return -1 - } - if (a.displayName > b.displayName) { - return 1 - } - return 0 - }) - - expect(member1).toStrictEqual(expectedMemberCreated) - }) - - it('Should succesfully update member with notes', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const notes1 = await NoteRepository.create( - { - body: 'note1', - }, - mockIRepositoryOptions, - ) - - const notes2 = await NoteRepository.create( - { - body: 'note2', - }, - mockIRepositoryOptions, - ) - - const member2add = { - username: { - [PlatformType.DISCORD]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - } - - const memberCreated = await MemberRepository.create(member2add, mockIRepositoryOptions) - const memberUpdated = await MemberRepository.update( - memberCreated.id, - { notes: [notes1.id, notes2.id] }, - mockIRepositoryOptions, - ) - expect(memberCreated.notes).toHaveLength(0) - expect(memberUpdated.notes).toHaveLength(2) - expect(memberUpdated.notes[0].id).toEqual(notes1.id) - expect(memberUpdated.notes[1].id).toEqual(notes2.id) - }) - - it('Should succesfully update member with tasks', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const tasks1 = await TaskRepository.create( - { - name: 'task1', - }, - mockIRepositoryOptions, - ) - - const task2 = await TaskRepository.create( - { - name: 'task2', - }, - mockIRepositoryOptions, - ) - - const member2add = { - username: { - [PlatformType.DISCORD]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - } - - const memberCreated = await MemberRepository.create(member2add, mockIRepositoryOptions) - expect(memberCreated.tasks).toHaveLength(0) - - const memberUpdated = await MemberRepository.update( - memberCreated.id, - { tasks: [tasks1.id, task2.id] }, - mockIRepositoryOptions, - ) - expect(memberUpdated.tasks).toHaveLength(2) - expect(memberUpdated.tasks.find((t) => t.id === tasks1.id)).not.toBeUndefined() - expect(memberUpdated.tasks.find((t) => t.id === task2.id)).not.toBeUndefined() - }) - - it('Should throw 404 error when trying to update non existent member', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const { randomUUID } = require('crypto') - - await expect(() => - MemberRepository.update(randomUUID(), { location: 'test' }, mockIRepositoryOptions), - ).rejects.toThrowError(new Error404()) - }) - - it('Should throw a sequelize foreign key error when trying to update a member with a non existing tag', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const { randomUUID } = require('crypto') - - const member1 = await MemberRepository.create( - { - username: { - [PlatformType.DISCORD]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: new Date(), - }, - mockIRepositoryOptions, - ) - - await expect(() => - MemberRepository.update(member1.id, { tags: [randomUUID()] }, mockIRepositoryOptions), - ).rejects.toThrow() - }) - - it('Should succesfully update member organization affiliations', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const segmentRepo = new SegmentRepository(mockIRepositoryOptions) - - const segment1 = await segmentRepo.create({ - name: 'Crowd.dev - Segment1', - url: '', - parentName: 'Crowd.dev - Segment1', - grandparentName: 'Crowd.dev - Segment1', - slug: 'crowd.dev-1', - parentSlug: 'crowd.dev-1', - grandparentSlug: 'crowd.dev-1', - status: SegmentStatus.ACTIVE, - sourceId: null, - sourceParentId: null, - }) - - const segment2 = await segmentRepo.create({ - name: 'Crowd.dev - Segment2', - url: '', - parentName: 'Crowd.dev - Segment2', - grandparentName: 'Crowd.dev - Segment2', - slug: 'crowd.dev-2', - parentSlug: 'crowd.dev-2', - grandparentSlug: 'crowd.dev-2', - status: SegmentStatus.ACTIVE, - sourceId: null, - sourceParentId: null, - }) - - const org1 = await OrganizationRepository.create( - { - displayName: 'crowd.dev', - }, - mockIRepositoryOptions, - ) - - const member2add = { - username: { - [PlatformType.DISCORD]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - affiliations: [ - { - segmentId: segment1.id, - organizationId: org1.id, - }, - { - segmentId: segment2.id, - organizationId: null, - }, - ], - } - - const memberCreated = await MemberRepository.create(member2add, mockIRepositoryOptions) - expect(memberCreated.affiliations).toHaveLength(2) - - // removes segment1 affiliation, and set segment2 affilition to org1 - const memberUpdated = await MemberRepository.update( - memberCreated.id, - { - affiliations: [ - { - segmentId: segment2.id, - organizationId: org1.id, - }, - ], - }, - mockIRepositoryOptions, - ) - - expect(memberUpdated.affiliations.filter((a) => a.segmentId === segment1.id)).toHaveLength(0) - expect( - memberUpdated.affiliations.filter((a) => a.segmentId === segment2.id)[0].organizationId, - ).toEqual(org1.id) - }) - }) - - describe('destroy method', () => { - it('Should succesfully destroy previously created member', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = { - username: { - [PlatformType.DISCORD]: { - username: 'test1', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - score: '1', - joinedAt: '2021-05-27T15:14:30Z', - } - const returnedMember = await MemberRepository.create(member1, mockIRepositoryOptions) - - await MemberRepository.destroy(returnedMember.id, mockIRepositoryOptions, true) - - // Try selecting it after destroy, should throw 404 - await expect(() => - MemberRepository.findById(returnedMember.id, mockIRepositoryOptions), - ).rejects.toThrowError(new Error404()) - }) - - it('Should throw 404 when trying to destroy a non existent member', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const { randomUUID } = require('crypto') - - await expect(() => - MemberRepository.destroy(randomUUID(), mockIRepositoryOptions), - ).rejects.toThrowError(new Error404()) - }) - }) - - describe('removeToMerge method', () => { - it('Should remove a member from other members toMerge list', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = { - username: { - [PlatformType.DISCORD]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - } - - const member2 = { - username: { - [PlatformType.DISCORD]: { - username: 'anil2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - joinedAt: '2020-05-27T15:13:30Z', - } - - const memberCreated1 = await MemberRepository.create(member1, mockIRepositoryOptions) - const memberCreated2 = await MemberRepository.create(member2, mockIRepositoryOptions) - - await MemberRepository.addToMerge( - [{ members: [memberCreated1.id, memberCreated2.id], similarity: null }], - mockIRepositoryOptions, - ) - await MemberRepository.addToMerge( - [{ members: [memberCreated2.id, memberCreated1.id], similarity: null }], - mockIRepositoryOptions, - ) - - let m1 = await MemberRepository.findById(memberCreated1.id, mockIRepositoryOptions) - const m2 = await MemberRepository.findById(memberCreated2.id, mockIRepositoryOptions) - m1 = await MemberRepository.removeToMerge( - memberCreated1.id, - memberCreated2.id, - mockIRepositoryOptions, - ) - - // Member2 should be removed from Member1.toMerge - expect(m1.toMerge.length).toBe(0) - - // Member1 is still in member2.toMerge list - expect(m2.toMerge[0]).toBe(m1.id) - }) - }) - - describe('addNoMerge method', () => { - it('Should add a member to other members noMerge list', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = { - username: { - [PlatformType.DISCORD]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - } - - const member2 = { - username: { - [PlatformType.DISCORD]: { - username: 'anil2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - joinedAt: '2020-05-27T15:13:30Z', - } - - const memberCreated1 = await MemberRepository.create(member1, mockIRepositoryOptions) - const memberCreated2 = await MemberRepository.create(member2, mockIRepositoryOptions) - - let memberUpdated1 = await MemberRepository.addNoMerge( - memberCreated1.id, - memberCreated2.id, - mockIRepositoryOptions, - ) - const memberUpdated2 = await MemberRepository.addNoMerge( - memberCreated2.id, - memberCreated1.id, - mockIRepositoryOptions, - ) - - memberUpdated1 = await MemberRepository.removeToMerge( - memberCreated1.id, - memberCreated2.id, - mockIRepositoryOptions, - ) - - expect(memberUpdated1.noMerge[0]).toBe(memberUpdated2.id) - expect(memberUpdated2.noMerge[0]).toBe(memberUpdated1.id) - }) - }) - - describe('removeNoMerge method', () => { - let options - let memberService - - let defaultMember - - beforeEach(async () => { - options = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(options) - - memberService = new MemberService(options) - - defaultMember = { - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - } - }) - it('Should remove a member from other members noMerge list', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const member1 = { - username: { - [PlatformType.DISCORD]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 1', - joinedAt: '2020-05-27T15:13:30Z', - } - - const member2 = { - username: { - [PlatformType.DISCORD]: { - username: 'anil2', - integrationId: generateUUIDv1(), - }, - }, - displayName: 'Member 2', - joinedAt: '2020-05-27T15:13:30Z', - } - - const memberCreated1 = await MemberRepository.create(member1, mockIRepositoryOptions) - const memberCreated2 = await MemberRepository.create(member2, mockIRepositoryOptions) - - let memberUpdated1 = await MemberRepository.addNoMerge( - memberCreated1.id, - memberCreated2.id, - mockIRepositoryOptions, - ) - const memberUpdated2 = await MemberRepository.addNoMerge( - memberCreated2.id, - memberCreated1.id, - mockIRepositoryOptions, - ) - - memberUpdated1 = await MemberRepository.removeNoMerge( - memberCreated1.id, - memberCreated2.id, - mockIRepositoryOptions, - ) - - // Member2 should be removed from Member1.noMerge - expect(memberUpdated1.noMerge.length).toBe(0) - - // Member1 is still in member2.noMerge list - expect(memberUpdated2.noMerge[0]).toBe(memberUpdated1.id) - }) - }) - - describe('work experiences', () => { - let options - let memberService - let organizationService - - let defaultMember - - beforeEach(async () => { - options = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(options) - - memberService = new MemberService(options) - organizationService = new OrganizationService(options) - - defaultMember = { - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - } - }) - - async function createMember(data = {}) { - return await memberService.upsert({ - ...defaultMember, - username: { - [PlatformType.GITHUB]: uuid(), - }, - ...data, - }) - } - - async function createOrg(name, data = {}) { - return await organizationService.createOrUpdate({ - identities: [ - { - name, - platform: 'crowd', - }, - ], - ...data, - }) - } - - async function addWorkExperience(memberId, orgId, data = {}) { - return await MemberRepository.createOrUpdateWorkExperience( - { - memberId, - organizationId: orgId, - source: 'test', - ...data, - }, - options, - ) - } - - async function findMember(id) { - return await memberService.findById(id) - } - - function formatDate(value) { - if (!value) { - return null - } - return moment(value).format('YYYY-MM-DD') - } - - it('Should not create multiple work experiences for same org without dates', async () => { - let member = await createMember() - - const org = await createOrg('org') - - await addWorkExperience(member.id, org.id) - await addWorkExperience(member.id, org.id) - - member = await findMember(member.id) - - expect(member.organizations.length).toBe(1) - }) - - it('Should not create multiple work experiences for same org with same start dates', async () => { - let member = await createMember() - - const org = await createOrg('org') - - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-01', - }) - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-01', - }) - - member = await findMember(member.id) - - expect(member.organizations.length).toBe(1) - }) - - it('Should not create multiple work experiences for same org with same dates', async () => { - let member = await createMember() - - const org = await createOrg('org') - - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-01', - dateEnd: '2020-01-05', - }) - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-01', - dateEnd: '2020-01-05', - }) - - member = await findMember(member.id) - - expect(member.organizations.length).toBe(1) - }) - - it('Should create multiple work experiences for same org with different dates', async () => { - let member = await createMember() - - const org = await createOrg('org') - - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-01', - }) - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-08', - }) - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-01', - dateEnd: '2020-01-05', - }) - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-06', - dateEnd: '2020-01-07', - }) - - member = await findMember(member.id) - - expect(member.organizations.length).toBe(4) - }) - - it('Should clean up work experiences without dates once we get start dates', async () => { - let member = await createMember() - - const org = await createOrg('org') - - await addWorkExperience(member.id, org.id) - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-01', - }) - - member = await findMember(member.id) - - expect(member.organizations.length).toBe(1) - const dates = member.organizations[0].memberOrganizations.dataValues - expect(formatDate(dates.dateStart)).toBe('2020-01-01') - expect(formatDate(dates.dateEnd)).toBeNull() - }) - it('Should clean up work experiences without dates once we get both dates', async () => { - let member = await createMember() - - const org = await createOrg('org') - - await addWorkExperience(member.id, org.id) - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-01', - dateEnd: '2020-07-01', - }) - - member = await findMember(member.id) - - expect(member.organizations.length).toBe(1) - const dates = member.organizations[0].memberOrganizations.dataValues - expect(formatDate(dates.dateStart)).toBe('2020-01-01') - expect(formatDate(dates.dateEnd)).toBe('2020-07-01') - }) - it('Should not add new work experiences without dates if we have start dates', async () => { - let member = await createMember() - - const org = await createOrg('org') - - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-01', - }) - await addWorkExperience(member.id, org.id) - - member = await findMember(member.id) - - expect(member.organizations.length).toBe(1) - const dates = member.organizations[0].memberOrganizations.dataValues - expect(formatDate(dates.dateStart)).toBe('2020-01-01') - expect(formatDate(dates.dateEnd)).toBeNull() - }) - it('Should not add new work experiences without dates if we have both dates', async () => { - let member = await createMember() - - const org = await createOrg('org') - - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-01', - dateEnd: '2020-07-01', - }) - await addWorkExperience(member.id, org.id) - - member = await findMember(member.id) - - expect(member.organizations.length).toBe(1) - const dates = member.organizations[0].memberOrganizations.dataValues - expect(formatDate(dates.dateStart)).toBe('2020-01-01') - expect(formatDate(dates.dateEnd)).toBe('2020-07-01') - }) - }) -}) diff --git a/backend/src/database/repositories/memberRepository.ts b/backend/src/database/repositories/memberRepository.ts index 4f34f05f66..ba00a843c4 100644 --- a/backend/src/database/repositories/memberRepository.ts +++ b/backend/src/database/repositories/memberRepository.ts @@ -47,7 +47,6 @@ import { } from './types/memberTypes' import OrganizationRepository from './organizationRepository' import MemberSyncRemoteRepository from './memberSyncRemoteRepository' -import MemberAffiliationRepository from './memberAffiliationRepository' import MemberAttributeSettingsRepository from './memberAttributeSettingsRepository' const { Op } = Sequelize @@ -950,7 +949,6 @@ class MemberRepository { ): Promise { const affiliationRepository = new MemberSegmentAffiliationRepository(options) await affiliationRepository.setForMember(memberId, data) - await MemberAffiliationRepository.update(memberId, options) } static async getAffiliations( @@ -3457,15 +3455,7 @@ class MemberRepository { } static async createOrUpdateWorkExperience( - { - memberId, - organizationId, - source, - title = null, - dateStart = null, - dateEnd = null, - updateAffiliation = true, - }, + { memberId, organizationId, source, title = null, dateStart = null, dateEnd = null }, options: IRepositoryOptions, ) { const seq = SequelizeRepository.getSequelize(options) @@ -3548,10 +3538,6 @@ class MemberRepository { transaction, }, ) - - if (updateAffiliation) { - await MemberAffiliationRepository.update(memberId, options) - } } static async deleteWorkExperience(id, options: IRepositoryOptions) { diff --git a/backend/src/services/__tests__/activityService.test.ts b/backend/src/services/__tests__/activityService.test.ts deleted file mode 100644 index 72edf61759..0000000000 --- a/backend/src/services/__tests__/activityService.test.ts +++ /dev/null @@ -1,3193 +0,0 @@ -import { v4 as uuid } from 'uuid' - -import SequelizeTestUtils from '../../database/utils/sequelizeTestUtils' -import MemberService from '../memberService' -import ActivityService from '../activityService' -import MemberRepository from '../../database/repositories/memberRepository' -import ActivityRepository from '../../database/repositories/activityRepository' -import ConversationService from '../conversationService' -import SequelizeRepository from '../../database/repositories/sequelizeRepository' -import { MemberAttributeName, PlatformType, SegmentStatus } from '@crowd/types' -import SettingsRepository from '../../database/repositories/settingsRepository' -import ConversationSettingsRepository from '../../database/repositories/conversationSettingsRepository' -import MemberAttributeSettingsService from '../memberAttributeSettingsService' -import { IServiceOptions } from '../../services/IServiceOptions' -import { GITHUB_MEMBER_ATTRIBUTES, TWITTER_MEMBER_ATTRIBUTES } from '@crowd/integrations' -import { populateSegments, switchSegments } from '../../database/utils/segmentTestUtils' -import SegmentRepository from '../../database/repositories/segmentRepository' -import OrganizationRepository from '../../database/repositories/organizationRepository' -import OrganizationService from '../organizationService' -import MemberSegmentAffiliationRepository from '../../database/repositories/memberSegmentAffiliationRepository' -import SegmentService from '../segmentService' -import MemberAffiliationService from '../memberAffiliationService' - -const db = null -const searchEngine = null - -describe('ActivityService tests', () => { - beforeEach(async () => { - await SequelizeTestUtils.wipeDatabase(db) - }) - - afterAll(async () => { - // Closing the DB connection allows Jest to exit successfully. - await SequelizeTestUtils.closeConnection(db) - }) - - describe('upsert method', () => { - it('Should create non existent activity with no parent', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - body: 'Body', - title: 'Title', - url: 'URL', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - attributes: { - replies: 12, - }, - sourceId: '#sourceId', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - } - - const activityCreated = await new ActivityService(mockIRepositoryOptions).upsert(activity) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - activityCreated.createdAt = activityCreated.createdAt.toISOString().split('T')[0] - activityCreated.updatedAt = activityCreated.updatedAt.toISOString().split('T')[0] - delete activityCreated.member - delete activityCreated.objectMember - - const expectedActivityCreated = { - id: activityCreated.id, - attributes: activity.attributes, - type: 'activity', - timestamp: new Date('2020-05-27T15:13:30Z'), - platform: PlatformType.GITHUB, - isContribution: true, - score: 1, - username: 'test', - objectMemberUsername: null, - memberId: memberCreated.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - channel: null, - body: 'Body', - title: 'Title', - url: 'URL', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - tasks: [], - parent: null, - parentId: null, - conversationId: null, - sourceId: activity.sourceId, - sourceParentId: null, - display: { - default: activityCreated.type, - short: activityCreated.type, - channel: '', - }, - organizationId: null, - organization: null, - } - - expect(activityCreated).toStrictEqual(expectedActivityCreated) - }) - - it('Should create non existent activity with parent', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - const activity1 = { - type: 'question', - timestamp: '2020-05-27T15:13:30Z', - username: 'test', - member: memberCreated.id, - platform: 'non-existing-platform', - body: 'What is love?', - isContribution: true, - score: 1, - sourceId: 'sourceId#1', - } - - const activityCreated1 = await new ActivityService(mockIRepositoryOptions).upsert(activity1) - - const activity2 = { - type: 'answer', - timestamp: '2020-05-28T15:13:30Z', - platform: 'non-existing-platform', - body: 'Baby dont hurt me', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 2, - sourceId: 'sourceId#2', - sourceParentId: activityCreated1.sourceId, - } - - const activityCreated2 = await new ActivityService(mockIRepositoryOptions).upsert(activity2) - - // Since an activity with a parent is created, a Conversation entity should be created at this point - // with both parent and the child activities. Try finding it using the slug - - const conversationCreated = await new ConversationService( - mockIRepositoryOptions, - ).findAndCountAll({ slug: 'what-is-love' }) - - delete activityCreated2.member - delete activityCreated2.parent - delete activityCreated2.objectMember - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - activityCreated2.createdAt = activityCreated2.createdAt.toISOString().split('T')[0] - activityCreated2.updatedAt = activityCreated2.updatedAt.toISOString().split('T')[0] - - const expectedActivityCreated = { - id: activityCreated2.id, - body: activity2.body, - type: activity2.type, - channel: null, - attributes: {}, - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.42, - }, - url: null, - title: null, - timestamp: new Date(activity2.timestamp), - platform: activity2.platform, - isContribution: activity2.isContribution, - score: activity2.score, - username: 'test', - objectMemberUsername: null, - memberId: memberCreated.id, - objectMemberId: null, - tasks: [], - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parentId: activityCreated1.id, - sourceParentId: activity1.sourceId, - sourceId: activity2.sourceId, - conversationId: conversationCreated.rows[0].id, - display: { - default: activity2.type, - short: activity2.type, - channel: '', - }, - organizationId: null, - organization: null, - } - - expect(activityCreated2).toStrictEqual(expectedActivityCreated) - }) - - it('Should update already existing activity succesfully', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - const activity1 = { - type: 'question', - timestamp: '2020-05-27T15:13:30Z', - username: 'test', - member: memberCreated.id, - body: 'What is love?', - title: 'Song', - platform: 'non-existing-platform', - attributes: { - nested_1: { - attribute_1: '1', - nested_2: { - attribute_2: '2', - attribute_array: [1, 2, 3], - }, - }, - }, - isContribution: true, - score: 1, - sourceId: '#sourceId1', - } - - const activityCreated1 = await new ActivityService(mockIRepositoryOptions).upsert(activity1) - - const activity2 = { - type: 'question', - timestamp: '2020-05-27T15:13:30Z', - username: 'test', - member: memberCreated.id, - platform: 'non-existing-platform', - body: 'Test', - attributes: { - nested_1: { - attribute_1: '1', - nested_2: { - attribute_2: '5', - attribute_3: 'test', - attribute_array: [3, 4, 5], - }, - }, - one: 'Baby dont hurt me', - two: 'Dont hurt me', - three: 'No more', - }, - isContribution: false, - score: 2, - sourceId: '#sourceId1', - } - - const activityUpserted = await new ActivityService(mockIRepositoryOptions).upsert(activity2) - - // Trim the hour part from timestamp so we can atleast test if the day is correct for createdAt and joinedAt - activityUpserted.createdAt = activityUpserted.createdAt.toISOString().split('T')[0] - activityUpserted.updatedAt = activityUpserted.updatedAt.toISOString().split('T')[0] - - // delete models before expect because we already have ids (memberId, parentId) - delete activityUpserted.member - delete activityUpserted.parent - delete activityUpserted.objectMember - - const attributesExpected = { - ...activity1.attributes, - ...activity2.attributes, - } - - attributesExpected.nested_1.nested_2.attribute_array = [1, 2, 3, 4, 5] - - const expectedActivityCreated = { - id: activityCreated1.id, - attributes: attributesExpected, - type: activity2.type, - timestamp: new Date(activity2.timestamp), - platform: activity2.platform, - isContribution: activity2.isContribution, - score: activity2.score, - title: activity1.title, - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.42, - }, - url: null, - body: activity2.body, - channel: null, - username: 'test', - objectMemberUsername: null, - memberId: memberCreated.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - tasks: [], - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parentId: null, - sourceParentId: null, - sourceId: activity1.sourceId, - conversationId: null, - display: { - default: activity2.type, - short: activity2.type, - channel: '', - }, - organizationId: null, - organization: null, - } - - expect(activityUpserted).toStrictEqual(expectedActivityCreated) - }) - - it('Should create various conversations successfully with given parent-child relationships of activities [ascending timestamp order]', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberService = new MemberService(mockIRepositoryOptions) - const activityService = new ActivityService(mockIRepositoryOptions) - - const member1Created = await memberService.upsert({ - username: { - [PlatformType.DISCORD]: 'test', - }, - platform: PlatformType.DISCORD, - joinedAt: '2020-05-27T15:13:30Z', - }) - const member2Created = await memberService.upsert({ - username: { - [PlatformType.DISCORD]: 'test2', - }, - platform: PlatformType.DISCORD, - joinedAt: '2020-05-27T15:13:30Z', - }) - - // Simulate a reply chain in discord - - const activity1 = { - type: 'message', - timestamp: '2020-05-27T15:13:30Z', - username: 'test', - member: member1Created.id, - platform: PlatformType.DISCORD, - body: 'What is love?', - isContribution: true, - score: 1, - sourceId: 'sourceId#1', - } - - let activityCreated1 = await activityService.upsert(activity1) - - const activity2 = { - type: 'message', - timestamp: '2020-05-28T15:14:30Z', - platform: PlatformType.DISCORD, - body: 'Baby dont hurt me', - isContribution: true, - username: 'test2', - member: member2Created.id, - score: 2, - sourceId: 'sourceId#2', - sourceParentId: activityCreated1.sourceId, - } - - const activityCreated2 = await activityService.upsert(activity2) - - const activity3 = { - type: 'message', - timestamp: '2020-05-28T15:15:30Z', - platform: PlatformType.DISCORD, - body: 'Dont hurt me', - isContribution: true, - username: 'test', - member: member1Created.id, - score: 2, - sourceId: 'sourceId#3', - sourceParentId: activityCreated2.sourceId, - } - - const activityCreated3 = await activityService.upsert(activity3) - - const activity4 = { - type: 'message', - timestamp: '2020-05-28T15:16:30Z', - platform: PlatformType.DISCORD, - body: 'No more', - isContribution: true, - username: 'test2', - member: member2Created.id, - score: 2, - sourceId: 'sourceId#4', - sourceParentId: activityCreated3.sourceId, - } - - const activityCreated4 = await activityService.upsert(activity4) - - // Get the conversation using slug (generated using the chain starter activity attributes.body) - const conversationCreated = ( - await new ConversationService(mockIRepositoryOptions).findAndCountAll({ - slug: 'what-is-love', - }) - ).rows[0] - - // We have to get activity1 again because conversation creation happens - // after creation of the first activity that has a parent (activity2) - activityCreated1 = await activityService.findById(activityCreated1.id) - - // All activities (including chain starter) should belong to the same conversation - expect(activityCreated1.conversationId).toStrictEqual(conversationCreated.id) - expect(activityCreated2.conversationId).toStrictEqual(conversationCreated.id) - expect(activityCreated3.conversationId).toStrictEqual(conversationCreated.id) - expect(activityCreated4.conversationId).toStrictEqual(conversationCreated.id) - - // Emulate a thread in discord - - const activity5 = { - type: 'message', - timestamp: '2020-05-28T15:17:30Z', - platform: PlatformType.DISCORD, - body: 'Never gonna give you up', - isContribution: true, - username: 'test', - member: member1Created.id, - score: 2, - sourceId: 'sourceId#5', - } - let activityCreated5 = await activityService.upsert(activity5) - - const activity6 = { - type: 'message', - timestamp: '2020-05-28T15:18:30Z', - platform: PlatformType.DISCORD, - body: 'Never gonna let you down', - isContribution: true, - username: 'test2', - member: member2Created.id, - score: 2, - sourceId: 'sourceId#6', - sourceParentId: activityCreated5.sourceId, - } - const activityCreated6 = await activityService.upsert(activity6) - - const activity7 = { - type: 'message', - timestamp: '2020-05-28T15:19:30Z', - platform: PlatformType.DISCORD, - body: 'Never gonna run around and desert you', - isContribution: true, - username: 'test', - member: member1Created.id, - score: 2, - sourceId: 'sourceId#7', - sourceParentId: activityCreated5.sourceId, - } - const activityCreated7 = await activityService.upsert(activity7) - - const conversationCreated2 = ( - await new ConversationService(mockIRepositoryOptions).findAndCountAll({ - slug: 'never-gonna-give-you-up', - }) - ).rows[0] - - activityCreated5 = await activityService.findById(activityCreated5.id) - - // All activities (including thread starter) should belong to the same conversation - expect(activityCreated5.conversationId).toStrictEqual(conversationCreated2.id) - expect(activityCreated6.conversationId).toStrictEqual(conversationCreated2.id) - expect(activityCreated7.conversationId).toStrictEqual(conversationCreated2.id) - }) - - it('Should keep old timestamp', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberService = new MemberService(mockIRepositoryOptions) - const activityService = new ActivityService(mockIRepositoryOptions) - - const cm = await memberService.upsert({ - username: { - [PlatformType.DISCORD]: 'test', - }, - platform: PlatformType.DISCORD, - }) - - const activity1 = { - type: 'message', - timestamp: '2020-05-27T15:13:30Z', - username: 'test', - member: cm.id, - platform: PlatformType.DISCORD, - sourceId: 'sourceId#1', - } - - const activityCreated1 = await activityService.upsert(activity1) - - const activity2 = { - type: 'message', - timestamp: '2022-05-27T15:13:30Z', - username: 'test', - member: cm.id, - platform: PlatformType.DISCORD, - sourceId: 'sourceId#1', - body: 'What is love?', - } - - const activityCreated2 = await activityService.upsert(activity2) - - expect(activityCreated2.timestamp).toStrictEqual(activityCreated1.timestamp) - expect(activityCreated2.body).toBe(activity2.body) - }) - - it('Should keep isMainBranch as true', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberService = new MemberService(mockIRepositoryOptions) - const activityService = new ActivityService(mockIRepositoryOptions) - - const cm = await memberService.upsert({ - username: { - [PlatformType.DISCORD]: 'test', - }, - platform: PlatformType.DISCORD, - }) - - const activity1 = { - type: 'message', - timestamp: '2020-05-27T15:13:30Z', - username: 'test', - member: cm.id, - platform: PlatformType.DISCORD, - sourceId: 'sourceId#1', - attributes: { - isMainBranch: true, - other: 'other', - }, - } - - await activityService.upsert(activity1) - - const activity2 = { - type: 'message', - timestamp: '2022-05-27T15:13:30Z', - username: 'test', - member: cm.id, - platform: PlatformType.DISCORD, - sourceId: 'sourceId#1', - body: 'What is love?', - attributes: { - isMainBranch: false, - other2: 'other2', - }, - } - - const activityCreated2 = await activityService.upsert(activity2) - - expect(activityCreated2.attributes).toStrictEqual({ - isMainBranch: true, - other: 'other', - other2: 'other2', - }) - }) - - it('Should create various conversations successfully with given parent-child relationships of activities [descending timestamp order]', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberService = new MemberService(mockIRepositoryOptions) - const activityService = new ActivityService(mockIRepositoryOptions) - - const member1Created = await memberService.upsert({ - username: { - [PlatformType.DISCORD]: 'test', - }, - platform: PlatformType.DISCORD, - joinedAt: '2020-05-27T15:13:30Z', - }) - - const member2Created = await memberService.upsert({ - username: { - [PlatformType.DISCORD]: 'test2', - }, - platform: PlatformType.DISCORD, - joinedAt: '2020-05-27T15:13:30Z', - }) - - // Simulate a reply chain in discord in reverse order (child activities come first) - - const activity4 = { - type: 'message', - timestamp: '2020-05-28T15:16:30Z', - platform: PlatformType.DISCORD, - body: 'No more', - isContribution: true, - username: 'test2', - member: member2Created.id, - score: 2, - sourceId: 'sourceId#4', - sourceParentId: 'sourceId#3', - } - - let activityCreated4 = await activityService.upsert(activity4) - - const activity3 = { - type: 'message', - timestamp: '2020-05-28T15:15:30Z', - platform: PlatformType.DISCORD, - body: 'Dont hurt me', - isContribution: true, - username: 'test', - member: member1Created.id, - score: 2, - sourceId: 'sourceId#3', - sourceParentId: 'sourceId#2', - } - - let activityCreated3 = await activityService.upsert(activity3) - - const activity2 = { - type: 'message', - timestamp: '2020-05-28T15:14:30Z', - platform: PlatformType.DISCORD, - body: 'Baby dont hurt me', - isContribution: true, - username: 'test2', - member: member2Created.id, - score: 2, - sourceId: 'sourceId#2', - sourceParentId: 'sourceId#1', - } - - let activityCreated2 = await activityService.upsert(activity2) - - const activity1 = { - type: 'message', - timestamp: '2020-05-27T15:13:30Z', - username: 'test', - member: member1Created.id, - platform: PlatformType.DISCORD, - body: 'What is love?', - isContribution: true, - score: 1, - sourceId: 'sourceId#1', - } - - // main parent activity that starts the reply chain - let activityCreated1 = await activityService.upsert(activity1) - - // get activities again - activityCreated1 = await activityService.findById(activityCreated1.id) - activityCreated2 = await activityService.findById(activityCreated2.id) - activityCreated3 = await activityService.findById(activityCreated3.id) - activityCreated4 = await activityService.findById(activityCreated4.id) - - // expect parentIds - expect(activityCreated4.parentId).toBe(activityCreated3.id) - expect(activityCreated3.parentId).toBe(activityCreated2.id) - expect(activityCreated2.parentId).toBe(activityCreated1.id) - - // Get the conversation using slug (generated using the chain starter activity attributes.body -last added activityCreated1-) - const conversationCreated = ( - await new ConversationService(mockIRepositoryOptions).findAndCountAll({ - slug: 'what-is-love', - }) - ).rows[0] - - // All activities (including chain starter) should belong to the same conversation - expect(activityCreated1.conversationId).toStrictEqual(conversationCreated.id) - expect(activityCreated2.conversationId).toStrictEqual(conversationCreated.id) - expect(activityCreated3.conversationId).toStrictEqual(conversationCreated.id) - expect(activityCreated4.conversationId).toStrictEqual(conversationCreated.id) - - // Simulate a thread in reverse order - - const activity6 = { - type: 'message', - timestamp: '2020-05-28T15:18:30Z', - platform: PlatformType.DISCORD, - body: 'Never gonna let you down', - isContribution: true, - username: 'test2', - member: member2Created.id, - score: 2, - sourceId: 'sourceId#6', - sourceParentId: 'sourceId#5', - } - let activityCreated6 = await activityService.upsert(activity6) - - const activity7 = { - type: 'message', - timestamp: '2020-05-28T15:19:30Z', - platform: PlatformType.DISCORD, - body: 'Never gonna run around and desert you', - - isContribution: true, - username: 'test', - member: member1Created.id, - score: 2, - sourceId: 'sourceId#7', - sourceParentId: 'sourceId#5', - } - let activityCreated7 = await activityService.upsert(activity7) - - const activity5 = { - type: 'message', - timestamp: '2020-05-28T15:17:30Z', - platform: PlatformType.DISCORD, - body: 'Never gonna give you up', - isContribution: true, - username: 'test', - member: member1Created.id, - score: 2, - sourceId: 'sourceId#5', - } - let activityCreated5 = await activityService.upsert(activity5) - - const conversationCreated2 = ( - await new ConversationService(mockIRepositoryOptions).findAndCountAll({ - slug: 'never-gonna-give-you-up', - }) - ).rows[0] - - // get activities again - activityCreated5 = await activityService.findById(activityCreated5.id) - activityCreated6 = await activityService.findById(activityCreated6.id) - activityCreated7 = await activityService.findById(activityCreated7.id) - - // expect parentIds - expect(activityCreated6.parentId).toBe(activityCreated5.id) - expect(activityCreated7.parentId).toBe(activityCreated5.id) - - expect(activityCreated5.conversationId).toStrictEqual(conversationCreated2.id) - expect(activityCreated6.conversationId).toStrictEqual(conversationCreated2.id) - expect(activityCreated7.conversationId).toStrictEqual(conversationCreated2.id) - - // Add some more childs to the conversation1 and conversation2 - // After setting child-parent in reverse order, we're now adding - // some more childiren in normal order - - // add a new reply to the chain-starter activity - const activity8 = { - type: 'message', - timestamp: '2020-05-28T15:21:30Z', - platform: PlatformType.DISCORD, - body: 'additional reply to the reply chain', - isContribution: true, - username: 'test2', - member: member2Created.id, - score: 2, - sourceId: 'sourceId#8', - sourceParentId: 'sourceId#1', - } - - const activityCreated8 = await activityService.upsert(activity8) - - expect(activityCreated8.parentId).toBe(activityCreated1.id) - expect(activityCreated8.conversationId).toStrictEqual(conversationCreated.id) - - // add a new activity to the thread - const activity9 = { - type: 'message', - timestamp: '2020-05-28T15:35:30Z', - platform: PlatformType.DISCORD, - body: 'additional message to the thread', - isContribution: true, - username: 'test2', - member: member2Created.id, - score: 2, - sourceId: 'sourceId#9', - sourceParentId: 'sourceId#5', - } - - const activityCreated9 = await activityService.upsert(activity9) - - expect(activityCreated9.parentId).toBe(activityCreated5.id) - expect(activityCreated9.conversationId).toStrictEqual(conversationCreated2.id) - }) - - // Tests for checking channel logic when creating activity - // Settings should get updated only when a new channel is sent alog while creating activity. - it('Should create an activity with a channel which is not present in settings and add it to settings', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test1', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - body: 'Body', - title: 'Title', - url: 'URL', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - channel: 'TestChannel', - attributes: { - replies: 12, - }, - sourceId: '#sourceId', - isContribution: true, - username: 'test1', - member: memberCreated.id, - score: 1, - } - - await new ActivityService(mockIRepositoryOptions).upsert(activity) - const segmentRepository = new SegmentRepository(mockIRepositoryOptions) - const subprojectIds = (await segmentRepository.querySubprojects({})).rows.map((s) => s.id) - const activityChannels = await segmentRepository.fetchTenantActivityChannels(subprojectIds) - expect(activityChannels[activity.platform].includes(activity.channel)).toBe(true) - }) - - it('Should not create a duplicate channel when a channel is present in settings', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test1', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - const activity = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - body: 'Body', - title: 'Title', - url: 'URL', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - channel: 'TestChannel', - attributes: { - replies: 12, - }, - sourceId: '#sourceId', - isContribution: true, - username: 'test1', - member: memberCreated.id, - score: 1, - } - - await new ActivityService(mockIRepositoryOptions).upsert(activity) - let settings = await SettingsRepository.findOrCreateDefault({}, mockIRepositoryOptions) - const activity1 = { - type: 'activity1', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - body: 'Body', - title: 'Title', - url: 'URL', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - label: 'positive', - sentiment: 0.98, - }, - channel: 'TestChannel', - attributes: { - replies: 12, - }, - sourceId: '#sourceId', - isContribution: true, - member: memberCreated.id, - score: 1, - } - - await new ActivityService(mockIRepositoryOptions).upsert(activity) - const segmentRepository = new SegmentRepository(mockIRepositoryOptions) - const subprojectIds = (await segmentRepository.querySubprojects({})).rows.map((s) => s.id) - const activityChannels = await segmentRepository.fetchTenantActivityChannels(subprojectIds) - expect(activityChannels[activity1.platform].length).toBe(1) - }) - }) - - describe('createWithMember method', () => { - it('Create an activity with given member [no parent activity]', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService( - mockIRepositoryOptions, - ) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - - const member = { - username: { - [PlatformType.GITHUB]: 'anil_github', - }, - email: 'lala@l.com', - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - [PlatformType.TWITTER]: 'https://twitter.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - organisation: 'Crowd', - joinedAt: '2020-05-27T15:13:30Z', - } - - const data = { - member, - body: 'Description\nThis pull request adds a new Dashboard and related widgets. This work will probably have to be revisited as soon as possible since a lot of decisions were made, without having too much time to think about different outcomes/possibilities. We rushed these changes so that we can demo a working dashboard to YC and to our Investors.\nChanges Proposed\n\nUpdate Chart.js\nAdd two different type of widgets (number and graph)\nRemove older/default widgets from dashboard and add our own widgets\nHide some items from the menu\nAdd all widget infrastructure (actions, services, etc) to integrate with the backend\nAdd a few more CSS tweaks\n\nScreenshots', - title: 'Dashboard widgets and some other tweaks/adjustments', - url: 'https://github.com/CrowdDevHQ/crowd-web/pull/16', - sentiment: { - positive: 0.98, - negative: 0.0, - neutral: 0.02, - mixed: 0.0, - sentiment: 0.98, - label: 'positive', - }, - channel: 'https://github.com/CrowdDevHQ/crowd-web', - timestamp: '2021-09-30T14:20:27.000Z', - type: 'pull_request-closed', - isContribution: true, - platform: PlatformType.GITHUB, - score: 4, - sourceId: '#sourceId1', - } - - const activityWithMember = await new ActivityService(mockIRepositoryOptions).createWithMember( - data, - ) - - delete activityWithMember.member - delete activityWithMember.display - delete activityWithMember.objectMember - - activityWithMember.createdAt = activityWithMember.createdAt.toISOString().split('T')[0] - activityWithMember.updatedAt = activityWithMember.updatedAt.toISOString().split('T')[0] - - const memberFound = await MemberRepository.findById( - activityWithMember.memberId, - mockIRepositoryOptions, - ) - - const expectedActivityCreated = { - id: activityWithMember.id, - type: data.type, - body: data.body, - title: data.title, - url: data.url, - channel: data.channel, - sentiment: data.sentiment, - attributes: {}, - timestamp: new Date(data.timestamp), - platform: data.platform, - isContribution: data.isContribution, - score: data.score, - username: 'anil_github', - objectMemberUsername: null, - memberId: memberFound.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - tasks: [], - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parentId: null, - parent: null, - sourceParentId: null, - sourceId: data.sourceId, - conversationId: null, - organizationId: null, - organization: null, - } - - expect(activityWithMember).toStrictEqual(expectedActivityCreated) - }) - - it('Create an activity with given member [with parent activity, upsert member, new activity] [parent first, child later]', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService( - mockIRepositoryOptions, - ) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - - const member = { - username: 'anil_github', - email: 'lala@l.com', - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - [PlatformType.TWITTER]: 'https://twitter.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - organisation: 'Crowd', - joinedAt: '2020-05-27T15:13:30Z', - } - - const data = { - member, - body: 'Description\nThis pull request adds a new Dashboard and related widgets. This work will probably have to be revisited as soon as possible since a lot of decisions were made, without having too much time to think about different outcomes/possibilities. We rushed these changes so that we can demo a working dashboard to YC and to our Investors.\nChanges Proposed\n\nUpdate Chart.js\nAdd two different type of widgets (number and graph)\nRemove older/default widgets from dashboard and add our own widgets\nHide some items from the menu\nAdd all widget infrastructure (actions, services, etc) to integrate with the backend\nAdd a few more CSS tweaks\n\nScreenshots', - title: 'Dashboard widgets and some other tweaks/adjustments', - url: 'https://github.com/CrowdDevHQ/crowd-web/pull/16', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - timestamp: '2021-09-30T14:20:27.000Z', - type: 'pull_request-closed', - isContribution: true, - platform: PlatformType.GITHUB, - score: 4, - sourceId: '#sourceId1', - } - - const activityWithMember1 = await new ActivityService( - mockIRepositoryOptions, - ).createWithMember(data) - - const data2 = { - member, - body: 'Description\nMinor pull request that fixes the order by Score and # of activities in the members list page', - title: 'Add order by score and # of activities', - url: 'https://github.com/CrowdDevHQ/crowd-web/pull/30', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - timestamp: '2021-11-30T14:20:27.000Z', - type: 'pull_request-open', - isContribution: true, - platform: PlatformType.GITHUB, - score: 4, - sourceId: '#sourceId2', - sourceParentId: data.sourceId, - } - - const activityWithMember2 = await new ActivityService( - mockIRepositoryOptions, - ).createWithMember(data2) - - // Since an activity with a parent is created, a Conversation entity should be created at this point - // with both parent and the child activities. Try finding it using the slug (slug is generated using parent.attributes.body) - - const conversationCreated = await new ConversationService( - mockIRepositoryOptions, - ).findAndCountAll({ slug: 'description-this-pull-request-adds-a-new-dashboard-and-related' }) - - // delete models before expect because we already have ids (memberId, parentId) - delete activityWithMember2.member - delete activityWithMember2.parent - delete activityWithMember2.display - delete activityWithMember2.objectMember - - activityWithMember2.createdAt = activityWithMember2.createdAt.toISOString().split('T')[0] - activityWithMember2.updatedAt = activityWithMember2.updatedAt.toISOString().split('T')[0] - - const memberFound = await MemberRepository.findById( - activityWithMember1.memberId, - mockIRepositoryOptions, - ) - - const expectedActivityCreated = { - id: activityWithMember2.id, - body: data2.body, - title: data2.title, - url: data2.url, - channel: data2.channel, - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.42, - }, - attributes: {}, - type: data2.type, - timestamp: new Date(data2.timestamp), - platform: data2.platform, - tasks: [], - isContribution: data2.isContribution, - score: data2.score, - username: 'anil_github', - objectMemberUsername: null, - memberId: memberFound.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parentId: activityWithMember1.id, - sourceParentId: data2.sourceParentId, - sourceId: data2.sourceId, - conversationId: conversationCreated.rows[0].id, - organizationId: null, - organization: null, - } - - expect(activityWithMember2).toStrictEqual(expectedActivityCreated) - }) - - it('Create an activity with given member [with parent activity, upsert member, new activity] [child first, parent later]', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const activityService = new ActivityService(mockIRepositoryOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService( - mockIRepositoryOptions, - ) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - - const member = { - username: 'anil_github', - email: 'lala@l.com', - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - [PlatformType.TWITTER]: 'https://twitter.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - organisation: 'Crowd', - joinedAt: '2020-05-27T15:13:30Z', - } - - const dataChild = { - member, - body: 'Description\nMinor pull request that fixes the order by Score and # of activities in the members list page', - title: 'Add order by score and # of activities', - url: 'https://github.com/CrowdDevHQ/crowd-web/pull/30', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - timestamp: '2021-11-30T14:20:27.000Z', - type: 'pull_request-open', - isContribution: true, - platform: PlatformType.GITHUB, - score: 4, - sourceParentId: '#sourceId1', - sourceId: '#childSourceId', - } - - let activityWithMemberChild = await activityService.createWithMember(dataChild) - - const dataParent = { - member, - body: 'Description\nThis pull request adds a new Dashboard and related widgets. This work will probably have to be revisited as soon as possible since a lot of decisions were made, without having too much time to think about different outcomes/possibilities. We rushed these changes so that we can demo a working dashboard to YC and to our Investors.\nChanges Proposed\n\nUpdate Chart.js\nAdd two different type of widgets (number and graph)\nRemove older/default widgets from dashboard and add our own widgets\nHide some items from the menu\nAdd all widget infrastructure (actions, services, etc) to integrate with the backend\nAdd a few more CSS tweaks\n\nScreenshots', - title: 'Dashboard widgets and some other tweaks/adjustments', - url: 'https://github.com/CrowdDevHQ/crowd-web/pull/16', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - timestamp: '2021-09-30T14:20:27.000Z', - type: 'pull_request-closed', - isContribution: true, - platform: PlatformType.GITHUB, - score: 4, - sourceId: dataChild.sourceParentId, - } - - let activityWithMemberParent = await activityService.createWithMember(dataParent) - - // after creating parent, conversation should be started - const conversationCreated = await new ConversationService( - mockIRepositoryOptions, - ).findAndCountAll({ slug: 'description-this-pull-request-adds-a-new-dashboard-and-related' }) - - // get child and parent activity again - activityWithMemberChild = await activityService.findById(activityWithMemberChild.id) - activityWithMemberParent = await activityService.findById(activityWithMemberParent.id) - - // delete models before expect because we already have ids (memberId, parentId) - delete activityWithMemberChild.member - delete activityWithMemberChild.parent - delete activityWithMemberChild.display - delete activityWithMemberChild.objectMember - delete activityWithMemberParent.member - delete activityWithMemberParent.parent - delete activityWithMemberParent.display - delete activityWithMemberParent.objectMember - - activityWithMemberChild.createdAt = activityWithMemberChild.createdAt - .toISOString() - .split('T')[0] - activityWithMemberChild.updatedAt = activityWithMemberChild.updatedAt - .toISOString() - .split('T')[0] - activityWithMemberParent.createdAt = activityWithMemberParent.createdAt - .toISOString() - .split('T')[0] - activityWithMemberParent.updatedAt = activityWithMemberParent.updatedAt - .toISOString() - .split('T')[0] - - const memberFound = await MemberRepository.findById( - activityWithMemberChild.memberId, - mockIRepositoryOptions, - ) - - const expectedParentActivityCreated = { - id: activityWithMemberParent.id, - body: dataParent.body, - title: dataParent.title, - url: dataParent.url, - channel: dataParent.channel, - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.42, - }, - attributes: {}, - type: dataParent.type, - timestamp: new Date(dataParent.timestamp), - platform: dataParent.platform, - isContribution: dataParent.isContribution, - tasks: [], - score: dataParent.score, - username: 'anil_github', - objectMemberUsername: null, - memberId: memberFound.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parentId: null, - sourceParentId: null, - sourceId: dataParent.sourceId, - conversationId: conversationCreated.rows[0].id, - organizationId: null, - organization: null, - } - - expect(activityWithMemberParent).toStrictEqual(expectedParentActivityCreated) - - const expectedChildActivityCreated = { - id: activityWithMemberChild.id, - body: dataChild.body, - title: dataChild.title, - url: dataChild.url, - channel: dataChild.channel, - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.42, - }, - attributes: {}, - type: dataChild.type, - timestamp: new Date(dataChild.timestamp), - platform: dataChild.platform, - isContribution: dataChild.isContribution, - score: dataChild.score, - username: 'anil_github', - objectMemberUsername: null, - memberId: memberFound.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - tasks: [], - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parentId: activityWithMemberParent.id, - sourceParentId: dataChild.sourceParentId, - sourceId: dataChild.sourceId, - conversationId: conversationCreated.rows[0].id, - organizationId: null, - organization: null, - } - - expect(activityWithMemberChild).toStrictEqual(expectedChildActivityCreated) - }) - - it(`Should respect the affiliation settings when setting an activity's organization with multiple member organizations`, async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const segmentRepo = new SegmentRepository(mockIRepositoryOptions) - - const segment1 = await segmentRepo.create({ - name: 'Crowd.dev - Segment1', - url: '', - parentName: 'Crowd.dev - Segment1', - grandparentName: 'Crowd.dev - Segment1', - slug: 'crowd.dev-1', - parentSlug: 'crowd.dev-1', - grandparentSlug: 'crowd.dev-1', - status: SegmentStatus.ACTIVE, - sourceId: null, - sourceParentId: null, - }) - - const segment2 = await segmentRepo.create({ - name: 'Crowd.dev - Segment2', - url: '', - parentName: 'Crowd.dev - Segment2', - grandparentName: 'Crowd.dev - Segment2', - slug: 'crowd.dev-2', - parentSlug: 'crowd.dev-2', - grandparentSlug: 'crowd.dev-2', - status: SegmentStatus.ACTIVE, - sourceId: null, - sourceParentId: null, - }) - - await populateSegments(mockIRepositoryOptions) - const org1 = await OrganizationRepository.create( - { - displayName: 'tesla', - }, - mockIRepositoryOptions, - ) - - const org2 = await OrganizationRepository.create( - { - displayName: 'crowd.dev', - }, - mockIRepositoryOptions, - ) - - const member = { - username: { - [PlatformType.GITHUB]: 'anil_github', - }, - organizations: [org1, org2], - affiliations: [ - { - segmentId: segment1.id, - organizationId: org2.id, - dateStart: '2021-09-01', - }, - { - segmentId: segment2.id, - organizationId: null, - dateStart: '2021-09-01', - }, - ], - } - - const data = { - member, - timestamp: '2021-09-30T14:20:27.000Z', - type: 'pull_request-closed', - platform: PlatformType.GITHUB, - sourceId: '#sourceId1', - } - - switchSegments(mockIRepositoryOptions, [segment1]) - - let activityWithMember = await new ActivityService(mockIRepositoryOptions).createWithMember( - data, - ) - - let activity = await ActivityRepository.findById( - activityWithMember.id, - mockIRepositoryOptions, - ) - - // org2 should be set as organization because it's in member's affiliated organizations - expect(activity.organization.name).toEqual(org2.name) - - // add another activity to segment2 for the same member - switchSegments(mockIRepositoryOptions, [segment2]) - - data.sourceId = '#sourceId2' - data.member = member // createWithMember modifies member, reset it - - activityWithMember = await new ActivityService(mockIRepositoryOptions).createWithMember(data) - - activity = await ActivityRepository.findById(activityWithMember.id, mockIRepositoryOptions) - - // this member had a null affiliation(meaning no organizations should be set) in segment 2 - expect(activity.organization).toBeNull() - }) - - describe('Member tests in createWithMember', () => { - it('Should set the joinedAt to the time of the activity when the member does not exist', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService( - mockIRepositoryOptions, - ) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - - const member = { - username: { - [PlatformType.GITHUB]: 'anil_github', - }, - email: 'lala@l.com', - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - [PlatformType.TWITTER]: 'https://twitter.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - organisation: 'Crowd', - joinedAt: '2020-05-27T15:13:30Z', - } - - const data = { - member, - body: 'Description\nThis pull request adds a new Dashboard and related widgets. This work will probably have to be revisited as soon as possible since a lot of decisions were made, without having too much time to think about different outcomes/possibilities. We rushed these changes so that we can demo a working dashboard to YC and to our Investors.\nChanges Proposed\n\nUpdate Chart.js\nAdd two different type of widgets (number and graph)\nRemove older/default widgets from dashboard and add our own widgets\nHide some items from the menu\nAdd all widget infrastructure (actions, services, etc) to integrate with the backend\nAdd a few more CSS tweaks\n\nScreenshots', - title: 'Dashboard widgets and some other tweaks/adjustments', - url: 'https://github.com/CrowdDevHQ/crowd-web/pull/16', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - timestamp: '2021-09-30T14:20:27.000Z', - type: 'pull_request-closed', - isContribution: true, - platform: PlatformType.GITHUB, - score: 4, - sourceId: '#sourceId1', - } - - const activityWithMember = await new ActivityService( - mockIRepositoryOptions, - ).createWithMember(data) - - delete activityWithMember.member - delete activityWithMember.display - delete activityWithMember.objectMember - - activityWithMember.createdAt = activityWithMember.createdAt.toISOString().split('T')[0] - activityWithMember.updatedAt = activityWithMember.updatedAt.toISOString().split('T')[0] - - const memberFound = await MemberRepository.findById( - activityWithMember.memberId, - mockIRepositoryOptions, - ) - - const expectedActivityCreated = { - id: activityWithMember.id, - body: data.body, - title: data.title, - url: data.url, - channel: data.channel, - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.42, - }, - attributes: {}, - type: data.type, - timestamp: new Date(data.timestamp), - platform: data.platform, - isContribution: data.isContribution, - score: data.score, - username: 'anil_github', - objectMemberUsername: null, - memberId: memberFound.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - tasks: [], - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parentId: null, - parent: null, - sourceParentId: null, - sourceId: data.sourceId, - conversationId: null, - organizationId: null, - organization: null, - } - - expect(activityWithMember).toStrictEqual(expectedActivityCreated) - expect(memberFound.joinedAt).toStrictEqual(expectedActivityCreated.timestamp) - expect(memberFound.username).toStrictEqual({ - [PlatformType.GITHUB]: ['anil_github'], - }) - }) - - it('Should replace joinedAt when activity ts is earlier than existing joinedAt', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService( - mockIRepositoryOptions, - ) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - - const member = { - username: { - [PlatformType.GITHUB]: 'anil_github', - }, - displayName: 'Anil', - email: 'lala@l.com', - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - [PlatformType.TWITTER]: 'https://twitter.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - organisation: 'Crowd', - joinedAt: '2022-05-27T15:13:30Z', - } - - await MemberRepository.create(member, mockIRepositoryOptions) - - const data = { - member, - body: 'Description\nThis pull request adds a new Dashboard and related widgets. This work will probably have to be revisited as soon as possible since a lot of decisions were made, without having too much time to think about different outcomes/possibilities. We rushed these changes so that we can demo a working dashboard to YC and to our Investors.\nChanges Proposed\n\nUpdate Chart.js\nAdd two different type of widgets (number and graph)\nRemove older/default widgets from dashboard and add our own widgets\nHide some items from the menu\nAdd all widget infrastructure (actions, services, etc) to integrate with the backend\nAdd a few more CSS tweaks\n\nScreenshots', - title: 'Dashboard widgets and some other tweaks/adjustments', - url: 'https://github.com/CrowdDevHQ/crowd-web/pull/16', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - timestamp: '2021-09-30T14:20:27.000Z', - type: 'pull_request-closed', - isContribution: true, - platform: PlatformType.GITHUB, - score: 4, - sourceId: '#sourceId1', - } - - const activityWithMember = await new ActivityService( - mockIRepositoryOptions, - ).createWithMember(data) - - delete activityWithMember.member - delete activityWithMember.display - delete activityWithMember.objectMember - - activityWithMember.createdAt = activityWithMember.createdAt.toISOString().split('T')[0] - activityWithMember.updatedAt = activityWithMember.updatedAt.toISOString().split('T')[0] - - const memberFound = await MemberRepository.findById( - activityWithMember.memberId, - mockIRepositoryOptions, - ) - - const expectedActivityCreated = { - id: activityWithMember.id, - body: data.body, - title: data.title, - url: data.url, - channel: data.channel, - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - sentiment: 0.42, - mixed: 0.42, - label: 'positive', - }, - attributes: {}, - type: data.type, - timestamp: new Date(data.timestamp), - platform: data.platform, - isContribution: data.isContribution, - score: data.score, - username: 'anil_github', - objectMemberUsername: null, - memberId: memberFound.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tasks: [], - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parentId: null, - parent: null, - sourceId: data.sourceId, - sourceParentId: null, - conversationId: null, - organizationId: null, - organization: null, - } - - expect(activityWithMember).toStrictEqual(expectedActivityCreated) - expect(memberFound.joinedAt).toStrictEqual(expectedActivityCreated.timestamp) - expect(memberFound.username).toStrictEqual({ - [PlatformType.GITHUB]: ['anil_github'], - }) - }) - - it('Should not replace joinedAt when activity ts is later than existing joinedAt', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService( - mockIRepositoryOptions, - ) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - - const member = { - username: { - [PlatformType.GITHUB]: 'anil_github', - }, - displayName: 'Anil', - email: 'lala@l.com', - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - [PlatformType.TWITTER]: 'https://twitter.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - organisation: 'Crowd', - joinedAt: '2020-05-27T15:13:30Z', - } - - await MemberRepository.create(member, mockIRepositoryOptions) - - const data = { - member, - body: 'Description\nThis pull request adds a new Dashboard and related widgets. This work will probably have to be revisited as soon as possible since a lot of decisions were made, without having too much time to think about different outcomes/possibilities. We rushed these changes so that we can demo a working dashboard to YC and to our Investors.\nChanges Proposed\n\nUpdate Chart.js\nAdd two different type of widgets (number and graph)\nRemove older/default widgets from dashboard and add our own widgets\nHide some items from the menu\nAdd all widget infrastructure (actions, services, etc) to integrate with the backend\nAdd a few more CSS tweaks\n\nScreenshots', - title: 'Dashboard widgets and some other tweaks/adjustments', - url: 'https://github.com/CrowdDevHQ/crowd-web/pull/16', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - timestamp: '2021-09-30T14:20:27.000Z', - type: 'pull_request-closed', - isContribution: true, - platform: PlatformType.GITHUB, - score: 4, - sourceId: '#sourceId1', - } - - const activityWithMember = await new ActivityService( - mockIRepositoryOptions, - ).createWithMember(data) - - delete activityWithMember.member - delete activityWithMember.display - delete activityWithMember.objectMember - - activityWithMember.createdAt = activityWithMember.createdAt.toISOString().split('T')[0] - activityWithMember.updatedAt = activityWithMember.updatedAt.toISOString().split('T')[0] - - const memberFound = await MemberRepository.findById( - activityWithMember.memberId, - mockIRepositoryOptions, - ) - - const expectedActivityCreated = { - id: activityWithMember.id, - body: data.body, - title: data.title, - url: data.url, - channel: data.channel, - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.42, - }, - attributes: {}, - type: data.type, - timestamp: new Date(data.timestamp), - platform: data.platform, - isContribution: data.isContribution, - score: data.score, - username: 'anil_github', - objectMemberUsername: null, - memberId: memberFound.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - tasks: [], - deletedAt: null, - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parentId: null, - parent: null, - sourceId: data.sourceId, - sourceParentId: null, - conversationId: null, - organizationId: null, - organization: null, - } - - expect(activityWithMember).toStrictEqual(expectedActivityCreated) - expect(memberFound.joinedAt).toStrictEqual(new Date('2020-05-27T15:13:30Z')) - expect(memberFound.username).toStrictEqual({ - [PlatformType.GITHUB]: ['anil_github'], - }) - }) - - it('It should replace joinedAt if the original was in year 1970', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - await populateSegments(mockIRepositoryOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService( - mockIRepositoryOptions, - ) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - - const member = { - username: { - [PlatformType.GITHUB]: 'anil_github', - }, - displayName: 'Anil', - email: 'lala@l.com', - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - [PlatformType.TWITTER]: 'https://twitter.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Computer Science', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Istanbul', - }, - }, - organisation: 'Crowd', - joinedAt: new Date('1970-01-01T00:00:00Z'), - } - - await MemberRepository.create(member, mockIRepositoryOptions) - - const data = { - member, - body: 'Description\nThis pull request adds a new Dashboard and related widgets. This work will probably have to be revisited as soon as possible since a lot of decisions were made, without having too much time to think about different outcomes/possibilities. We rushed these changes so that we can demo a working dashboard to YC and to our Investors.\nChanges Proposed\n\nUpdate Chart.js\nAdd two different type of widgets (number and graph)\nRemove older/default widgets from dashboard and add our own widgets\nHide some items from the menu\nAdd all widget infrastructure (actions, services, etc) to integrate with the backend\nAdd a few more CSS tweaks\n\nScreenshots', - title: 'Dashboard widgets and some other tweaks/adjustments', - state: 'merged', - url: 'https://github.com/CrowdDevHQ/crowd-web/pull/16', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - timestamp: '2021-09-30T14:20:27.000Z', - type: 'pull_request-closed', - isContribution: true, - platform: PlatformType.GITHUB, - score: 4, - sourceId: '#sourceId1', - } - - const activityWithMember = await new ActivityService( - mockIRepositoryOptions, - ).createWithMember(data) - - delete activityWithMember.member - delete activityWithMember.display - delete activityWithMember.objectMember - - activityWithMember.createdAt = activityWithMember.createdAt.toISOString().split('T')[0] - activityWithMember.updatedAt = activityWithMember.updatedAt.toISOString().split('T')[0] - - const memberFound = await MemberRepository.findById( - activityWithMember.memberId, - mockIRepositoryOptions, - ) - - const expectedActivityCreated = { - id: activityWithMember.id, - body: data.body, - title: data.title, - url: data.url, - channel: data.channel, - sentiment: { - positive: 0.42, - negative: 0.42, - neutral: 0.42, - mixed: 0.42, - label: 'positive', - sentiment: 0.42, - }, - attributes: {}, - type: data.type, - timestamp: new Date(data.timestamp), - platform: data.platform, - isContribution: data.isContribution, - score: data.score, - username: 'anil_github', - objectMemberUsername: null, - memberId: memberFound.id, - objectMemberId: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tasks: [], - tenantId: mockIRepositoryOptions.currentTenant.id, - segmentId: mockIRepositoryOptions.currentSegments[0].id, - createdById: mockIRepositoryOptions.currentUser.id, - updatedById: mockIRepositoryOptions.currentUser.id, - importHash: null, - parentId: null, - parent: null, - sourceId: data.sourceId, - sourceParentId: null, - conversationId: null, - organizationId: null, - organization: null, - } - - expect(activityWithMember).toStrictEqual(expectedActivityCreated) - expect(memberFound.joinedAt).toStrictEqual(expectedActivityCreated.timestamp) - expect(memberFound.username).toStrictEqual({ - [PlatformType.GITHUB]: ['anil_github'], - }) - }) - - it('Should respect joinedAt when an existing activity comes in with a different timestamp', async () => { - // This can happen in cases like the Twitter integration. - // For follow activities, if we are onboarding we set the timestamp to 1970, - // but if we are not onboarding, we set the timestamp to the current time. - // This can cause having 2 activities with different timestamps, but the same sourceId. - // The joinedAt should stay untouched in this case. - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(mockIRepositoryOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService( - mockIRepositoryOptions, - ) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - - const data = { - member: { - username: 'anil', - }, - timestamp: '1970-01-01T00:00:00.000Z', - type: 'follow', - platform: PlatformType.TWITTER, - sourceId: '#sourceId1', - } - - const activityWithMember = await new ActivityService( - mockIRepositoryOptions, - ).createWithMember(data) - - const data2 = { - member: { - username: 'anil', - }, - timestamp: '2021-09-30T14:20:27.000Z', - type: 'follow', - platform: PlatformType.TWITTER, - sourceId: '#sourceId1', - } - // Upsert the same activity with a different timestamp - await new ActivityService(mockIRepositoryOptions).createWithMember(data2) - - const memberFound = await MemberRepository.findById( - activityWithMember.memberId, - mockIRepositoryOptions, - ) - // The joinedAt should stay untouched - expect(memberFound.joinedAt).toStrictEqual(new Date('1970-01-01T00:00:00.000Z')) - }) - }) - }) - - describe('addToConversation method', () => { - it('Should create a new conversation and add the activities in, when parent and child has no conversation', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const activityService = new ActivityService(mockIRepositoryOptions) - - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - const activityParent = { - type: 'activity', - timestamp: '2020-05-27T14:13:30Z', - platform: PlatformType.GITHUB, - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - } - - let activityParentCreated = await ActivityRepository.create( - activityParent, - mockIRepositoryOptions, - ) - - const activityChild = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - parent: activityParentCreated.id, - sourceId: '#sourceId2', - } - - let activityChildCreated = await ActivityRepository.create( - activityChild, - mockIRepositoryOptions, - ) - - const transaction = await SequelizeRepository.createTransaction(mockIRepositoryOptions) - - await activityService.addToConversation( - activityChildCreated.id, - activityParentCreated.id, - transaction, - ) - - await SequelizeRepository.commitTransaction(transaction) - - const conversationCreated = ( - await new ConversationService(mockIRepositoryOptions).findAndCountAll({ - slug: 'some-parent-activity', - }) - ).rows[0] - - // get activities again - activityChildCreated = await activityService.findById(activityChildCreated.id) - activityParentCreated = await activityService.findById(activityParentCreated.id) - - // activities should belong to the newly created conversation - expect(activityChildCreated.conversationId).toBe(conversationCreated.id) - expect(activityParentCreated.conversationId).toBe(conversationCreated.id) - }) - - it('Should add the child activity to parents conversation, when parent already has a conversation', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const activityService = new ActivityService(mockIRepositoryOptions) - const conversationService = new ConversationService(mockIRepositoryOptions) - - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - const conversation = await conversationService.create({ - slug: 'some-slug', - title: 'some title', - }) - - const activityParent = { - type: 'activity', - timestamp: '2020-05-27T14:13:30Z', - platform: PlatformType.GITHUB, - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - conversationId: conversation.id, - sourceId: '#sourceId1', - } - - const activityParentCreated = await ActivityRepository.create( - activityParent, - mockIRepositoryOptions, - ) - - const activityChild = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - parent: activityParentCreated.id, - sourceId: '#sourceId2', - } - - let activityChildCreated = await ActivityRepository.create( - activityChild, - mockIRepositoryOptions, - ) - - const transaction = await SequelizeRepository.createTransaction(mockIRepositoryOptions) - - await activityService.addToConversation( - activityChildCreated.id, - activityParentCreated.id, - transaction, - ) - - await SequelizeRepository.commitTransaction(transaction) - - // get child activity again - activityChildCreated = await activityService.findById(activityChildCreated.id) - - // child should be added to already existing conservation - expect(activityChildCreated.conversationId).toBe(conversation.id) - expect(activityParentCreated.conversationId).toBe(conversation.id) - }) - - it('Should add the parent activity to childs conversation and update conversation [published=false] title&slug, when child already has a conversation', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const activityService = new ActivityService(mockIRepositoryOptions) - const conversationService = new ConversationService(mockIRepositoryOptions) - - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - let conversation = await conversationService.create({ - slug: 'some-slug', - title: 'some title', - }) - - const activityParent = { - type: 'activity', - timestamp: '2020-05-27T14:13:30Z', - platform: PlatformType.GITHUB, - channel: 'https://github.com/CrowdDevHQ/crowd-web', - body: 'Some Parent Activity', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - } - - let activityParentCreated = await ActivityRepository.create( - activityParent, - mockIRepositoryOptions, - ) - - const activityChild = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - parent: activityParentCreated.id, - conversationId: conversation.id, - sourceId: '#sourceId2', - } - - const activityChildCreated = await ActivityRepository.create( - activityChild, - mockIRepositoryOptions, - ) - - const transaction = await SequelizeRepository.createTransaction(mockIRepositoryOptions) - - await activityService.addToConversation( - activityChildCreated.id, - activityParentCreated.id, - transaction, - ) - - await SequelizeRepository.commitTransaction(transaction) - - // get the conversation again - conversation = await conversationService.findById(conversation.id) - - // conversation should be updated with newly added parents body - expect(conversation.title).toBe('Some Parent Activity') - expect(conversation.slug).toBe('some-parent-activity') - - // get parent activity again - activityParentCreated = await activityService.findById(activityParentCreated.id) - - // parent should be added to the conversation - expect(activityChildCreated.conversationId).toBe(conversation.id) - expect(activityParentCreated.conversationId).toBe(conversation.id) - }) - - it('Should add the parent activity to childs conversation and NOT update conversation [published=true] title&slug, when child already has a conversation', async () => { - let mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const activityService = new ActivityService(mockIRepositoryOptions) - const conversationService = new ConversationService(mockIRepositoryOptions) - - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - let conversation = await conversationService.create({ - slug: 'some-slug', - title: 'some title', - published: true, - }) - - const activityParent = { - type: 'activity', - timestamp: '2020-05-27T14:13:30Z', - platform: PlatformType.GITHUB, - body: 'Some Parent Activity', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - } - - let activityParentCreated = await ActivityRepository.create( - activityParent, - mockIRepositoryOptions, - ) - - const activityChild = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - body: 'Here', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - parent: activityParentCreated.id, - conversationId: conversation.id, - sourceId: '#sourceId2', - } - - const activityChildCreated = await ActivityRepository.create( - activityChild, - mockIRepositoryOptions, - ) - - const transaction = await SequelizeRepository.createTransaction(mockIRepositoryOptions) - - await activityService.addToConversation( - activityChildCreated.id, - activityParentCreated.id, - transaction, - ) - - await SequelizeRepository.commitTransaction(transaction) - - // get the conversation again - conversation = await conversationService.findById(conversation.id) - - // conversation fields should NOT be updated because it's already published - expect(conversation.title).toBe('some title') - expect(conversation.slug).toBe('some-slug') - - // get parent activity again - activityParentCreated = await activityService.findById(activityParentCreated.id) - - // parent should be added to the conversation - expect(activityChildCreated.conversationId).toBe(conversation.id) - expect(activityParentCreated.conversationId).toBe(conversation.id) - }) - - it('Should always auto-publish when conversationSettings.autoPublish.status is set to all', async () => { - let mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const activityService = new ActivityService(mockIRepositoryOptions) - await SettingsRepository.findOrCreateDefault( - { website: 'https://some-website' }, - mockIRepositoryOptions, - ) - await ConversationSettingsRepository.findOrCreateDefault( - { - autoPublish: { - status: 'all', - }, - }, - mockIRepositoryOptions, - ) - - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - const activityParent = { - type: 'activity', - timestamp: '2020-05-27T14:13:30Z', - platform: PlatformType.GITHUB, - body: 'Some Parent Activity', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - } - - let activityParentCreated = await ActivityRepository.create( - activityParent, - mockIRepositoryOptions, - ) - - const activityChild = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - body: 'Here', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - parent: activityParentCreated.id, - sourceId: '#sourceId2', - } - - let activityChildCreated = await ActivityRepository.create( - activityChild, - mockIRepositoryOptions, - ) - - const transaction = await SequelizeRepository.createTransaction(mockIRepositoryOptions) - - await activityService.addToConversation( - activityChildCreated.id, - activityParentCreated.id, - transaction, - ) - - const conversationCreated = ( - await new ConversationService({ - ...mockIRepositoryOptions, - transaction, - } as IServiceOptions).findAndCountAll({ - slug: 'some-parent-activity', - }) - ).rows[0] - - await SequelizeRepository.commitTransaction(transaction) - - // get activities again - activityChildCreated = await activityService.findById(activityChildCreated.id) - activityParentCreated = await activityService.findById(activityParentCreated.id) - - // activities should belong to the newly created conversation - expect(activityChildCreated.conversationId).toBe(conversationCreated.id) - expect(activityParentCreated.conversationId).toBe(conversationCreated.id) - - expect(conversationCreated.published).toStrictEqual(true) - }) - - it('Should never auto-publish when conversationSettings.autoPublish.status is set to disabled', async () => { - let mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const activityService = new ActivityService(mockIRepositoryOptions) - await SettingsRepository.findOrCreateDefault( - { website: 'https://some-website' }, - mockIRepositoryOptions, - ) - await ConversationSettingsRepository.findOrCreateDefault( - { - autoPublish: { - status: 'disabled', - }, - }, - mockIRepositoryOptions, - ) - - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - const activityParent = { - type: 'activity', - timestamp: '2020-05-27T14:13:30Z', - platform: PlatformType.GITHUB, - body: 'Some Parent Activity', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - } - - let activityParentCreated = await ActivityRepository.create( - activityParent, - mockIRepositoryOptions, - ) - - const activityChild = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - body: 'Here', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - parent: activityParentCreated.id, - sourceId: '#sourceId2', - } - - let activityChildCreated = await ActivityRepository.create( - activityChild, - mockIRepositoryOptions, - ) - - const transaction = await SequelizeRepository.createTransaction(mockIRepositoryOptions) - - await activityService.addToConversation( - activityChildCreated.id, - activityParentCreated.id, - transaction, - ) - - const conversationCreated = ( - await new ConversationService({ - ...mockIRepositoryOptions, - transaction, - } as IServiceOptions).findAndCountAll({ - slug: 'some-parent-activity', - }) - ).rows[0] - - await SequelizeRepository.commitTransaction(transaction) - - // get activities again - activityChildCreated = await activityService.findById(activityChildCreated.id) - activityParentCreated = await activityService.findById(activityParentCreated.id) - - // activities should belong to the newly created conversation - expect(activityChildCreated.conversationId).toBe(conversationCreated.id) - expect(activityParentCreated.conversationId).toBe(conversationCreated.id) - - expect(conversationCreated.published).toStrictEqual(false) - }) - - it('Should auto-publish when conversationSettings.autoPublish.status is set to custom and rules match', async () => { - let mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const activityService = new ActivityService(mockIRepositoryOptions) - await SettingsRepository.findOrCreateDefault( - { website: 'https://some-website' }, - mockIRepositoryOptions, - ) - await ConversationSettingsRepository.findOrCreateDefault( - { - autoPublish: { - status: 'custom', - channelsByPlatform: { - [PlatformType.GITHUB]: ['crowd-web'], - }, - }, - }, - mockIRepositoryOptions, - ) - - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - const activityParent = { - type: 'activity', - timestamp: '2020-05-27T14:13:30Z', - platform: PlatformType.GITHUB, - body: 'Some Parent Activity', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - } - - let activityParentCreated = await ActivityRepository.create( - activityParent, - mockIRepositoryOptions, - ) - - const activityChild = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - body: 'Here', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - parent: activityParentCreated.id, - sourceId: '#sourceId2', - } - - let activityChildCreated = await ActivityRepository.create( - activityChild, - mockIRepositoryOptions, - ) - - const transaction = await SequelizeRepository.createTransaction(mockIRepositoryOptions) - - await activityService.addToConversation( - activityChildCreated.id, - activityParentCreated.id, - transaction, - ) - - const conversationCreated = ( - await new ConversationService({ - ...mockIRepositoryOptions, - transaction, - } as IServiceOptions).findAndCountAll({ - slug: 'some-parent-activity', - }) - ).rows[0] - - await SequelizeRepository.commitTransaction(transaction) - - // get activities again - activityChildCreated = await activityService.findById(activityChildCreated.id) - activityParentCreated = await activityService.findById(activityParentCreated.id) - - // activities should belong to the newly created conversation - expect(activityChildCreated.conversationId).toBe(conversationCreated.id) - expect(activityParentCreated.conversationId).toBe(conversationCreated.id) - - expect(conversationCreated.published).toStrictEqual(true) - }) - - it("Should not auto-publish when conversationSettings.autoPublish.status is set to custom and rules don't match", async () => { - let mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - - const activityService = new ActivityService(mockIRepositoryOptions) - await SettingsRepository.findOrCreateDefault( - { website: 'https://some-website' }, - mockIRepositoryOptions, - ) - await ConversationSettingsRepository.findOrCreateDefault( - { - autoPublish: { - status: 'custom', - channelsByPlatform: { - [PlatformType.GITHUB]: ['a-different-test-channel'], - }, - }, - }, - mockIRepositoryOptions, - ) - - const memberCreated = await new MemberService(mockIRepositoryOptions).upsert({ - username: { - [PlatformType.GITHUB]: 'test', - }, - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - }) - - const activityParent = { - type: 'activity', - timestamp: '2020-05-27T14:13:30Z', - platform: PlatformType.GITHUB, - body: 'Some Parent Activity', - channel: 'https://github.com/CrowdDevHQ/crowd-web', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - sourceId: '#sourceId1', - } - - let activityParentCreated = await ActivityRepository.create( - activityParent, - mockIRepositoryOptions, - ) - - const activityChild = { - type: 'activity', - timestamp: '2020-05-27T15:13:30Z', - platform: PlatformType.GITHUB, - body: 'Here', - isContribution: true, - username: 'test', - member: memberCreated.id, - score: 1, - parent: activityParentCreated.id, - sourceId: '#sourceId2', - } - - let activityChildCreated = await ActivityRepository.create( - activityChild, - mockIRepositoryOptions, - ) - - const transaction = await SequelizeRepository.createTransaction(mockIRepositoryOptions) - - await activityService.addToConversation( - activityChildCreated.id, - activityParentCreated.id, - transaction, - ) - - const conversations = await new ConversationService({ - ...mockIRepositoryOptions, - transaction, - } as IServiceOptions).findAndCountAll({ - slug: 'some-parent-activity', - }) - - const conversationCreated = conversations.rows[0] - - await SequelizeRepository.commitTransaction(transaction) - - // get activities again - activityChildCreated = await activityService.findById(activityChildCreated.id) - activityParentCreated = await activityService.findById(activityParentCreated.id) - - // activities should belong to the newly created conversation - expect(activityChildCreated.conversationId).toBe(conversationCreated.id) - expect(activityParentCreated.conversationId).toBe(conversationCreated.id) - - expect(conversationCreated.published).toStrictEqual(false) - }) - }) - - describe('affiliations', () => { - let options - let activityService: ActivityService - let memberService - let organizationService - let segmentService: SegmentService - let memberAffiliationService: MemberAffiliationService - let memberSegmentAffiliationRepository: MemberSegmentAffiliationRepository - - let defaultActivity - let defaultMember - - beforeEach(async () => { - options = await SequelizeTestUtils.getTestIRepositoryOptions(db) - await populateSegments(options) - - activityService = new ActivityService(options) - memberService = new MemberService(options) - organizationService = new OrganizationService(options) - segmentService = new SegmentService(options) - memberAffiliationService = new MemberAffiliationService(options) - memberSegmentAffiliationRepository = new MemberSegmentAffiliationRepository(options) - - defaultActivity = { - type: 'question', - timestamp: '2020-05-27T15:13:30Z', - username: 'test', - platform: PlatformType.GITHUB, - } - defaultMember = { - platform: PlatformType.GITHUB, - joinedAt: '2020-05-27T15:13:30Z', - } - }) - - async function createMember(data = {}) { - return await memberService.upsert({ - ...defaultMember, - username: { - [PlatformType.GITHUB]: uuid(), - }, - ...data, - }) - } - - async function createActivity(memberId, data: any = {}) { - const activity = await activityService.upsert({ - ...defaultActivity, - sourceId: uuid(), - member: memberId, - ...data, - }) - - if (data.organizationId) { - await ActivityRepository.update( - activity.id, - { - organizationId: data.organizationId, - }, - options, - ) - return await activityService.findById(activity.id) - } - - return activity - } - - async function findActivity(id) { - return await activityService.findById(id) - } - - async function createOrg(name, data = {}) { - return await organizationService.createOrUpdate({ - identities: [ - { - name, - platform: 'crowd', - }, - ], - ...data, - }) - } - - async function createSegment(slug, data = {}) { - const db1 = await SequelizeTestUtils.getDatabase(db) - const tenant = options.currentTenant - try { - const segment = ( - await db1.segment.create({ - url: tenant.url, - name: slug, - parentName: tenant.name, - grandparentName: tenant.name, - slug: slug, - parentSlug: 'default', - grandparentSlug: 'default', - status: SegmentStatus.ACTIVE, - description: null, - sourceId: null, - sourceParentId: null, - tenantId: tenant.id, - }) - ).get({ plain: true }) - return segment - } catch (error) { - console.log(error) - throw error - } - } - - async function addWorkExperience(memberId, orgId, data = {}) { - return await MemberRepository.createOrUpdateWorkExperience( - { - memberId, - organizationId: orgId, - updateAffiliation: false, - source: 'test', - ...data, - }, - options, - ) - } - - describe('new activities', () => { - it('Should affiliate nothing if member has no organizations and no affiliations', async () => { - const member = await createMember() - const activity = await createActivity(member.id) - - expect(activity.organization).toBeNull() - }) - - it('Should affiliate work experience if member has organizations', async () => { - const member = await createMember() - const organization = await createOrg('hello') - await addWorkExperience(member.id, organization.id, { - dateStart: '2020-01-01', - }) - const activity = await createActivity(member.id, { - timestamp: '2023-01-01', - }) - - expect(activity.organization.id).toBe(organization.id) - }) - - it('Should affiliate with matching work experience if activity is from the past', async () => { - const member = await createMember() - const org1 = await createOrg('org1') - const org2 = await createOrg('org2') - - await addWorkExperience(member.id, org1.id, { - dateStart: '2020-01-01', - dateEnd: '2020-02-01', - }) - await addWorkExperience(member.id, org2.id, { - dateStart: '2020-02-01', - }) - - const activity = await createActivity(member.id, { - timestamp: '2020-01-15', - }) - - expect(activity.organization.id).toBe(org1.id) - }) - - it('Should affiliate with most recent open work experience if member has multiple organizations', async () => { - const member = await createMember() - const org1 = await createOrg('org1') - const org2 = await createOrg('org2') - - await addWorkExperience(member.id, org1.id, { - dateStart: '2020-01-01', - }) - await addWorkExperience(member.id, org2.id, { - dateStart: '2020-02-01', - }) - - const activity = await createActivity(member.id, { - timestamp: '2020-03-01', - }) - - expect(activity.organization.id).toBe(org2.id) - }) - - it('Should affiliate with most recent open work experience, even if there is a more recent closed one', async () => { - const member = await createMember() - const org1 = await createOrg('org1') - const org2 = await createOrg('org2') - - await addWorkExperience(member.id, org1.id, { - dateStart: '2020-01-01', - }) - await addWorkExperience(member.id, org2.id, { - dateStart: '2020-02-01', - dateEnd: '2020-03-01', - }) - - const activity = await createActivity(member.id) - - expect(activity.organization.id).toBe(org1.id) - }) - - it('Should affiliate with manual affiliation if member has organizations and affiliations', async () => { - const member = await createMember() - const org1 = await createOrg('org1') - const org2 = await createOrg('org2') - - await addWorkExperience(member.id, org1.id, { - dateStart: '2020-01-01', - }) - - await memberSegmentAffiliationRepository.createOrUpdate({ - memberId: member.id, - segmentId: options.currentSegments[0].id, - organizationId: org2.id, - dateStart: '2020-02-01', - }) - - const activity = await createActivity(member.id) - - expect(activity.organization.id).toBe(org2.id) - }) - - it('Should affiliate to invidiual if member has organizations and affiliations', async () => { - const member = await createMember() - const org1 = await createOrg('org1') - - await addWorkExperience(member.id, org1.id, { - dateStart: '2020-01-01', - }) - - await memberSegmentAffiliationRepository.createOrUpdate({ - memberId: member.id, - segmentId: options.currentSegments[0].id, - organizationId: null, - dateStart: '2020-02-01', - }) - - const activity = await createActivity(member.id) - - expect(activity.organization).toBeNull() - }) - - it('Should not affiliate if there are no relevant manual affiliations', async () => { - const member = await createMember() - const org1 = await createOrg('org1') - const segment1 = await createSegment('segment1') - - await memberSegmentAffiliationRepository.createOrUpdate({ - memberId: member.id, - segmentId: segment1.id, - organizationId: org1.id, - }) - - const activity = await createActivity(member.id) - - expect(activity.organization).toBeNull() - }) - }) - - describe('existing activities', () => { - it('Should clear affiliation if there is a manual individual affiliation', async () => { - const member = await createMember() - const org1 = await createOrg('org1') - const segment1 = await createSegment('segment1') - - await memberSegmentAffiliationRepository.createOrUpdate({ - memberId: member.id, - segmentId: segment1.id, - organizationId: null, - }) - - let activity = await createActivity(member.id, { - organizationId: org1.id, - }) - - await memberAffiliationService.updateAffiliation(member.id) - - activity = await findActivity(activity.id) - - expect(activity.organization).toBeNull() - }) - - it('Should affiliate activities', async () => { - const member = await createMember() - - let activity1 = await createActivity(member.id) - let activity2 = await createActivity(member.id) - - const org = await createOrg('org') - await addWorkExperience(member.id, org.id, { - dateStart: '2020-01-01', - }) - - await memberAffiliationService.updateAffiliation(member.id) - - activity1 = await findActivity(activity1.id) - activity2 = await findActivity(activity2.id) - - expect(activity1.organization.id).toBe(org.id) - expect(activity2.organization.id).toBe(org.id) - }) - - it('Should only affiliate activities of specific member', async () => { - const member1 = await createMember() - const member2 = await createMember() - - let activity1 = await createActivity(member1.id) - let activity2 = await createActivity(member2.id) - - const org1 = await createOrg('org1') - await addWorkExperience(member1.id, org1.id, { - dateStart: '2020-01-01', - }) - const org2 = await createOrg('org2') - await addWorkExperience(member2.id, org2.id, { - dateStart: '2020-01-01', - }) - - await memberAffiliationService.updateAffiliation(member1.id) - - activity2 = await findActivity(activity2.id) - - expect(activity2.organization).toBeNull() - }) - - it('Should affiliate with matching recent work experience', async () => { - const member = await createMember() - - let activity = await createActivity(member.id, { - timestamp: '2020-01-01', - }) - - const org1 = await createOrg('org1') - await addWorkExperience(member.id, org1.id) - const org2 = await createOrg('org2') - await addWorkExperience(member.id, org2.id, { - dateStart: '2023-01-01', - }) - const org3 = await createOrg('org2') - await addWorkExperience(member.id, org3.id, { - dateStart: '2019-01-01', - dateEnd: '2022-01-01', - }) - - await memberAffiliationService.updateAffiliation(member.id) - - activity = await findActivity(activity.id) - - expect(activity.organization.id).toBe(org3.id) - }) - - it('Should affiliate first created org to past activities', async () => { - const member = await createMember() - - let activity = await createActivity(member.id, { - timestamp: '2022-05-01', - }) - - const org1 = await createOrg('org1') - await addWorkExperience(member.id, org1.id) - const org2 = await createOrg('org2') - await addWorkExperience(member.id, org2.id, { - dateStart: '2023-01-01', - }) - const org3 = await createOrg('org2') - await addWorkExperience(member.id, org3.id, { - dateStart: '2019-01-01', - dateEnd: '2022-01-01', - }) - - await memberAffiliationService.updateAffiliation(member.id) - - activity = await findActivity(activity.id) - - expect(activity.organization.id).toBe(org1.id) - }) - - it('Should prefer manual affiliation over work experience', async () => { - const member = await createMember() - - let activity = await createActivity(member.id, { - timestamp: '2020-05-01', - }) - - const org1 = await createOrg('org1') - await addWorkExperience(member.id, org1.id, { - dateStart: '2020-01-01', - }) - - const org2 = await createOrg('org2') - await memberSegmentAffiliationRepository.createOrUpdate({ - memberId: member.id, - segmentId: options.currentSegments[0].id, - organizationId: org2.id, - dateStart: '2020-01-01', - }) - - await memberAffiliationService.updateAffiliation(member.id) - - activity = await findActivity(activity.id) - - expect(activity.organization.id).toBe(org2.id) - }) - - it('Should prefer manual individual affiliation over work experience', async () => { - const member = await createMember() - - let activity = await createActivity(member.id, { - timestamp: '2020-05-01', - }) - - const org1 = await createOrg('org1') - await addWorkExperience(member.id, org1.id, { - dateStart: '2020-01-01', - }) - - await memberSegmentAffiliationRepository.createOrUpdate({ - memberId: member.id, - segmentId: options.currentSegments[0].id, - organizationId: null, - dateStart: '2020-01-01', - }) - - await memberAffiliationService.updateAffiliation(member.id) - - activity = await findActivity(activity.id) - - expect(activity.organization).toBeNull() - }) - - it('Should trigger affiliation update when changing member organizations', async () => { - const member = await createMember() - - let activity = await createActivity(member.id, { - timestamp: '2020-05-01', - }) - - const org1 = await createOrg('org1') - await addWorkExperience(member.id, org1.id, { - dateStart: '2020-01-01', - updateAffiliation: true, - }) - - activity = await findActivity(activity.id) - - expect(activity.organization.id).toBe(org1.id) - }) - - it('Should trigger affiliation update when changing member manual affiliations', async () => { - const member = await createMember() - - let activity = await createActivity(member.id) - - const org1 = await createOrg('org1') - - await memberSegmentAffiliationRepository.createOrUpdate({ - memberId: member.id, - segmentId: options.currentSegments[0].id, - organizationId: org1.id, - }) - - activity = await findActivity(activity.id) - - expect(activity.organization.id).toBe(org1.id) - }) - }) - - it('Should filter by organization based on organizationId', async () => { - const member = await createMember() - - const org1 = await createOrg('org1') - await addWorkExperience(member.id, org1.id) - - const org2 = await createOrg('org2') - await addWorkExperience(member.id, org2.id) - - let activity1 = await createActivity(member.id, { - organizationId: org1.id, - }) - let activity2 = await createActivity(member.id, { - organizationId: org2.id, - }) - - const { rows } = await activityService.query({ - filter: { - organizations: [org1.id], - }, - }) - - expect(rows.length).toBe(1) - }) - }) -}) diff --git a/backend/src/services/__tests__/memberService.test.ts b/backend/src/services/__tests__/memberService.test.ts deleted file mode 100644 index 505c7d6db3..0000000000 --- a/backend/src/services/__tests__/memberService.test.ts +++ /dev/null @@ -1,2834 +0,0 @@ -import { generateUUIDv1, Error400, Error404 } from '@crowd/common' -import SequelizeTestUtils from '../../database/utils/sequelizeTestUtils' -import MemberService from '../memberService' -import MemberRepository from '../../database/repositories/memberRepository' -import ActivityRepository from '../../database/repositories/activityRepository' -import TagRepository from '../../database/repositories/tagRepository' -import { MemberAttributeName, MemberAttributeType, PlatformType } from '@crowd/types' -import OrganizationRepository from '../../database/repositories/organizationRepository' -import TaskRepository from '../../database/repositories/taskRepository' -import NoteRepository from '../../database/repositories/noteRepository' -import MemberAttributeSettingsService from '../memberAttributeSettingsService' -import SettingsRepository from '../../database/repositories/settingsRepository' -import OrganizationService from '../organizationService' -import Plans from '../../security/plans' -import lodash from 'lodash' -import { - DEVTO_MEMBER_ATTRIBUTES, - DISCORD_MEMBER_ATTRIBUTES, - GITHUB_MEMBER_ATTRIBUTES, - SLACK_MEMBER_ATTRIBUTES, - TWITTER_MEMBER_ATTRIBUTES, -} from '@crowd/integrations' - -const db = null - -describe('MemberService tests', () => { - beforeEach(async () => { - await SequelizeTestUtils.wipeDatabase(db) - }) - - afterAll(async () => { - // Closing the DB connection allows Jest to exit successfully. - await SequelizeTestUtils.closeConnection(db) - }) - - describe('upsert method', () => { - it('Should throw 400 error when platform does not exist in member data', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const mas = new MemberAttributeSettingsService(mockIServiceOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const member1 = { - username: { - [PlatformType.GITHUB]: { - username: 'anil', - integrationId: generateUUIDv1(), - }, - }, - emails: ['lala@l.com'], - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - joinedAt: '2020-05-28T15:13:30Z', - } - - await expect(() => - new MemberService(mockIServiceOptions).upsert(member1), - ).rejects.toThrowError(new Error400()) - }) - - it('Should create non existent member - attributes with matching platform', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const mas = new MemberAttributeSettingsService(mockIServiceOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await mas.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - await mas.createPredefined(DISCORD_MEMBER_ATTRIBUTES) - - const member1 = { - username: 'anil', - platform: PlatformType.GITHUB, - emails: ['lala@l.com'], - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - [PlatformType.TWITTER]: 'https://some-twitter-url', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - [MemberAttributeName.SOURCE_ID]: { - [PlatformType.TWITTER]: '#twitterId', - [PlatformType.DISCORD]: '#discordId', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://some-image-url', - }, - }, - joinedAt: '2020-05-28T15:13:30Z', - } - - // Save some attributes since they get modified in the upsert function - const { platform, username, attributes } = member1 - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - username: { - [platform]: [username], - }, - displayName: username, - attributes: { - [MemberAttributeName.SOURCE_ID]: { - [PlatformType.DISCORD]: attributes[MemberAttributeName.SOURCE_ID][PlatformType.DISCORD], - [PlatformType.TWITTER]: attributes[MemberAttributeName.SOURCE_ID][PlatformType.TWITTER], - default: attributes[MemberAttributeName.SOURCE_ID][PlatformType.TWITTER], - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: - attributes[MemberAttributeName.AVATAR_URL][PlatformType.TWITTER], - default: attributes[MemberAttributeName.AVATAR_URL][PlatformType.TWITTER], - }, - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - default: attributes[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.URL][PlatformType.GITHUB], - [PlatformType.TWITTER]: attributes[MemberAttributeName.URL][PlatformType.TWITTER], - default: attributes[MemberAttributeName.URL][PlatformType.TWITTER], - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - default: attributes[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.BIO][PlatformType.GITHUB], - default: attributes[MemberAttributeName.BIO][PlatformType.GITHUB], - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.LOCATION][PlatformType.GITHUB], - default: attributes[MemberAttributeName.LOCATION][PlatformType.GITHUB], - }, - }, - emails: member1.emails, - score: member1.score, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - organizations: [ - { - displayName: 'l.com', - id: memberCreated.organizations[0].id, - memberOrganizations: { - memberId: memberCreated.id, - organizationId: memberCreated.organizations[0].id, - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - }, - ], - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - reach: { total: -1 }, - joinedAt: new Date('2020-05-28T15:13:30Z'), - lastEnriched: null, - enrichedBy: [], - contributions: null, - affiliations: [], - manuallyCreated: false, - } - - expect(memberCreated).toStrictEqual(memberExpected) - }) - - it('Should create non existent member - object type username', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - const mas = new MemberAttributeSettingsService(mockIServiceOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const member1 = { - username: { - [PlatformType.GITHUB]: 'anil', - [PlatformType.TWITTER]: 'anil_twitter', - }, - platform: PlatformType.GITHUB, - emails: ['lala@l.com'], - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - joinedAt: '2020-05-28T15:13:30Z', - } - - // Save some attributes since they get modified in the upsert function - const { username, attributes } = member1 - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - username: { - [PlatformType.GITHUB]: ['anil'], - [PlatformType.TWITTER]: ['anil_twitter'], - }, - displayName: 'anil', - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - default: attributes[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.URL][PlatformType.GITHUB], - default: attributes[MemberAttributeName.URL][PlatformType.GITHUB], - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - default: attributes[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.BIO][PlatformType.GITHUB], - default: attributes[MemberAttributeName.BIO][PlatformType.GITHUB], - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.LOCATION][PlatformType.GITHUB], - default: attributes[MemberAttributeName.LOCATION][PlatformType.GITHUB], - }, - }, - emails: member1.emails, - score: member1.score, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - lastEnriched: null, - organizations: [ - { - displayName: 'l.com', - id: memberCreated.organizations[0].id, - memberOrganizations: { - memberId: memberCreated.id, - organizationId: memberCreated.organizations[0].id, - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - }, - ], - enrichedBy: [], - contributions: null, - reach: { total: -1 }, - joinedAt: new Date('2020-05-28T15:13:30Z'), - affiliations: [], - manuallyCreated: false, - } - - expect(memberCreated).toStrictEqual(memberExpected) - }) - - it('Should create non existent member - reach as number', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: 'anil', - platform: PlatformType.GITHUB, - emails: ['lala@l.com'], - score: 10, - attributes: {}, - reach: 10, - bio: 'Computer Science', - joinedAt: '2020-05-28T15:13:30Z', - location: 'Istanbul', - } - - // Save some attributes since they get modified in the upsert function - const { platform, username } = member1 - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - username: { - [platform]: [username], - }, - displayName: username, - attributes: {}, - emails: member1.emails, - lastEnriched: null, - organizations: [ - { - displayName: 'l.com', - id: memberCreated.organizations[0].id, - memberOrganizations: { - memberId: memberCreated.id, - organizationId: memberCreated.organizations[0].id, - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - }, - ], - enrichedBy: [], - contributions: null, - score: member1.score, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - reach: { total: 10, [PlatformType.GITHUB]: 10 }, - joinedAt: new Date('2020-05-28T15:13:30Z'), - affiliations: [], - manuallyCreated: false, - } - - expect(memberCreated).toStrictEqual(memberExpected) - }) - - it('Should create non existent member - reach as object, platform in object', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: 'anil', - platform: PlatformType.GITHUB, - emails: ['lala@l.com'], - score: 10, - reach: { [PlatformType.GITHUB]: 10, [PlatformType.TWITTER]: 10 }, - bio: 'Computer Science', - joinedAt: '2020-05-28T15:13:30Z', - location: 'Istanbul', - } - - // Save some attributes since they get modified in the upsert function - const { platform, username } = member1 - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - username: { - [platform]: [username], - }, - displayName: username, - attributes: {}, - lastEnriched: null, - organizations: [ - { - displayName: 'l.com', - id: memberCreated.organizations[0].id, - memberOrganizations: { - memberId: memberCreated.id, - organizationId: memberCreated.organizations[0].id, - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - }, - ], - enrichedBy: [], - contributions: null, - emails: member1.emails, - score: member1.score, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - reach: { total: 20, [PlatformType.GITHUB]: 10, [PlatformType.TWITTER]: 10 }, - joinedAt: new Date('2020-05-28T15:13:30Z'), - affiliations: [], - manuallyCreated: false, - } - - expect(memberCreated).toStrictEqual(memberExpected) - }) - - it('Should create non existent member - reach as object, platform not in object', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: 'anil', - platform: PlatformType.GITHUB, - emails: ['lala@l.com'], - score: 10, - reach: { [PlatformType.DISCORD]: 10, [PlatformType.TWITTER]: 10 }, - bio: 'Computer Science', - joinedAt: '2020-05-28T15:13:30Z', - location: 'Istanbul', - } - - // Save some attributes since they get modified in the upsert function - const { platform, username } = member1 - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - username: { - [platform]: [username], - }, - displayName: username, - attributes: {}, - emails: member1.emails, - score: member1.score, - lastEnriched: null, - organizations: [ - { - displayName: 'l.com', - id: memberCreated.organizations[0].id, - memberOrganizations: { - memberId: memberCreated.id, - organizationId: memberCreated.organizations[0].id, - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - }, - ], - enrichedBy: [], - contributions: null, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - reach: { total: 20, [PlatformType.DISCORD]: 10, [PlatformType.TWITTER]: 10 }, - joinedAt: new Date('2020-05-28T15:13:30Z'), - affiliations: [], - manuallyCreated: false, - } - - expect(memberCreated).toStrictEqual(memberExpected) - }) - - it('Should create non existent member - organization as name, no enrichment', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: 'anil', - platform: PlatformType.GITHUB, - emails: ['lala@gmail.com'], - score: 10, - attributes: {}, - reach: 10, - bio: 'Computer Science', - organizations: ['crowd.dev'], - joinedAt: '2020-05-28T15:13:30Z', - location: 'Istanbul', - } - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const organization = (await OrganizationRepository.findAndCountAll({}, mockIServiceOptions)) - .rows[0] - - const foundMember = await MemberRepository.findById(memberCreated.id, mockIServiceOptions) - - const o1 = foundMember.organizations[0].get({ plain: true }) - delete o1.createdAt - delete o1.updatedAt - - expect(o1).toStrictEqual({ - id: organization.id, - displayName: 'crowd.dev', - github: null, - location: null, - website: null, - description: null, - emails: null, - phoneNumbers: null, - logo: null, - memberOrganizations: { - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - tags: null, - twitter: null, - linkedin: null, - crunchbase: null, - employees: null, - revenueRange: null, - importHash: null, - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - isTeamOrganization: false, - type: null, - ticker: null, - size: null, - naics: null, - lastEnrichedAt: null, - industry: null, - headline: null, - geoLocation: null, - founded: null, - employeeCountByCountry: null, - address: null, - profiles: null, - attributes: {}, - manuallyCreated: false, - affiliatedProfiles: null, - allSubsidiaries: null, - alternativeDomains: null, - alternativeNames: null, - averageEmployeeTenure: null, - averageTenureByLevel: null, - averageTenureByRole: null, - directSubsidiaries: null, - employeeChurnRate: null, - employeeCountByMonth: null, - employeeGrowthRate: null, - employeeCountByMonthByLevel: null, - employeeCountByMonthByRole: null, - gicsSector: null, - grossAdditionsByMonth: null, - grossDeparturesByMonth: null, - ultimateParent: null, - immediateParent: null, - manuallyChangedFields: null, - }) - }) - - it('Should create non existent member - organization as object, no enrichment', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: 'anil', - platform: PlatformType.GITHUB, - emails: ['lala@gmail.com'], - score: 10, - attributes: {}, - reach: 10, - bio: 'Computer Science', - organizations: [{ name: 'crowd.dev', url: 'https://crowd.dev', description: 'Here' }], - joinedAt: '2020-05-28T15:13:30Z', - location: 'Istanbul', - } - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const organization = (await OrganizationRepository.findAndCountAll({}, mockIServiceOptions)) - .rows[0] - - const foundMember = await MemberRepository.findById(memberCreated.id, mockIServiceOptions) - - const o1 = foundMember.organizations[0].get({ plain: true }) - delete o1.createdAt - delete o1.updatedAt - - expect(o1).toStrictEqual({ - id: organization.id, - displayName: 'crowd.dev', - github: null, - location: null, - website: null, - description: 'Here', - emails: null, - phoneNumbers: null, - logo: null, - memberOrganizations: { - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - tags: null, - twitter: null, - linkedin: null, - crunchbase: null, - employees: null, - revenueRange: null, - importHash: null, - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - isTeamOrganization: false, - type: null, - ticker: null, - size: null, - naics: null, - lastEnrichedAt: null, - industry: null, - headline: null, - geoLocation: null, - founded: null, - employeeCountByCountry: null, - address: null, - profiles: null, - attributes: {}, - manuallyCreated: false, - affiliatedProfiles: null, - allSubsidiaries: null, - alternativeDomains: null, - alternativeNames: null, - averageEmployeeTenure: null, - averageTenureByLevel: null, - averageTenureByRole: null, - directSubsidiaries: null, - employeeChurnRate: null, - employeeCountByMonth: null, - employeeGrowthRate: null, - employeeCountByMonthByLevel: null, - employeeCountByMonthByRole: null, - gicsSector: null, - grossAdditionsByMonth: null, - grossDeparturesByMonth: null, - ultimateParent: null, - immediateParent: null, - manuallyChangedFields: null, - }) - }) - - it('Should create non existent member - organization as id', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const oCreated = await new OrganizationService(mockIServiceOptions).createOrUpdate({ - identities: [ - { - name: 'crowd.dev', - platform: 'crowd', - }, - ], - }) - - const member1 = { - username: 'anil', - platform: PlatformType.GITHUB, - emails: ['lala@gmail.com'], - score: 10, - attributes: {}, - reach: 10, - bio: 'Computer Science', - organizations: [oCreated.id], - joinedAt: '2020-05-28T15:13:30Z', - location: 'Istanbul', - } - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const organization = (await OrganizationRepository.findAndCountAll({}, mockIServiceOptions)) - .rows[0] - - const foundMember = await MemberRepository.findById(memberCreated.id, mockIServiceOptions) - - const o1 = foundMember.organizations[0].get({ plain: true }) - delete o1.createdAt - delete o1.updatedAt - - expect(o1).toStrictEqual({ - id: organization.id, - displayName: 'crowd.dev', - github: null, - location: null, - website: null, - description: null, - emails: null, - phoneNumbers: null, - logo: null, - manuallyChangedFields: null, - memberOrganizations: { - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - tags: null, - twitter: null, - linkedin: null, - crunchbase: null, - employees: null, - revenueRange: null, - importHash: null, - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - isTeamOrganization: false, - type: null, - ticker: null, - size: null, - naics: null, - lastEnrichedAt: null, - industry: null, - headline: null, - geoLocation: null, - founded: null, - employeeCountByCountry: null, - address: null, - profiles: null, - attributes: {}, - manuallyCreated: false, - affiliatedProfiles: null, - allSubsidiaries: null, - alternativeDomains: null, - alternativeNames: null, - averageEmployeeTenure: null, - averageTenureByLevel: null, - averageTenureByRole: null, - directSubsidiaries: null, - employeeChurnRate: null, - employeeCountByMonth: null, - employeeGrowthRate: null, - employeeCountByMonthByLevel: null, - employeeCountByMonthByRole: null, - gicsSector: null, - grossAdditionsByMonth: null, - grossDeparturesByMonth: null, - ultimateParent: null, - immediateParent: null, - }) - }) - - it('Should update existent member succesfully - simple', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const mas = new MemberAttributeSettingsService(mockIServiceOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const member1 = { - username: 'anil', - emails: ['lala@l.com'], - platform: PlatformType.GITHUB, - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - joinedAt: '2020-05-28T15:13:30Z', - } - - const member1Username = member1.username - const attributes = member1.attributes - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const member2 = { - username: 'anil', - emails: ['test@gmail.com', 'test2@yahoo.com'], - platform: PlatformType.GITHUB, - location: 'Ankara', - } - - const memberUpdated = await new MemberService(mockIServiceOptions).upsert(member2) - - memberUpdated.createdAt = memberUpdated.createdAt.toISOString().split('T')[0] - memberUpdated.updatedAt = memberUpdated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - username: { - [PlatformType.GITHUB]: [member1Username], - }, - displayName: member1Username, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - default: attributes[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.URL][PlatformType.GITHUB], - default: attributes[MemberAttributeName.URL][PlatformType.GITHUB], - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - default: attributes[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.BIO][PlatformType.GITHUB], - default: attributes[MemberAttributeName.BIO][PlatformType.GITHUB], - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.LOCATION][PlatformType.GITHUB], - default: attributes[MemberAttributeName.LOCATION][PlatformType.GITHUB], - }, - }, - lastEnriched: null, - organizations: [ - { - displayName: 'l.com', - id: memberCreated.organizations[0].id, - memberOrganizations: { - memberId: memberCreated.id, - organizationId: memberCreated.organizations[0].id, - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - }, - ], - enrichedBy: [], - contributions: null, - emails: ['lala@l.com', 'test@gmail.com', 'test2@yahoo.com'], - score: member1.score, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - joinedAt: new Date('2020-05-28T15:13:30Z'), - reach: { total: -1 }, - affiliations: [], - manuallyCreated: false, - } - - expect(memberUpdated).toStrictEqual(memberExpected) - }) - - it('Should update existent member successfully - attributes with matching platform', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const mas = new MemberAttributeSettingsService(mockIServiceOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await mas.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - - const member1 = { - username: 'anil', - emails: ['lala@l.com'], - platform: PlatformType.GITHUB, - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - }, - }, - joinedAt: '2020-05-28T15:13:30Z', - } - - const member1Username = member1.username - const attributes1 = member1.attributes - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const member2 = { - username: 'anil', - platform: PlatformType.GITHUB, - attributes: { - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - [MemberAttributeName.URL]: { - [PlatformType.TWITTER]: 'https://twitter-url', - }, - }, - } - - const attributes2 = member2.attributes - - const memberUpdated = await new MemberService(mockIServiceOptions).upsert(member2) - - memberUpdated.createdAt = memberUpdated.createdAt.toISOString().split('T')[0] - memberUpdated.updatedAt = memberUpdated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - username: { - [PlatformType.GITHUB]: [member1Username], - }, - displayName: member1Username, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: - attributes1[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - default: attributes1[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: attributes1[MemberAttributeName.URL][PlatformType.GITHUB], - [PlatformType.TWITTER]: attributes2[MemberAttributeName.URL][PlatformType.TWITTER], - default: attributes2[MemberAttributeName.URL][PlatformType.TWITTER], - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: - attributes2[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - default: attributes2[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: attributes2[MemberAttributeName.BIO][PlatformType.GITHUB], - default: attributes2[MemberAttributeName.BIO][PlatformType.GITHUB], - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: attributes2[MemberAttributeName.LOCATION][PlatformType.GITHUB], - default: attributes2[MemberAttributeName.LOCATION][PlatformType.GITHUB], - }, - }, - lastEnriched: null, - organizations: [ - { - displayName: 'l.com', - id: memberCreated.organizations[0].id, - memberOrganizations: { - memberId: memberCreated.id, - organizationId: memberCreated.organizations[0].id, - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - }, - ], - enrichedBy: [], - contributions: null, - emails: member1.emails, - score: member1.score, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - joinedAt: new Date('2020-05-28T15:13:30Z'), - reach: { total: -1 }, - affiliations: [], - manuallyCreated: false, - } - - expect(memberUpdated).toStrictEqual(memberExpected) - }) - - it('Should update existent member succesfully - object type username', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const mas = new MemberAttributeSettingsService(mockIServiceOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const member1 = { - username: 'anil', - emails: ['lala@l.com'], - platform: PlatformType.GITHUB, - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - joinedAt: '2020-05-28T15:13:30Z', - } - - const member1Username = member1.username - const attributes = member1.attributes - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const member2 = { - username: { - [PlatformType.GITHUB]: 'anil', - [PlatformType.TWITTER]: 'anil_twitter', - [PlatformType.DISCORD]: 'anil_discord', - }, - platform: PlatformType.GITHUB, - } - - const memberUpdated = await new MemberService(mockIServiceOptions).upsert(member2) - - memberUpdated.createdAt = memberUpdated.createdAt.toISOString().split('T')[0] - memberUpdated.updatedAt = memberUpdated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - username: { - [PlatformType.GITHUB]: ['anil'], - [PlatformType.TWITTER]: ['anil_twitter'], - [PlatformType.DISCORD]: ['anil_discord'], - }, - displayName: 'anil', - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - default: attributes[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.URL][PlatformType.GITHUB], - default: attributes[MemberAttributeName.URL][PlatformType.GITHUB], - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - default: attributes[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.BIO][PlatformType.GITHUB], - default: attributes[MemberAttributeName.BIO][PlatformType.GITHUB], - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.LOCATION][PlatformType.GITHUB], - default: attributes[MemberAttributeName.LOCATION][PlatformType.GITHUB], - }, - }, - emails: member1.emails, - lastEnriched: null, - organizations: [ - { - displayName: 'l.com', - id: memberCreated.organizations[0].id, - memberOrganizations: { - memberId: memberCreated.id, - organizationId: memberCreated.organizations[0].id, - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - }, - ], - enrichedBy: [], - contributions: null, - score: member1.score, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - joinedAt: new Date('2020-05-28T15:13:30Z'), - reach: { total: -1 }, - affiliations: [], - manuallyCreated: false, - } - - expect(memberUpdated).toStrictEqual(memberExpected) - }) - - it('Should throw 400 error when given platform does not match with username object ', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - const mas = new MemberAttributeSettingsService(mockIServiceOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const member1 = { - username: 'anil', - emails: ['lala@l.com'], - platform: PlatformType.GITHUB, - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - joinedAt: '2020-05-28T15:13:30Z', - } - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const member2 = { - username: { - [PlatformType.GITHUB]: 'anil', - [PlatformType.TWITTER]: 'anil_twitter', - [PlatformType.DISCORD]: 'anil_discord', - }, - platform: PlatformType.SLACK, - } - - await expect(() => - new MemberService(mockIServiceOptions).upsert(member2), - ).rejects.toThrowError(new Error400()) - }) - - it('Should update existent member succesfully - JSON fields', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - const mas = new MemberAttributeSettingsService(mockIServiceOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await mas.createPredefined(DEVTO_MEMBER_ATTRIBUTES) - - const member1 = { - username: 'anil', - platform: PlatformType.TWITTER, - emails: ['lala@l.com'], - score: 10, - attributes: { - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/imcvampire', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - }, - joinedAt: '2020-05-28T15:13:30Z', - } - - const member1Username = member1.username - const attributes1 = member1.attributes - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const member2 = { - username: 'anil', - platform: PlatformType.TWITTER, - joinedAt: '2020-05-28T15:13:30Z', - location: 'Ankara', - attributes: { - [MemberAttributeName.SOURCE_ID]: { - [PlatformType.DEVTO]: '#someDevtoId', - [PlatformType.SLACK]: '#someSlackId', - }, - [MemberAttributeName.NAME]: { - [PlatformType.DEVTO]: 'Michael Scott', - }, - [MemberAttributeName.URL]: { - [PlatformType.DEVTO]: 'https://some-devto-url', - }, - }, - } - - const attributes2 = member2.attributes - - const memberUpdated = await new MemberService(mockIServiceOptions).upsert(member2) - - memberUpdated.createdAt = memberUpdated.createdAt.toISOString().split('T')[0] - memberUpdated.updatedAt = memberUpdated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - joinedAt: new Date('2020-05-28T15:13:30Z'), - username: { - [PlatformType.TWITTER]: [member1Username], - }, - displayName: member1Username, - attributes: { - [MemberAttributeName.SOURCE_ID]: { - [PlatformType.DEVTO]: attributes2[MemberAttributeName.SOURCE_ID][PlatformType.DEVTO], - [PlatformType.SLACK]: attributes2[MemberAttributeName.SOURCE_ID][PlatformType.SLACK], - default: attributes2[MemberAttributeName.SOURCE_ID][PlatformType.DEVTO], - }, - [MemberAttributeName.NAME]: { - [PlatformType.DEVTO]: attributes2[MemberAttributeName.NAME][PlatformType.DEVTO], - default: attributes2[MemberAttributeName.NAME][PlatformType.DEVTO], - }, - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: - attributes1[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - default: attributes1[MemberAttributeName.IS_HIREABLE][PlatformType.GITHUB], - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: attributes1[MemberAttributeName.URL][PlatformType.GITHUB], - [PlatformType.DEVTO]: attributes2[MemberAttributeName.URL][PlatformType.DEVTO], - default: attributes1[MemberAttributeName.URL][PlatformType.GITHUB], - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: - attributes1[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - default: attributes1[MemberAttributeName.WEBSITE_URL][PlatformType.GITHUB], - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: attributes1[MemberAttributeName.BIO][PlatformType.GITHUB], - default: attributes1[MemberAttributeName.BIO][PlatformType.GITHUB], - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: attributes1[MemberAttributeName.LOCATION][PlatformType.GITHUB], - default: attributes1[MemberAttributeName.LOCATION][PlatformType.GITHUB], - }, - }, - emails: member1.emails, - lastEnriched: null, - organizations: [ - { - displayName: 'l.com', - id: memberCreated.organizations[0].id, - memberOrganizations: { - memberId: memberCreated.id, - organizationId: memberCreated.organizations[0].id, - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - }, - ], - enrichedBy: [], - contributions: null, - score: member1.score, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - reach: { total: -1 }, - affiliations: [], - manuallyCreated: false, - } - - expect(memberUpdated).toStrictEqual(memberExpected) - }) - - it('Should update existent member succesfully - reach from default to complete - sending number', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: 'anil', - platform: PlatformType.GITHUB, - joinedAt: '2020-05-28T15:13:30Z', - } - - const member1Username = member1.username - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const member2 = { - username: 'anil', - platform: PlatformType.GITHUB, - reach: 10, - } - - const memberUpdated = await new MemberService(mockIServiceOptions).upsert(member2) - - memberUpdated.createdAt = memberUpdated.createdAt.toISOString().split('T')[0] - memberUpdated.updatedAt = memberUpdated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - joinedAt: new Date('2020-05-28T15:13:30Z'), - username: { - [PlatformType.GITHUB]: [member1Username], - }, - displayName: member1Username, - lastEnriched: null, - organizations: [], - enrichedBy: [], - contributions: null, - reach: { total: 10, [PlatformType.GITHUB]: 10 }, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - score: -1, - emails: [], - attributes: {}, - affiliations: [], - manuallyCreated: false, - } - - expect(memberUpdated).toStrictEqual(memberExpected) - }) - - it('Should update existent member succesfully - reach from default to complete - sending platform', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: 'anil', - type: 'member', - platform: PlatformType.GITHUB, - joinedAt: '2020-05-28T15:13:30Z', - } - - const member1Username = member1.username - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const member2 = { - username: 'anil', - platform: PlatformType.GITHUB, - reach: { [PlatformType.GITHUB]: 10 }, - } - - const memberUpdated = await new MemberService(mockIServiceOptions).upsert(member2) - - memberUpdated.createdAt = memberUpdated.createdAt.toISOString().split('T')[0] - memberUpdated.updatedAt = memberUpdated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - joinedAt: new Date('2020-05-28T15:13:30Z'), - username: { - [PlatformType.GITHUB]: [member1Username], - }, - lastEnriched: null, - organizations: [], - enrichedBy: [], - contributions: null, - displayName: member1Username, - reach: { total: 10, [PlatformType.GITHUB]: 10 }, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - score: -1, - emails: [], - attributes: {}, - affiliations: [], - manuallyCreated: false, - } - - expect(memberUpdated).toStrictEqual(memberExpected) - }) - - it('Should update existent member succesfully - complex reach update from object', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: 'anil', - type: 'member', - platform: PlatformType.GITHUB, - joinedAt: '2020-05-28T15:13:30Z', - reach: { [PlatformType.TWITTER]: 10, linkedin: 10, total: 20 }, - } - - const member1Username = member1.username - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const member2 = { - username: 'anil', - platform: PlatformType.GITHUB, - reach: { [PlatformType.GITHUB]: 15, linkedin: 11 }, - } - - const memberUpdated = await new MemberService(mockIServiceOptions).upsert(member2) - - memberUpdated.createdAt = memberUpdated.createdAt.toISOString().split('T')[0] - memberUpdated.updatedAt = memberUpdated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - joinedAt: new Date('2020-05-28T15:13:30Z'), - username: { - [PlatformType.GITHUB]: [member1Username], - }, - lastEnriched: null, - organizations: [], - enrichedBy: [], - contributions: null, - displayName: member1Username, - reach: { total: 36, [PlatformType.GITHUB]: 15, linkedin: 11, [PlatformType.TWITTER]: 10 }, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - score: -1, - emails: [], - attributes: {}, - affiliations: [], - manuallyCreated: false, - } - - expect(memberUpdated).toStrictEqual(memberExpected) - }) - - it('Should update existent member succesfully - complex reach update from number', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: 'anil', - type: 'member', - platform: PlatformType.GITHUB, - joinedAt: '2020-05-28T15:13:30Z', - reach: { [PlatformType.TWITTER]: 10, linkedin: 10, total: 20 }, - } - - const member1Username = member1.username - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const member2 = { - username: 'anil', - platform: PlatformType.GITHUB, - reach: 30, - } - - const memberUpdated = await new MemberService(mockIServiceOptions).upsert(member2) - - memberUpdated.createdAt = memberUpdated.createdAt.toISOString().split('T')[0] - memberUpdated.updatedAt = memberUpdated.updatedAt.toISOString().split('T')[0] - - const memberExpected = { - id: memberCreated.id, - joinedAt: new Date('2020-05-28T15:13:30Z'), - username: { - [PlatformType.GITHUB]: [member1Username], - }, - displayName: member1Username, - lastEnriched: null, - organizations: [], - enrichedBy: [], - contributions: null, - reach: { total: 50, [PlatformType.GITHUB]: 30, linkedin: 10, [PlatformType.TWITTER]: 10 }, - importHash: null, - createdAt: SequelizeTestUtils.getNowWithoutTime(), - updatedAt: SequelizeTestUtils.getNowWithoutTime(), - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - segments: mockIServiceOptions.currentSegments, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - score: -1, - emails: [], - attributes: {}, - affiliations: [], - manuallyCreated: false, - } - - expect(memberUpdated).toStrictEqual(memberExpected) - }) - }) - - describe('update method', () => { - it('Should update existent member succesfully - removing identities with simple string format', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: 'anil', - type: 'member', - platform: PlatformType.GITHUB, - joinedAt: '2020-05-28T15:13:30Z', - } - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - const toUpdate = { - username: 'anil_new', - platform: PlatformType.GITHUB, - } - - const memberUpdated = await new MemberService(mockIServiceOptions).update( - memberCreated.id, - toUpdate, - ) - - expect(memberUpdated.username[PlatformType.GITHUB]).toStrictEqual(['anil_new']) - }) - - it('Should update existent member succesfully - removing identities with simple identity format', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: { - [PlatformType.GITHUB]: { - username: 'anil', - }, - }, - platform: PlatformType.GITHUB, - type: 'member', - joinedAt: '2020-05-28T15:13:30Z', - } - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - const toUpdate = { - username: { - [PlatformType.GITHUB]: { - username: 'anil_new', - }, - }, - platform: PlatformType.GITHUB, - } - - const memberUpdated = await new MemberService(mockIServiceOptions).update( - memberCreated.id, - toUpdate, - ) - - expect(memberUpdated.username[PlatformType.GITHUB]).toStrictEqual(['anil_new']) - }) - - it('Should update existent member succesfully - removing identities with array identity format', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const member1 = { - username: { - [PlatformType.GITHUB]: [ - { - username: 'anil', - }, - ], - }, - platform: PlatformType.GITHUB, - type: 'member', - joinedAt: '2020-05-28T15:13:30Z', - } - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - const toUpdate = { - username: { - [PlatformType.GITHUB]: [ - { - username: 'anil_new', - }, - { - username: 'anil_new2', - }, - ], - }, - platform: PlatformType.GITHUB, - } - - const memberUpdated = await new MemberService(mockIServiceOptions).update( - memberCreated.id, - toUpdate, - ) - - expect(memberUpdated.username[PlatformType.GITHUB]).toStrictEqual(['anil_new', 'anil_new2']) - }) - }) - - describe('merge method', () => { - it('Should catch when two members are the same', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const mas = new MemberAttributeSettingsService(mockIRepositoryOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const memberService = new MemberService(mockIRepositoryOptions) - - const member1 = { - username: { - [PlatformType.GITHUB]: 'anil', - }, - displayName: 'Anil', - joinedAt: '2021-05-27T15:14:30Z', - attributes: {}, - } - - const memberCreated = await MemberRepository.create(member1, mockIRepositoryOptions) - const mergeOutput = await memberService.merge(memberCreated.id, memberCreated.id) - - expect(mergeOutput).toStrictEqual({ status: 203, mergedId: memberCreated.id }) - - const found = await memberService.findById(memberCreated.id) - expect(found).toStrictEqual(memberCreated) - }) - }) - - describe('addToNoMerge method', () => { - it('Should add two members to their respective noMerges, these members should be excluded from toMerges respectively', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const mas = new MemberAttributeSettingsService(mockIRepositoryOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await mas.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - await mas.createPredefined(DISCORD_MEMBER_ATTRIBUTES) - - const memberService = new MemberService(mockIRepositoryOptions) - - const member1 = { - username: { - [PlatformType.GITHUB]: 'anil', - }, - displayName: 'Anil', - joinedAt: '2021-05-27T15:14:30Z', - attributes: {}, - } - - const member2 = { - username: { - [PlatformType.DISCORD]: 'anil', - }, - displayName: 'Anil', - joinedAt: '2021-05-30T15:14:30Z', - attributes: { - [MemberAttributeName.SOURCE_ID]: { - [PlatformType.DISCORD]: '#discordId', - default: '#discordId', - }, - }, - } - - const member3 = { - username: { - [PlatformType.TWITTER]: 'anil', - }, - displayName: 'Anil', - joinedAt: '2021-05-30T15:14:30Z', - attributes: { - [MemberAttributeName.URL]: { - [PlatformType.TWITTER]: 'https://a-twitter-url', - default: 'https://a-twitter-url', - }, - }, - } - - let returnedMember1 = await MemberRepository.create(member1, mockIRepositoryOptions) - let returnedMember2 = await MemberRepository.create(member2, mockIRepositoryOptions) - let returnedMember3 = await MemberRepository.create(member3, mockIRepositoryOptions) - - // toMerge[1] = [(1,2),(1,3)] toMerge[2] = [(2,1),(2,3)] toMerge[3] = [(3,1),(3,2)] - await MemberRepository.addToMerge( - [{ members: [returnedMember1.id, returnedMember2.id], similarity: null }], - mockIRepositoryOptions, - ) - await MemberRepository.addToMerge( - [{ members: [returnedMember2.id, returnedMember1.id], similarity: null }], - mockIRepositoryOptions, - ) - - await MemberRepository.addToMerge( - [{ members: [returnedMember1.id, returnedMember3.id], similarity: null }], - mockIRepositoryOptions, - ) - await MemberRepository.addToMerge( - [{ members: [returnedMember3.id, returnedMember1.id], similarity: null }], - mockIRepositoryOptions, - ) - await MemberRepository.addToMerge( - [{ members: [returnedMember2.id, returnedMember3.id], similarity: null }], - mockIRepositoryOptions, - ) - await MemberRepository.addToMerge( - [{ members: [returnedMember3.id, returnedMember2.id], similarity: null }], - mockIRepositoryOptions, - ) - - await memberService.addToNoMerge(returnedMember1.id, returnedMember2.id) - - returnedMember1 = await MemberRepository.findById(returnedMember1.id, mockIRepositoryOptions) - - expect(returnedMember1.toMerge).toStrictEqual([returnedMember3.id]) - expect(returnedMember1.noMerge).toStrictEqual([returnedMember2.id]) - - returnedMember2 = await MemberRepository.findById(returnedMember2.id, mockIRepositoryOptions) - - expect(returnedMember2.toMerge).toStrictEqual([returnedMember3.id]) - expect(returnedMember2.noMerge).toStrictEqual([returnedMember1.id]) - - // call addToNoMerge once more, between member1 and member3 - await memberService.addToNoMerge(returnedMember1.id, returnedMember3.id) - - returnedMember1 = await MemberRepository.findById(returnedMember1.id, mockIRepositoryOptions) - - expect(returnedMember1.toMerge).toStrictEqual([]) - expect(returnedMember1.noMerge).toStrictEqual([returnedMember2.id, returnedMember3.id]) - - returnedMember3 = await MemberRepository.findById(returnedMember3.id, mockIRepositoryOptions) - - expect(returnedMember3.toMerge).toStrictEqual([returnedMember2.id]) - expect(returnedMember3.noMerge).toStrictEqual([returnedMember1.id]) - - // only toMerge relation (2,3) left. Testing addToNoMerge(2,3) - await memberService.addToNoMerge(returnedMember3.id, returnedMember2.id) - - returnedMember2 = await MemberRepository.findById(returnedMember2.id, mockIRepositoryOptions) - - expect(returnedMember2.toMerge).toStrictEqual([]) - expect(returnedMember2.noMerge).toStrictEqual([returnedMember1.id, returnedMember3.id]) - - returnedMember3 = await MemberRepository.findById(returnedMember3.id, mockIRepositoryOptions) - - expect(returnedMember3.toMerge).toStrictEqual([]) - expect(returnedMember3.noMerge).toStrictEqual([returnedMember1.id, returnedMember2.id]) - }) - - it('Should throw 404 not found when trying to add non existent members to noMerge', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const mas = new MemberAttributeSettingsService(mockIRepositoryOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const memberService = new MemberService(mockIRepositoryOptions) - - const member1 = { - username: { - [PlatformType.GITHUB]: 'anil', - }, - displayName: 'Anil', - joinedAt: '2021-05-27T15:14:30Z', - attributes: {}, - } - - const returnedMember1 = await MemberRepository.create(member1, mockIRepositoryOptions) - - const { randomUUID } = require('crypto') - - await expect(() => - memberService.addToNoMerge(returnedMember1.id, randomUUID()), - ).rejects.toThrowError(new Error404()) - }) - }) - - describe('memberExists method', () => { - it('Should find existing member with string username and default platform', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const mas = new MemberAttributeSettingsService(mockIRepositoryOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const memberService = new MemberService(mockIRepositoryOptions) - - const member1 = { - username: { - [PlatformType.GITHUB]: 'anil', - }, - displayName: 'Anil', - joinedAt: '2021-05-27T15:14:30Z', - attributes: {}, - } - - const cloned = lodash.cloneDeep(member1) - const returnedMember1 = await MemberRepository.create(cloned, mockIRepositoryOptions) - delete returnedMember1.toMerge - delete returnedMember1.noMerge - delete returnedMember1.tags - delete returnedMember1.activities - delete returnedMember1.tasks - delete returnedMember1.notes - delete returnedMember1.activityCount - delete returnedMember1.averageSentiment - delete returnedMember1.lastActive - delete returnedMember1.lastActivity - delete returnedMember1.activeOn - delete returnedMember1.identities - delete returnedMember1.activityTypes - delete returnedMember1.activeDaysCount - delete returnedMember1.numberOfOpenSourceContributions - delete returnedMember1.affiliations - delete returnedMember1.manuallyCreated - - returnedMember1.segments = returnedMember1.segments.map((s) => s.id) - - const existing = await memberService.memberExists( - member1.username[PlatformType.GITHUB], - PlatformType.GITHUB, - ) - - expect(existing).toStrictEqual(returnedMember1) - }) - - it('Should return null if member is not found - string type', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const mas = new MemberAttributeSettingsService(mockIRepositoryOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const memberService = new MemberService(mockIRepositoryOptions) - - const member1 = { - username: { - [PlatformType.GITHUB]: 'anil', - }, - displayName: 'Anil', - joinedAt: '2021-05-27T15:14:30Z', - attributes: {}, - } - - await MemberRepository.create(member1, mockIRepositoryOptions) - - const existing = await memberService.memberExists('some-random-username', PlatformType.GITHUB) - - expect(existing).toBeNull() - }) - - it('Should return null if member is not found - object type', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const mas = new MemberAttributeSettingsService(mockIRepositoryOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const memberService = new MemberService(mockIRepositoryOptions) - - const member1 = { - username: { - [PlatformType.GITHUB]: 'anil', - }, - displayName: 'Anil', - joinedAt: '2021-05-27T15:14:30Z', - attributes: {}, - } - - await MemberRepository.create(member1, mockIRepositoryOptions) - - const existing = await memberService.memberExists( - { - ...member1.username, - [PlatformType.SLACK]: 'some-slack-username', - }, - PlatformType.SLACK, - ) - - expect(existing).toBeNull() - }) - - it('Should find existing member with object username and given platform', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const mas = new MemberAttributeSettingsService(mockIRepositoryOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const memberService = new MemberService(mockIRepositoryOptions) - - const member1 = { - username: { - [PlatformType.GITHUB]: 'anil', - [PlatformType.DISCORD]: 'some-other-username', - }, - displayName: 'Anil', - joinedAt: '2021-05-27T15:14:30Z', - attributes: {}, - } - - const returnedMember1 = await MemberRepository.create(member1, mockIRepositoryOptions) - delete returnedMember1.toMerge - delete returnedMember1.noMerge - delete returnedMember1.tags - delete returnedMember1.activities - delete returnedMember1.tasks - delete returnedMember1.notes - delete returnedMember1.activityCount - delete returnedMember1.averageSentiment - delete returnedMember1.lastActive - delete returnedMember1.lastActivity - delete returnedMember1.activeOn - delete returnedMember1.identities - delete returnedMember1.activityTypes - delete returnedMember1.activeDaysCount - delete returnedMember1.numberOfOpenSourceContributions - delete returnedMember1.affiliations - delete returnedMember1.manuallyCreated - - returnedMember1.segments = returnedMember1.segments.map((s) => s.id) - - const existing = await memberService.memberExists( - { [PlatformType.DISCORD]: 'some-other-username' }, - PlatformType.DISCORD, - ) - - expect(returnedMember1).toStrictEqual(existing) - }) - - it('Should throw 400 error when username is type of object and username[platform] is not present ', async () => { - const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db) - const mas = new MemberAttributeSettingsService(mockIRepositoryOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const memberService = new MemberService(mockIRepositoryOptions) - - const member1 = { - username: { - [PlatformType.GITHUB]: 'anil', - [PlatformType.DISCORD]: 'some-other-username', - }, - displayName: 'Anil', - joinedAt: '2021-05-27T15:14:30Z', - attributes: {}, - } - - await MemberRepository.create(member1, mockIRepositoryOptions) - - await expect(() => - memberService.memberExists( - { [PlatformType.DISCORD]: 'some-other-username' }, - PlatformType.SLACK, - ), - ).rejects.toThrowError(new Error400()) - }) - }) - - describe('Update Reach method', () => { - it('Should keep as total: -1 for an empty new reach and a default old reach', async () => { - const oldReach = { total: -1 } - const updatedReach = MemberService.calculateReach({}, oldReach) - expect(updatedReach).toStrictEqual({ - total: -1, - }) - }) - it('Should keep as total: -1 for a default new reach and a default old reach', async () => { - const oldReach = { total: -1 } - const updatedReach = MemberService.calculateReach({ total: -1 }, oldReach) - expect(updatedReach).toStrictEqual({ - total: -1, - }) - }) - it('Should update for a new reach and a default old reach', async () => { - const oldReach = { total: -1 } - const newReach = { [PlatformType.TWITTER]: 10 } - const updatedReach = MemberService.calculateReach(oldReach, newReach) - expect(updatedReach).toStrictEqual({ - total: 10, - [PlatformType.TWITTER]: 10, - }) - }) - it('Should update for a new reach and old reach in the same platform', async () => { - const oldReach = { [PlatformType.TWITTER]: 5, total: 5 } - const newReach = { [PlatformType.TWITTER]: 10 } - const updatedReach = MemberService.calculateReach(oldReach, newReach) - expect(updatedReach).toStrictEqual({ - total: 10, - [PlatformType.TWITTER]: 10, - }) - }) - it('Should update for a complex reach with different platforms', async () => { - const oldReach = { - [PlatformType.TWITTER]: 10, - [PlatformType.GITHUB]: 20, - [PlatformType.DISCORD]: 50, - total: 10 + 20 + 50, - } - const newReach = { - [PlatformType.TWITTER]: 20, - [PlatformType.GITHUB]: 2, - linkedin: 10, - total: 20 + 2 + 10, - } - const updatedReach = MemberService.calculateReach(oldReach, newReach) - expect(updatedReach).toStrictEqual({ - total: 10 + 20 + 2 + 50, - [PlatformType.TWITTER]: 20, - [PlatformType.GITHUB]: 2, - linkedin: 10, - [PlatformType.DISCORD]: 50, - }) - }) - it('Should work with reach 0', async () => { - const oldReach = { total: -1 } - const newReach = { [PlatformType.TWITTER]: 0 } - const updatedReach = MemberService.calculateReach(oldReach, newReach) - expect(updatedReach).toStrictEqual({ - total: 0, - [PlatformType.TWITTER]: 0, - }) - }) - }) - - describe('getHighestPriorityPlatformForAttributes method', () => { - it('Should return the highest priority platform from a priority array, handling the exceptions', async () => { - const priorityArray = [ - PlatformType.TWITTER, - PlatformType.CROWD, - PlatformType.SLACK, - PlatformType.DEVTO, - PlatformType.DISCORD, - PlatformType.GITHUB, - ] - - let inputPlatforms = [PlatformType.GITHUB, PlatformType.DEVTO] - let highestPriorityPlatform = MemberService.getHighestPriorityPlatformForAttributes( - inputPlatforms, - priorityArray, - ) - - expect(highestPriorityPlatform).toBe(PlatformType.DEVTO) - - inputPlatforms = [PlatformType.GITHUB, 'someOtherPlatform'] as any - highestPriorityPlatform = MemberService.getHighestPriorityPlatformForAttributes( - inputPlatforms, - priorityArray, - ) - - expect(highestPriorityPlatform).toBe(PlatformType.GITHUB) - - inputPlatforms = ['somePlatform1', 'somePlatform2'] as any - - // if no match in the priority array, it should return the first platform it finds - highestPriorityPlatform = MemberService.getHighestPriorityPlatformForAttributes( - inputPlatforms, - priorityArray, - ) - - expect(highestPriorityPlatform).toBe('somePlatform1') - - inputPlatforms = [] - - // if no platforms are sent to choose from, it should return undefined - highestPriorityPlatform = MemberService.getHighestPriorityPlatformForAttributes( - inputPlatforms, - priorityArray, - ) - expect(highestPriorityPlatform).not.toBeDefined() - }) - }) - - describe('validateAttributes method', () => { - it('Should validate attributes object succesfully', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const memberService = new MemberService(mockIServiceOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService(mockIServiceOptions) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(DEVTO_MEMBER_ATTRIBUTES) - - const attributes = { - [MemberAttributeName.NAME]: { - [PlatformType.DEVTO]: 'Dweet Srute', - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://some-github-url', - [PlatformType.TWITTER]: 'https://some-twitter-url', - [PlatformType.DEVTO]: 'https://some-devto-url', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Berlin', - [PlatformType.DEVTO]: 'Istanbul', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Assistant to the Regional Manager', - [PlatformType.DEVTO]: 'Assistant Regional Manager', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://some-image-url', - }, - } - - const validateAttributes = await memberService.validateAttributes(attributes) - - expect(validateAttributes).toEqual(attributes) - }) - - it(`Should accept custom attributes without 'custom' platform key`, async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const memberService = new MemberService(mockIServiceOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService(mockIServiceOptions) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - - const attributes = { - [MemberAttributeName.BIO]: 'Assistant to the Regional Manager', - } - - const validateAttributes = await memberService.validateAttributes(attributes) - - const expectedValidatedAttributes = { - [MemberAttributeName.BIO]: { - custom: 'Assistant to the Regional Manager', - }, - } - - expect(validateAttributes).toEqual(expectedValidatedAttributes) - }) - - it(`Should accept custom attributes both without and with 'custom' platform key`, async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const memberService = new MemberService(mockIServiceOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService(mockIServiceOptions) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(DEVTO_MEMBER_ATTRIBUTES) - - const attributes = { - [MemberAttributeName.NAME]: 'Dwight Schrute', - [MemberAttributeName.URL]: 'https://some-url', - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Berlin', - [PlatformType.DEVTO]: 'Istanbul', - custom: 'a custom location', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Assistant to the Regional Manager', - [PlatformType.DEVTO]: 'Assistant Regional Manager', - custom: 'a custom bio', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://some-image-url', - custom: 'a custom image url', - }, - } - - const validateAttributes = await memberService.validateAttributes(attributes) - - const expectedValidatedAttributes = { - [MemberAttributeName.NAME]: { - custom: 'Dwight Schrute', - }, - [MemberAttributeName.URL]: { - custom: 'https://some-url', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Berlin', - [PlatformType.DEVTO]: 'Istanbul', - custom: 'a custom location', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Assistant to the Regional Manager', - [PlatformType.DEVTO]: 'Assistant Regional Manager', - custom: 'a custom bio', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://some-image-url', - custom: 'a custom image url', - }, - } - - expect(validateAttributes).toEqual(expectedValidatedAttributes) - }) - - it('Should throw a 400 Error when an attribute does not exist in member attribute settings', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const memberService = new MemberService(mockIServiceOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService(mockIServiceOptions) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - - // in settings name has a string type, inserting an integer should throw an error - const attributes = { - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://some-github-url', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://some-image-url', - }, - 'non-existing-attribute': { - [PlatformType.TWITTER]: 'some value', - }, - } - const validateAttributes = await memberService.validateAttributes(attributes) - - // member attribute that is non existing in settings, should be omitted after validate - const expectedValidatedAttributes = { - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://some-github-url', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://some-image-url', - }, - } - expect(validateAttributes).toEqual(expectedValidatedAttributes) - }) - - it('Should throw a 400 Error when the type of an attribute does not match the type in member attribute settings', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const memberService = new MemberService(mockIServiceOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService(mockIServiceOptions) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - - // in settings website_url has a url type, inserting an integer should throw an error - const attributes = { - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 55, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://some-github-url', - [PlatformType.TWITTER]: 'https://some-twitter-url', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://some-image-url', - }, - } - - await expect(() => memberService.validateAttributes(attributes)).rejects.toThrowError( - new Error400('en', 'settings.memberAttributes.wrongType'), - ) - }) - }) - describe('setAttributesDefaultValues method', () => { - it('Should return the structured attributes object with default values succesfully', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const memberService = new MemberService(mockIServiceOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService(mockIServiceOptions) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(DEVTO_MEMBER_ATTRIBUTES) - - const attributes = { - [MemberAttributeName.NAME]: { - [PlatformType.DEVTO]: 'Dweet Srute', - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://some-github-url', - [PlatformType.TWITTER]: 'https://some-twitter-url', - [PlatformType.DEVTO]: 'https://some-devto-url', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Berlin', - [PlatformType.DEVTO]: 'Istanbul', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Assistant to the Regional Manager', - [PlatformType.DEVTO]: 'Assistant Regional Manager', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://some-image-url', - }, - } - - const attributesWithDefaultValues = await memberService.setAttributesDefaultValues(attributes) - - // Default platform priority is: custom, twitter, github, devto, slack, discord, crowd - const expectedAttributesWithDefaultValues = { - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.URL][PlatformType.GITHUB], - [PlatformType.TWITTER]: attributes[MemberAttributeName.URL][PlatformType.TWITTER], - [PlatformType.DEVTO]: attributes[MemberAttributeName.URL][PlatformType.DEVTO], - default: attributes[MemberAttributeName.URL][PlatformType.TWITTER], - }, - [MemberAttributeName.NAME]: { - [PlatformType.DEVTO]: attributes[MemberAttributeName.NAME][PlatformType.DEVTO], - default: attributes[MemberAttributeName.NAME][PlatformType.DEVTO], - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: attributes[MemberAttributeName.AVATAR_URL][PlatformType.TWITTER], - default: attributes[MemberAttributeName.AVATAR_URL][PlatformType.TWITTER], - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.BIO][PlatformType.GITHUB], - [PlatformType.DEVTO]: attributes[MemberAttributeName.BIO][PlatformType.DEVTO], - default: attributes[MemberAttributeName.BIO][PlatformType.GITHUB], - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: attributes[MemberAttributeName.LOCATION][PlatformType.GITHUB], - [PlatformType.DEVTO]: attributes[MemberAttributeName.LOCATION][PlatformType.DEVTO], - default: attributes[MemberAttributeName.LOCATION][PlatformType.GITHUB], - }, - } - - expect(attributesWithDefaultValues).toEqual(expectedAttributesWithDefaultValues) - }) - - it('Should throw a 400 Error when priority array does not exist', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const memberService = new MemberService(mockIServiceOptions) - const memberAttributeSettingsService = new MemberAttributeSettingsService(mockIServiceOptions) - - await memberAttributeSettingsService.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - await memberAttributeSettingsService.createPredefined(DEVTO_MEMBER_ATTRIBUTES) - - // Empty default priority array - const settings = await SettingsRepository.findOrCreateDefault({}, mockIServiceOptions) - - await SettingsRepository.save( - { ...settings, attributeSettings: { priorities: [] } }, - mockIServiceOptions, - ) - const attributes = { - [MemberAttributeName.NAME]: { - [PlatformType.DEVTO]: 'Dweet Srute', - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://some-github-url', - [PlatformType.TWITTER]: 'https://some-twitter-url', - [PlatformType.DEVTO]: 'https://some-devto-url', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Berlin', - [PlatformType.DEVTO]: 'Istanbul', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Assistant to the Regional Manager', - [PlatformType.DEVTO]: 'Assistant Regional Manager', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://some-image-url', - }, - } - - await expect(() => memberService.setAttributesDefaultValues(attributes)).rejects.toThrowError( - new Error400('en', 'settings.memberAttributes.priorityArrayNotFound'), - ) - }) - }) - - describe('findAndCountAll method', () => { - it('Should filter and sort by dynamic attributes using advanced filters successfully', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - - const ms = new MemberService(mockIServiceOptions) - - const mas = new MemberAttributeSettingsService(mockIServiceOptions) - - await mas.createPredefined(GITHUB_MEMBER_ATTRIBUTES) - await mas.createPredefined(TWITTER_MEMBER_ATTRIBUTES) - await mas.createPredefined(DISCORD_MEMBER_ATTRIBUTES) - - const attribute1 = { - name: 'aNumberAttribute', - label: 'A number Attribute', - type: MemberAttributeType.NUMBER, - canDelete: true, - show: true, - } - - const attribute2 = { - name: 'aDateAttribute', - label: 'A date Attribute', - type: MemberAttributeType.DATE, - canDelete: true, - show: true, - } - - const attribute3 = { - name: 'aMultiSelectAttribute', - label: 'A multi select Attribute', - options: ['a', 'b', 'c'], - type: MemberAttributeType.MULTI_SELECT, - canDelete: true, - show: true, - } - - await mas.create(attribute1) - await mas.create(attribute2) - await mas.create(attribute3) - - const member1 = { - username: { - [PlatformType.GITHUB]: 'anil', - [PlatformType.DISCORD]: 'anil', - [PlatformType.TWITTER]: 'anil', - }, - platform: PlatformType.GITHUB, - emails: ['lala@l.com'], - score: 10, - attributes: { - aDateAttribute: { - custom: '2022-08-01T00:00:00', - }, - aMultiSelectAttribute: { - custom: ['a', 'b'], - github: ['a'], - }, - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: false, - [PlatformType.DISCORD]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/anil', - [PlatformType.TWITTER]: 'https://twitter.com/anil', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://imcvampire.js.org/', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Lazy geek', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Helsinki, Finland', - }, - [MemberAttributeName.SOURCE_ID]: { - [PlatformType.TWITTER]: '#twitterId2', - [PlatformType.DISCORD]: '#discordId1', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://twitter.com/anil/image', - }, - aNumberAttribute: { - [PlatformType.GITHUB]: 1, - [PlatformType.TWITTER]: 2, - [PlatformType.DISCORD]: 300000, - }, - }, - contributions: [ - { - id: 112529473, - url: 'https://github.com/bighead/silicon-valley', - topics: ['TV Shows', 'Comedy', 'Startups'], - summary: 'Silicon Valley: 50 commits in 2 weeks', - numberCommits: 50, - lastCommitDate: '02/01/2023', - firstCommitDate: '01/17/2023', - }, - { - id: 112529474, - url: 'https://github.com/bighead/startup-ideas', - topics: ['Ideas', 'Startups'], - summary: 'Startup Ideas: 20 commits in 1 week', - numberCommits: 20, - lastCommitDate: '03/01/2023', - firstCommitDate: '02/22/2023', - }, - ], - joinedAt: '2022-05-28T15:13:30', - } - - const member2 = { - username: { - [PlatformType.GITHUB]: 'michaelScott', - [PlatformType.DISCORD]: 'michaelScott', - [PlatformType.TWITTER]: 'michaelScott', - }, - platform: PlatformType.GITHUB, - emails: ['michael@mifflin.com'], - score: 10, - attributes: { - aDateAttribute: { - custom: '2022-08-06T00:00:00', - }, - aMultiSelectAttribute: { - custom: ['b', 'c'], - github: ['b'], - }, - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: true, - [PlatformType.DISCORD]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/michael-scott', - [PlatformType.TWITTER]: 'https://twitter.com/michael', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://website/michael', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Dunder & Mifflin Regional Manager', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Berlin', - }, - [MemberAttributeName.SOURCE_ID]: { - [PlatformType.TWITTER]: '#twitterId2', - [PlatformType.DISCORD]: '#discordId2', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://twitter.com/michael/image', - }, - aNumberAttribute: { - [PlatformType.GITHUB]: 1500, - [PlatformType.TWITTER]: 2500, - [PlatformType.DISCORD]: 2, - }, - }, - contributions: [ - { - id: 112529472, - url: 'https://github.com/bachman/pied-piper', - topics: ['compression', 'data', 'middle-out', 'Java'], - summary: 'Pied Piper: 10 commits in 1 day', - numberCommits: 10, - lastCommitDate: '2023-03-10', - firstCommitDate: '2023-03-01', - }, - { - id: 112529473, - url: 'https://github.com/bachman/aviato', - topics: ['Python', 'Django'], - summary: 'Aviato: 5 commits in 1 day', - numberCommits: 5, - lastCommitDate: '2023-02-25', - firstCommitDate: '2023-02-20', - }, - { - id: 112529476, - url: 'https://github.com/bachman/erlichbot', - topics: ['Python', 'Slack API'], - summary: 'ErlichBot: 2 commits in 1 day', - numberCommits: 2, - lastCommitDate: '2023-01-25', - firstCommitDate: '2023-01-24', - }, - ], - joinedAt: '2022-09-15T15:13:30', - } - - const member3 = { - username: { - [PlatformType.GITHUB]: 'jimHalpert', - [PlatformType.DISCORD]: 'jimHalpert', - [PlatformType.TWITTER]: 'jimHalpert', - }, - platform: PlatformType.GITHUB, - emails: ['jim@mifflin.com'], - score: 10, - attributes: { - aDateAttribute: { - custom: '2022-08-15T00:00:00', - }, - aMultiSelectAttribute: { - custom: ['a', 'c'], - github: ['c'], - }, - [MemberAttributeName.IS_HIREABLE]: { - [PlatformType.GITHUB]: false, - [PlatformType.DISCORD]: true, - }, - [MemberAttributeName.URL]: { - [PlatformType.GITHUB]: 'https://github.com/jim-halpert', - [PlatformType.TWITTER]: 'https://twitter.com/jim', - }, - [MemberAttributeName.WEBSITE_URL]: { - [PlatformType.GITHUB]: 'https://website/jim', - }, - [MemberAttributeName.BIO]: { - [PlatformType.GITHUB]: 'Sales guy', - }, - [MemberAttributeName.LOCATION]: { - [PlatformType.GITHUB]: 'Scranton', - }, - [MemberAttributeName.SOURCE_ID]: { - [PlatformType.TWITTER]: '#twitterId3', - [PlatformType.DISCORD]: '#discordId3', - }, - [MemberAttributeName.AVATAR_URL]: { - [PlatformType.TWITTER]: 'https://twitter.com/jim/image', - }, - aNumberAttribute: { - [PlatformType.GITHUB]: 15500, - [PlatformType.TWITTER]: 25500, - [PlatformType.DISCORD]: 200000, - }, - }, - joinedAt: '2022-09-16T15:13:30Z', - } - - const member1Created = await ms.upsert(member1) - const member2Created = await ms.upsert(member2) - const member3Created = await ms.upsert(member3) - - await SequelizeTestUtils.refreshMaterializedViews(db) - - // filter and sort by aNumberAttribute default values - let members = await ms.findAndCountAll({ - advancedFilter: { - aNumberAttribute: { - gte: 1000, - }, - }, - orderBy: 'aNumberAttribute_DESC', - }) - - expect(members.count).toBe(2) - expect(members.rows.map((i) => i.id)).toStrictEqual([member3Created.id, member2Created.id]) - - // filter and sort by aNumberAttribute platform specific values - members = await ms.findAndCountAll({ - advancedFilter: { - 'attributes.aNumberAttribute.discord': { - gte: 100000, - }, - }, - orderBy: 'attributes.aNumberAttribute.discord_DESC', - }) - - expect(members.count).toBe(2) - expect(members.rows.map((i) => i.id)).toStrictEqual([member1Created.id, member3Created.id]) - - // filter by isHireable default values - members = await ms.findAndCountAll({ - advancedFilter: { - isHireable: true, - }, - }) - - expect(members.count).toBe(1) - expect(members.rows.map((i) => i.id)).toStrictEqual([member2Created.id]) - - // filter by isHireable platform specific values - members = await ms.findAndCountAll({ - advancedFilter: { - 'attributes.isHireable.discord': true, - }, - }) - - expect(members.count).toBe(3) - expect(members.rows.map((i) => i.id)).toStrictEqual([ - member3Created.id, - member2Created.id, - member1Created.id, - ]) - - // filter and sort by url default values - members = await ms.findAndCountAll({ - advancedFilter: { - url: { - textContains: 'jim', - }, - }, - orderBy: 'url_DESC', - }) - - expect(members.count).toBe(1) - expect(members.rows.map((i) => i.id)).toStrictEqual([member3Created.id]) - - // filter and sort by url platform specific values - members = await ms.findAndCountAll({ - advancedFilter: { - 'attributes.url.github': { - textContains: 'github', - }, - }, - orderBy: 'attributes.url.github_ASC', - }) - - expect(members.count).toBe(3) - - // results will be sorted by github.url anil -> jim -> michael - expect(members.rows.map((i) => i.id)).toStrictEqual([ - member1Created.id, - member3Created.id, - member2Created.id, - ]) - - // filter and sort by custom aDateAttribute - members = await ms.findAndCountAll({ - advancedFilter: { - aDateAttribute: { - lte: '2022-08-06T00:00:00', - }, - }, - orderBy: 'aDateAttribute_DESC', - }) - - expect(members.count).toBe(2) - expect(members.rows.map((i) => i.id)).toStrictEqual([member2Created.id, member1Created.id]) - - // filter by custom aMultiSelectAttribute - members = await ms.findAndCountAll({ - advancedFilter: { - aMultiSelectAttribute: { - overlap: ['a'], - }, - }, - orderBy: 'createdAt_DESC', - }) - expect(members.count).toBe(2) - expect(members.rows.map((i) => i.id)).toStrictEqual([member3Created.id, member1Created.id]) - - // filter by numberOfOpenSourceContributions - members = await ms.findAndCountAll({ - filter: { - numberOfOpenSourceContributionsRange: [2, 6], - }, - }) - expect(members.count).toBe(2) - expect(members.rows.map((i) => i.id)).toEqual([member2Created.id, member1Created.id]) - - // filter by numberOfOpenSourceContributions only start - members = await ms.findAndCountAll({ - filter: { - numberOfOpenSourceContributionsRange: [3], - }, - }) - expect(members.count).toBe(1) - expect(members.rows.map((i) => i.id)).toStrictEqual([member2Created.id]) - - // filter and sort by numberOfOpenSourceContributions - members = await ms.findAndCountAll({ - filter: { - numberOfOpenSourceContributionsRange: [2, 6], - }, - orderBy: 'numberOfOpenSourceContributions_ASC', - }) - expect(members.count).toBe(2) - expect(members.rows.map((i) => i.id)).toStrictEqual([member1Created.id, member2Created.id]) - - // sort by numberOfOpenSourceContributions - members = await ms.findAndCountAll({ - orderBy: 'numberOfOpenSourceContributions_ASC', - }) - expect(members.count).toBe(3) - expect(members.rows.map((i) => i.id)).toStrictEqual([ - member3Created.id, - member1Created.id, - member2Created.id, - ]) - }) - }) -}) diff --git a/backend/src/services/memberAffiliationService.ts b/backend/src/services/memberAffiliationService.ts index 28f29b5127..e41484e924 100644 --- a/backend/src/services/memberAffiliationService.ts +++ b/backend/src/services/memberAffiliationService.ts @@ -1,8 +1,9 @@ import { LoggerBase } from '@crowd/logging' +import { TemporalWorkflowId } from '@crowd/types' +import { WorkflowIdReusePolicy } from '@crowd/temporal' import { IServiceOptions } from './IServiceOptions' import MemberSegmentAffiliationRepository from '../database/repositories/memberSegmentAffiliationRepository' import MemberRepository from '../database/repositories/memberRepository' -import MemberAffiliationRepository from '../database/repositories/memberAffiliationRepository' export default class MemberAffiliationService extends LoggerBase { options: IServiceOptions @@ -53,6 +54,23 @@ export default class MemberAffiliationService extends LoggerBase { } async updateAffiliation(memberId: string) { - await MemberAffiliationRepository.update(memberId, this.options) + await this.options.temporal.workflow.start('memberUpdate', { + taskQueue: 'profiles', + workflowId: `${TemporalWorkflowId.MEMBER_UPDATE}/${this.options.currentTenant.id}/${memberId}`, + workflowIdReusePolicy: WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING, + retry: { + maximumAttempts: 10, + }, + args: [ + { + member: { + id: memberId, + }, + }, + ], + searchAttributes: { + TenantId: [this.options.currentTenant.id], + }, + }) } } diff --git a/backend/src/services/memberService.ts b/backend/src/services/memberService.ts index 615a056620..09b7ce508d 100644 --- a/backend/src/services/memberService.ts +++ b/backend/src/services/memberService.ts @@ -1000,6 +1000,24 @@ export default class MemberService extends LoggerBase { const record = await MemberRepository.update(id, data, repoOptions) await SequelizeRepository.commitTransaction(transaction) + await this.options.temporal.workflow.start('memberUpdate', { + taskQueue: 'profiles', + workflowId: `${TemporalWorkflowId.MEMBER_UPDATE}/${this.options.currentTenant.id}/${id}`, + workflowIdReusePolicy: WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING, + retry: { + maximumAttempts: 10, + }, + args: [ + { + member: { + id, + }, + }, + ], + searchAttributes: { + TenantId: [this.options.currentTenant.id], + }, + }) if (syncToOpensearch) { try { diff --git a/backend/src/services/premium/enrichment/memberEnrichmentService.ts b/backend/src/services/premium/enrichment/memberEnrichmentService.ts index d59af509be..62f6e2a7b9 100644 --- a/backend/src/services/premium/enrichment/memberEnrichmentService.ts +++ b/backend/src/services/premium/enrichment/memberEnrichmentService.ts @@ -14,6 +14,7 @@ import { OrganizationSource, SyncMode, IOrganizationIdentity, + TemporalWorkflowId, } from '@crowd/types' import { EnrichmentAPICertification, @@ -24,6 +25,7 @@ import { EnrichmentAPISkills, EnrichmentAPIWorkExperience, } from '@crowd/types/premium' +import { WorkflowIdReusePolicy } from '@crowd/temporal' import { ENRICHMENT_CONFIG, REDIS_CONFIG } from '../../../conf' import { AttributeData } from '../../../database/attributes/attribute' import MemberEnrichmentCacheRepository from '../../../database/repositories/memberEnrichmentCacheRepository' @@ -389,6 +391,28 @@ export default class MemberEnrichmentService extends LoggerBase { } await SequelizeRepository.commitTransaction(transaction) + if (enrichmentData.work_experiences) { + await this.options.temporal.workflow.start('memberUpdate', { + taskQueue: 'profiles', + workflowId: `${TemporalWorkflowId.MEMBER_UPDATE}/${this.options.currentTenant.id}/${result.id}`, + workflowIdReusePolicy: + WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING, + retry: { + maximumAttempts: 10, + }, + args: [ + { + member: { + id: result.id, + }, + }, + ], + searchAttributes: { + TenantId: [this.options.currentTenant.id], + }, + }) + } + await searchSyncService.triggerMemberSync(this.options.currentTenant.id, result.id) result = await MemberRepository.findByIdOpensearch(result.id, this.options) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b296b28a4..e0f56fac4b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1217,6 +1217,58 @@ importers: specifier: ^3.0.3 version: 3.1.0 + services/apps/profiles_worker: + dependencies: + '@crowd/archetype-standard': + specifier: file:../../archetypes/standard + version: file:services/archetypes/standard(@swc/core@1.3.100)(@types/node@20.10.1)(typescript@5.3.2) + '@crowd/archetype-worker': + specifier: file:../../archetypes/worker + version: file:services/archetypes/worker(@swc/core@1.3.100)(@types/node@20.10.1)(typescript@5.3.2) + '@crowd/types': + specifier: file:../../libs/types + version: file:services/libs/types + '@temporalio/workflow': + specifier: ~1.8.6 + version: 1.8.6 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@swc/core@1.3.100)(@types/node@20.10.1)(typescript@5.3.2) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.2.2 + version: 5.3.2 + devDependencies: + '@types/node': + specifier: ^20.8.2 + version: 20.10.1 + '@types/uuid': + specifier: ~9.0.6 + version: 9.0.7 + '@typescript-eslint/eslint-plugin': + specifier: ^6.7.4 + version: 6.13.1(@typescript-eslint/parser@6.13.1)(eslint@8.54.0)(typescript@5.3.2) + '@typescript-eslint/parser': + specifier: ^6.7.4 + version: 6.13.1(eslint@8.54.0)(typescript@5.3.2) + eslint: + specifier: ^8.50.0 + version: 8.54.0 + eslint-config-prettier: + specifier: ^9.0.0 + version: 9.0.0(eslint@8.54.0) + eslint-plugin-prettier: + specifier: ^5.0.0 + version: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.54.0)(prettier@3.1.0) + nodemon: + specifier: ^3.0.1 + version: 3.0.1 + prettier: + specifier: ^3.0.3 + version: 3.1.0 + services/apps/search_sync_api: dependencies: '@crowd/common': diff --git a/scripts/builders/profiles-worker.sh b/scripts/builders/profiles-worker.sh new file mode 100644 index 0000000000..83688bf071 --- /dev/null +++ b/scripts/builders/profiles-worker.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +DOCKERFILE="./services/docker/Dockerfile.profiles_worker" +CONTEXT="../" +REPO="crowddotdev/profiles-worker" \ No newline at end of file diff --git a/scripts/services/docker/Dockerfile.profiles_worker b/scripts/services/docker/Dockerfile.profiles_worker new file mode 100644 index 0000000000..ddcf8631f4 --- /dev/null +++ b/scripts/services/docker/Dockerfile.profiles_worker @@ -0,0 +1,18 @@ +FROM node:16-alpine as builder + +WORKDIR /usr/crowd/app +RUN corepack enable && apk add --update --no-cache python3 build-base && ln -sf python3 /usr/bin/python && python3 -m ensurepip && pip3 install --no-cache --upgrade pip setuptools + +COPY ./pnpm-workspace.yaml ./pnpm-lock.yaml ./ +COPY ./services ./services +RUN pnpm i --frozen-lockfile + +FROM node:16-bookworm-slim as runner + +WORKDIR /usr/crowd/app +RUN corepack enable && apt update && apt install -y ca-certificates --no-install-recommends && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/crowd/app/node_modules ./node_modules +COPY --from=builder /usr/crowd/app/services/libs ./services/libs +COPY --from=builder /usr/crowd/app/services/archetypes/ ./services/archetypes +COPY --from=builder /usr/crowd/app/services/apps/profiles_worker/ ./services/apps/profiles_worker diff --git a/scripts/services/profiles-worker.yaml b/scripts/services/profiles-worker.yaml new file mode 100644 index 0000000000..8410b1aec5 --- /dev/null +++ b/scripts/services/profiles-worker.yaml @@ -0,0 +1,64 @@ +version: '3.1' + +x-env-args: &env-args + DOCKER_BUILDKIT: 1 + NODE_ENV: docker + SERVICE: profiles-worker + CROWD_TEMPORAL_TASKQUEUE: profiles + SHELL: /bin/sh + +services: + profiles-worker: + build: + context: ../../ + dockerfile: ./scripts/services/docker/Dockerfile.profiles_worker + command: 'pnpm run start' + working_dir: /usr/crowd/app/services/apps/profiles_worker + env_file: + - ../../backend/.env.dist.local + - ../../backend/.env.dist.composed + - ../../backend/.env.override.local + - ../../backend/.env.override.composed + environment: + <<: *env-args + restart: always + networks: + - crowd-bridge + + profiles-worker-dev: + build: + context: ../../ + dockerfile: ./scripts/services/docker/Dockerfile.profiles_worker + command: 'pnpm run dev' + working_dir: /usr/crowd/app/services/apps/profiles_worker + # user: '${USER_ID}:${GROUP_ID}' + env_file: + - ../../backend/.env.dist.local + - ../../backend/.env.dist.composed + - ../../backend/.env.override.local + - ../../backend/.env.override.composed + environment: + <<: *env-args + hostname: profiles-worker + networks: + - crowd-bridge + volumes: + - ../../services/libs/alerting/src:/usr/crowd/app/services/libs/alerting/src + - ../../services/libs/common/src:/usr/crowd/app/services/libs/common/src + - ../../services/libs/common_services/src:/usr/crowd/app/services/libs/common_services/src + - ../../services/libs/conversations/src:/usr/crowd/app/services/libs/conversations/src + - ../../services/libs/cubejs/src:/usr/crowd/app/services/libs/cubejs/src + - ../../services/libs/database/src:/usr/crowd/app/services/libs/database/src + - ../../services/libs/integrations/src:/usr/crowd/app/services/libs/integrations/src + - ../../services/libs/ioc/src:/usr/crowd/app/services/libs/ioc/src + - ../../services/libs/logging/src:/usr/crowd/app/services/libs/logging/src + - ../../services/libs/opensearch/src:/usr/crowd/app/services/libs/opensearch/src + - ../../services/libs/redis/src:/usr/crowd/app/services/libs/redis/src + - ../../services/libs/sentiment/src:/usr/crowd/app/services/libs/sentiment/src + - ../../services/libs/sqs/src:/usr/crowd/app/services/libs/sqs/src + - ../../services/libs/types/src:/usr/crowd/app/services/libs/types/src + - ../../services/apps/profiles_worker/src:/usr/crowd/app/services/apps/profiles_worker/src + +networks: + crowd-bridge: + external: true diff --git a/services/apps/profiles_worker/.eslintrc.cjs b/services/apps/profiles_worker/.eslintrc.cjs new file mode 100644 index 0000000000..25ba4e0d66 --- /dev/null +++ b/services/apps/profiles_worker/.eslintrc.cjs @@ -0,0 +1,21 @@ +module.exports = { + parser: '@typescript-eslint/parser', + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + plugins: ['@typescript-eslint', 'prettier'], + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, + env: { + es6: true, + node: true, + }, + rules: { + 'prettier/prettier': 'error', + '@typescript-eslint/explicit-module-boundary-types': 'off', + }, +} diff --git a/services/apps/profiles_worker/.prettierignore b/services/apps/profiles_worker/.prettierignore new file mode 100644 index 0000000000..2b24877309 --- /dev/null +++ b/services/apps/profiles_worker/.prettierignore @@ -0,0 +1,3 @@ +dist/ +.eslintrc.cjs +.prettierrc.cjs diff --git a/services/apps/profiles_worker/.prettierrc.cjs b/services/apps/profiles_worker/.prettierrc.cjs new file mode 100644 index 0000000000..a8ca459839 --- /dev/null +++ b/services/apps/profiles_worker/.prettierrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + singleQuote: true, + arrowParens: 'always', + printWidth: 100, + trailingComma: 'all', + semi: false, +} diff --git a/services/apps/profiles_worker/package.json b/services/apps/profiles_worker/package.json new file mode 100644 index 0000000000..f28772b071 --- /dev/null +++ b/services/apps/profiles_worker/package.json @@ -0,0 +1,36 @@ +{ + "name": "@crowd/profiles-worker", + "version": "1.0.0", + "private": true, + "scripts": { + "start": "CROWD_TEMPORAL_TASKQUEUE=profiles SERVICE=profiles-worker TS_NODE_TRANSPILE_ONLY=true node -r tsconfig-paths/register -r ts-node/register src/main.ts", + "start:debug:local": "set -a && . ../../../backend/.env.dist.local && . ../../../backend/.env.override.local && set +a && CROWD_TEMPORAL_TASKQUEUE=profiles SERVICE=profiles-worker TS_NODE_TRANSPILE_ONLY=true LOG_LEVEL=trace node --inspect=0.0.0.0:9232 -r tsconfig-paths/register -r ts-node/register src/main.ts", + "start:debug": "CROWD_TEMPORAL_TASKQUEUE=profiles SERVICE=profiles-worker TS_NODE_TRANSPILE_ONLY=true LOG_LEVEL=trace node --inspect=0.0.0.0:9232 -r tsconfig-paths/register -r ts-node/register src/main.ts", + "dev:local": "./node_modules/.bin/nodemon --watch src --watch ../../libs --ext ts --exec pnpm run start:debug:local", + "dev": "./node_modules/.bin/nodemon --watch src --watch ../../libs --ext ts --exec pnpm run start:debug", + "lint": "./node_modules/.bin/eslint --ext .ts src --max-warnings=0", + "format": "./node_modules/.bin/prettier --write \"src/**/*.ts\"", + "format-check": "./node_modules/.bin/prettier --check .", + "tsc-check": "./node_modules/.bin/tsc --noEmit" + }, + "dependencies": { + "@crowd/archetype-standard": "file:../../archetypes/standard", + "@crowd/archetype-worker": "file:../../archetypes/worker", + "@crowd/types": "file:../../libs/types", + "@temporalio/workflow": "~1.8.6", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.2.2" + }, + "devDependencies": { + "@types/node": "^20.8.2", + "@types/uuid": "~9.0.6", + "@typescript-eslint/eslint-plugin": "^6.7.4", + "@typescript-eslint/parser": "^6.7.4", + "eslint": "^8.50.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "nodemon": "^3.0.1", + "prettier": "^3.0.3" + } +} diff --git a/services/apps/profiles_worker/src/activities.ts b/services/apps/profiles_worker/src/activities.ts new file mode 100644 index 0000000000..be231964e8 --- /dev/null +++ b/services/apps/profiles_worker/src/activities.ts @@ -0,0 +1,3 @@ +import { updateMemberAffiliations } from './activities/member/memberUpdate' + +export { updateMemberAffiliations } diff --git a/services/apps/profiles_worker/src/activities/member/memberUpdate.ts b/services/apps/profiles_worker/src/activities/member/memberUpdate.ts new file mode 100644 index 0000000000..a9cf0aba47 --- /dev/null +++ b/services/apps/profiles_worker/src/activities/member/memberUpdate.ts @@ -0,0 +1,57 @@ +import { svc } from '../../main' +import { MemberWithIDOnly } from '../../types/member' + +/* +updateMemberAffiliations is a Temporal activity that updates all affiliations for +a given member. +*/ +export async function updateMemberAffiliations(input: MemberWithIDOnly): Promise { + try { + await svc.postgres.writer.connection().query( + `WITH new_activities_organizations AS ( + SELECT + a.id, + + -- this 000000 magic is to differentiate between nothing to LEFT JOIN with and real individial affiliation + -- we want to keep NULL in 'organizationId' if there is an affiliation configured, + -- but if there is no manual affiliation, we know this by 'msa.id' being NULL, and then using 000000 as a marker, + -- which we remove afterwards + (ARRAY_REMOVE(ARRAY_AGG(CASE WHEN msa.id IS NULL THEN '00000000-0000-0000-0000-000000000000' ELSE msa."organizationId" END), '00000000-0000-0000-0000-000000000000') + || ARRAY_REMOVE(ARRAY_AGG(mo."organizationId" ORDER BY mo."dateStart" DESC, mo.id), NULL) + || ARRAY_REMOVE(ARRAY_AGG(mo1."organizationId" ORDER BY mo1."createdAt" DESC, mo1.id), NULL) + || ARRAY_REMOVE(ARRAY_AGG(mo2."organizationId" ORDER BY mo2."createdAt", mo2.id), NULL) + )[1] AS new_org + FROM activities a + LEFT JOIN "memberSegmentAffiliations" msa ON msa."memberId" = a."memberId" AND a."segmentId" = msa."segmentId" AND ( + a.timestamp BETWEEN msa."dateStart" AND msa."dateEnd" + OR (a.timestamp >= msa."dateStart" AND msa."dateEnd" IS NULL) + ) + LEFT JOIN "memberOrganizations" mo ON mo."memberId" = a."memberId" + AND ( + a.timestamp BETWEEN mo."dateStart" AND mo."dateEnd" + OR (a.timestamp >= mo."dateStart" AND mo."dateEnd" IS NULL) + ) + AND mo."deletedAt" IS NULL + LEFT JOIN "memberOrganizations" mo1 ON mo1."memberId" = a."memberId" + AND mo1."dateStart" IS NULL AND mo1."dateEnd" IS NULL + AND mo1."createdAt" <= a.timestamp + AND mo1."deletedAt" IS NULL + LEFT JOIN "memberOrganizations" mo2 ON mo2."memberId" = a."memberId" + AND mo2."dateStart" IS NULL AND mo2."dateEnd" IS NULL + AND mo2."deletedAt" IS NULL + WHERE a."memberId" = $1 + GROUP BY a.id + ) + UPDATE activities a1 + SET "organizationId" = nao.new_org + FROM new_activities_organizations nao + WHERE a1.id = nao.id + AND ("organizationId" != nao.new_org + OR ("organizationId" IS NULL AND nao.new_org IS NOT NULL) + OR ("organizationId" IS NOT NULL AND nao.new_org IS NULL));`, + [input.member.id], + ) + } catch (err) { + throw new Error(err) + } +} diff --git a/services/apps/profiles_worker/src/main.ts b/services/apps/profiles_worker/src/main.ts new file mode 100644 index 0000000000..27896e9f9a --- /dev/null +++ b/services/apps/profiles_worker/src/main.ts @@ -0,0 +1,31 @@ +import { Config } from '@crowd/archetype-standard' +import { ServiceWorker, Options } from '@crowd/archetype-worker' + +const config: Config = { + producer: { + enabled: false, + }, + temporal: { + enabled: true, + }, + redis: { + enabled: false, + }, +} + +const options: Options = { + postgres: { + enabled: true, + }, + opensearch: { + enabled: false, + }, +} + +export const svc = new ServiceWorker(config, options) + +setImmediate(async () => { + await svc.init() + + await svc.start() +}) diff --git a/services/apps/profiles_worker/src/types/member.ts b/services/apps/profiles_worker/src/types/member.ts new file mode 100644 index 0000000000..74e210924d --- /dev/null +++ b/services/apps/profiles_worker/src/types/member.ts @@ -0,0 +1,5 @@ +export interface MemberWithIDOnly { + member: { + id: string + } +} diff --git a/services/apps/profiles_worker/src/workflows.ts b/services/apps/profiles_worker/src/workflows.ts new file mode 100644 index 0000000000..233f128fb7 --- /dev/null +++ b/services/apps/profiles_worker/src/workflows.ts @@ -0,0 +1,3 @@ +import { memberUpdate } from './workflows/member/memberUpdate' + +export { memberUpdate } diff --git a/services/apps/profiles_worker/src/workflows/member/memberUpdate.ts b/services/apps/profiles_worker/src/workflows/member/memberUpdate.ts new file mode 100644 index 0000000000..3c022af415 --- /dev/null +++ b/services/apps/profiles_worker/src/workflows/member/memberUpdate.ts @@ -0,0 +1,21 @@ +import { proxyActivities } from '@temporalio/workflow' + +import * as activities from '../../activities' +import { MemberWithIDOnly } from '../../types/member' + +// Configure timeouts and retry policies to update a member in the database. +const { updateMemberAffiliations } = proxyActivities({ + startToCloseTimeout: '60 seconds', +}) + +/* +memberUpdate is a Temporal workflow that: + - [Activity]: Update all affiliations for a given member in the database. +*/ +export async function memberUpdate(input: MemberWithIDOnly): Promise { + try { + await updateMemberAffiliations(input) + } catch (err) { + throw new Error(err) + } +} diff --git a/services/apps/profiles_worker/tsconfig.json b/services/apps/profiles_worker/tsconfig.json new file mode 100644 index 0000000000..47a983b5d5 --- /dev/null +++ b/services/apps/profiles_worker/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "Node16", + "lib": ["es6", "es7", "es2017", "es2017.object", "es2015.promise"], + "skipLibCheck": true, + "sourceMap": true, + "moduleResolution": "node16", + "experimentalDecorators": true, + "esModuleInterop": true, + "baseUrl": "./src", + "paths": { + "@crowd/*": ["../../../libs/*/src"], + "@crowd/archetype-*": ["../../../archetypes/*/src"] + } + }, + "include": ["src/**/*"] +} diff --git a/services/libs/types/src/enums/temporal.ts b/services/libs/types/src/enums/temporal.ts index 3dda198445..529d0af6d1 100644 --- a/services/libs/types/src/enums/temporal.ts +++ b/services/libs/types/src/enums/temporal.ts @@ -4,4 +4,6 @@ export enum TemporalWorkflowId { EMAIL_WEEKLY_ANALYTICS = 'email-weekly-analytics', EMAIL_EAGLEEYE_DIGEST = 'email-eagleeye-digest', + + MEMBER_UPDATE = 'member-update', }