+
{{ props.organization.location || '-' }}
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/migrations/U1704365919__fix-member-joined-at.sql b/backend/src/database/migrations/U1704365919__fix-member-joined-at.sql new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/src/database/migrations/V1704365919__fix-member-joined-at.sql b/backend/src/database/migrations/V1704365919__fix-member-joined-at.sql new file mode 100644 index 0000000000..ab41e713c2 --- /dev/null +++ b/backend/src/database/migrations/V1704365919__fix-member-joined-at.sql @@ -0,0 +1,36 @@ +DO $$ +DECLARE + _member_id UUID; + _first_acitivity TIMESTAMP; +BEGIN + FOR _member_id IN + SELECT id + FROM members + WHERE EXTRACT(YEAR FROM "joinedAt") = 1970 -- those who have the wrong joinedAt + AND EXISTS ( -- yet have at least one activity with a non-1970 timestamp + SELECT 1 + FROM activities a + WHERE a."memberId" = members.id + AND EXTRACT(YEAR FROM a.timestamp) != 1970 + ) + LOOP + RAISE NOTICE 'member_id: %', _member_id; + + -- find the actual first non-1970 activity timestamp + SELECT MIN(a.timestamp) INTO _first_acitivity + FROM activities a + WHERE EXTRACT(YEAR FROM a.timestamp) != 1970 + AND a."memberId" = _member_id; + + IF _first_acitivity IS NULL THEN + CONTINUE; + END IF; + + RAISE NOTICE 'first_acitivity: %', _first_acitivity; + + UPDATE members + SET "joinedAt" = _first_acitivity + WHERE id = _member_id; + END LOOP; +END; +$$; 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 HTML
', - type: 'activity', - title: 'Malicious
", - title: "Malicious
', - type: 'activity', - title: 'This is some HTML
', - title: 'This is some HTML
') - expect(updatedActivity.title).toBe('Malicious
", - title: "Malicious
') - expect(updatedActivity.title).toBe('Location
-+
{{ member.attributes.location?.default || '-' }}
@@ -117,7 +123,7 @@Title
-+
{{ member.attributes.jobTitle?.default || '-' }}
@@ -128,7 +134,7 @@Contact since
-+
{{ moment(member.joinedAt).format('YYYY-MM-DD') }}
@@ -189,6 +195,11 @@ const props = defineProps({ required: false, default: false, }, + isPreview: { + type: Boolean, + required: false, + default: false, + }, loading: { type: Boolean, required: false, diff --git a/frontend/src/modules/member/pages/member-merge-suggestions-page.vue b/frontend/src/modules/member/pages/member-merge-suggestions-page.vue index 9db1912980..38d7c9d130 100644 --- a/frontend/src/modules/member/pages/member-merge-suggestions-page.vue +++ b/frontend/src/modules/member/pages/member-merge-suggestions-page.vue @@ -99,7 +99,7 @@+
{{ props.organization.location || '-' }}
Number of employees
-+
{{ props.organization.employees || '-' }}
@@ -137,7 +143,7 @@Annual Revenue
-+
{{ revenueRange.displayValue( props.organization.revenueRange, ) || '-' }} @@ -153,7 +159,7 @@
Industry
-+
{{ props.organization.industry || '-' }}
@@ -167,7 +173,7 @@Type
-+
{{ props.organization.type || '-' }}
@@ -181,7 +187,7 @@Founded
-+
{{ props.organization.founded || '-' }}
@@ -195,7 +201,7 @@Joined date
-+
{{ formatDateToTimeAgo(props.organization.joinedAt) || '-' }}
@@ -209,7 +215,7 @@# of contacts
-+
{{ props.organization.memberCount || '-' }}
@@ -223,7 +229,7 @@# of Activities
-+
{{ props.organization.activityCount || '-' }}
@@ -269,6 +275,11 @@ const props = defineProps({ required: false, default: false, }, + isPreview: { + type: Boolean, + required: false, + default: false, + }, loading: { type: Boolean, required: false, diff --git a/frontend/src/modules/organization/pages/organization-merge-suggestions-page.vue b/frontend/src/modules/organization/pages/organization-merge-suggestions-page.vue index 92d0deb431..0376c3cb0d 100644 --- a/frontend/src/modules/organization/pages/organization-merge-suggestions-page.vue +++ b/frontend/src/modules/organization/pages/organization-merge-suggestions-page.vue @@ -99,7 +99,7 @@