Skip to content

Commit

Permalink
Add 3 basic checks for data issues
Browse files Browse the repository at this point in the history
  • Loading branch information
gaspergrom committed Oct 24, 2024
1 parent ff46371 commit 2d06e22
Show file tree
Hide file tree
Showing 23 changed files with 758 additions and 50 deletions.
1 change: 1 addition & 0 deletions backend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ setImmediate(async () => {
require('./customViews').default(routes)
require('./dashboard').default(routes)
require('./mergeAction').default(routes)
require('./dataQuality').default(routes)
// Loads the Tenant if the :tenantId param is passed
routes.param('tenantId', tenantMiddleware)
routes.param('tenantId', segmentMiddleware)
Expand Down
19 changes: 16 additions & 3 deletions backend/src/database/repositories/dataQualityRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,47 @@ import { IRepositoryOptions } from './IRepositoryOptions'
import SequelizeRepository from '@/database/repositories/sequelizeRepository'

class DataQualityRepository {
/**
* Finds members with no work experience.
*
* @param {IRepositoryOptions} options - The repository options for executing the query.
* @param {string} tenantId - The ID of the tenant.
* @param {number} limit - The maximum number of results to return.
* @param {number} offset - The offset from which to begin returning results.
* @param {string} segmentId - The ID of the segment.
* @return {Promise<Members[]>} - A promise that resolves with an array of members having no work experience.
*/
static async findMembersWithNoWorkExperience(
options: IRepositoryOptions,
tenantId: string,
limit: number,
offset: number,
segmentId: string,
) {
const qx = SequelizeRepository.getQueryExecutor(options)
return fetchMembersWithoutWorkExperience(qx, tenantId, limit, offset)
return fetchMembersWithoutWorkExperience(qx, tenantId, limit, offset, segmentId)
}

static async findMembersWithTooManyIdentities(
options: IRepositoryOptions,
tenantId: string,
limit: number,
offset: number,
segmentId: string,
) {
const qx = SequelizeRepository.getQueryExecutor(options)
return fetchMembersWithTooManyIdentities(qx, 10, tenantId, limit, offset)
return fetchMembersWithTooManyIdentities(qx, 10, tenantId, limit, offset, segmentId)
}

static async findMembersWithTooManyIdentitiesPerPlatform(
options: IRepositoryOptions,
tenantId: string,
limit: number,
offset: number,
segmentId: string,
) {
const qx = SequelizeRepository.getQueryExecutor(options)
return fetchMembersWithTooManyIdentitiesPerPlatform(qx, 1, tenantId, limit, offset)
return fetchMembersWithTooManyIdentitiesPerPlatform(qx, 1, tenantId, limit, offset, segmentId)
}
}

Expand Down
10 changes: 7 additions & 3 deletions backend/src/services/dataQualityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ export default class DataQualityService extends LoggerBase {
this.options = options
}

async findMemberIssues(tenantId: string, params: IDataQualityParams, _: string) {
async findMemberIssues(tenantId: string, params: IDataQualityParams, segmentId: string) {
if (params.type === IDataQualityType.NO_WORK_EXPERIENCE) {
return DataQualityRepository.findMembersWithNoWorkExperience(
this.options,
tenantId,
params.limit || 10,
params.offset || 0,
segmentId,
)
}
if (params.type === IDataQualityType.MORE_THAN_10_IDENTITIES) {
Expand All @@ -27,6 +28,7 @@ export default class DataQualityService extends LoggerBase {
tenantId,
params.limit || 10,
params.offset || 0,
segmentId,
)
}
if (params.type === IDataQualityType.MORE_THAN_1_IDENTITY_PER_PLATFORM) {
Expand All @@ -35,12 +37,14 @@ export default class DataQualityService extends LoggerBase {
tenantId,
params.limit || 10,
params.offset || 0,
segmentId,
)
}
return []
}

async findOrganizationIssues(tenantId: string, params: IDataQualityParams, _: string) {
return []
// eslint-disable-next-line class-methods-use-this
async findOrganizationIssues() {
return Promise.resolve([])
}
}
10 changes: 10 additions & 0 deletions frontend/config/styles/components/badge.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,14 @@
--lf-badge-tertiary-background: var(--lf-color-gray-100);
--lf-badge-tertiary-border: var(--lf-color-gray-200);
--lf-badge-tertiary-text: var(--lf-color-gray-900);

/* Danger */
--lf-badge-danger-background: var(--lf-color-red-100);
--lf-badge-danger-border: var(--lf-color-red-200);
--lf-badge-danger-text: var(--lf-color-red-800);

/* Warning */
--lf-badge-warning-background: var(--lf-color-yellow-100);
--lf-badge-warning-border: var(--lf-color-yellow-200);
--lf-badge-warning-text: var(--lf-color-yellow-800);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,56 @@
<template>
<div>
Member
<div class="flex">
<div class="border border-gray-200 rounded-lg p-0.5 flex gap-1 mb-6">
<lf-button
type="secondary-ghost"
:class="tab === 'merge-suggestions' ? '!font-semibold !bg-gray-200' : '!font-normal'"
@click="tab = 'merge-suggestions'"
>
Merge suggestions
</lf-button>
<lf-button
type="secondary-ghost"
:class="tab === 'identities' ? '!font-semibold !bg-gray-200' : '!font-normal'"
@click="tab = 'identities'"
>
Identities
</lf-button>
<lf-button
type="secondary-ghost"
:class="tab === 'work-history' ? '!font-semibold !bg-gray-200' : '!font-normal'"
@click="tab = 'work-history'"
>
Work history
</lf-button>
</div>
</div>
<div class="border-b border-gray-200 w-full mb-1" />
<div>
<lf-data-quality-member-merge-suggestions
v-if="tab === 'merge-suggestions'"
/>
<lf-data-quality-member-issues
v-else-if="tab === 'identities'"
:types="[DataIssueType.MORE_THAN_10_IDENTITIES, DataIssueType.MORE_THAN_1_IDENTITY_PER_PLATFORM]"
/>
<lf-data-quality-member-issues
v-else-if="tab === 'work-history'"
:types="[DataIssueType.NO_WORK_EXPERIENCE]"
/>
</div>
</div>
</template>

<script lang="ts" setup>
import LfButton from '@/ui-kit/button/Button.vue';
import { ref } from 'vue';
import LfDataQualityMemberMergeSuggestions
from '@/modules/data-quality/components/member/data-quality-member-merge-suggestions.vue';
import LfDataQualityMemberIssues from '@/modules/data-quality/components/member/data-quality-member-issues.vue';
import { DataIssueType } from '@/modules/data-quality/types/DataIssueType';
const tab = ref('merge-suggestions');
</script>

<script lang="ts">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
<template>
<div>
Organization
<div class="flex">
<div class="border border-gray-200 rounded-lg p-0.5 flex gap-1 mb-6">
<lf-button
type="secondary-ghost"
:class="tab === 'merge-suggestions' ? '!font-semibold !bg-gray-200' : '!font-normal'"
@click="tab = 'merge-suggestions'"
>
Merge suggestions
</lf-button>
</div>
</div>
<div class="border-b border-gray-200 w-full mb-1" />
<div>
<lf-data-quality-organization-merge-suggestions v-if="tab === 'merge-suggestions'" />
</div>
</div>
</template>

<script lang="ts" setup>
import LfButton from '@/ui-kit/button/Button.vue';
import { ref } from 'vue';
import LfDataQualityOrganizationMergeSuggestions
from '@/modules/data-quality/components/organization/data-quality-organization-merge-suggestions.vue';
const tab = ref('merge-suggestions');
</script>

<script lang="ts">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<template>
<article class="border-b border-gray-100 py-5">
<div class="flex justify-between items-center">
<div class="flex items-center gap-3">
<lf-avatar
:src="avatar(props.member)"
:name="props.member.displayName"
:size="32"
/>
<p class="text-medium font-semibold">
{{ props.member.displayName }}
</p>
<lf-badge size="small" :type="config.badgeType" class="!font-semibold">
{{ config.badgeText(props.member) }}
</lf-badge>
</div>
<router-link
:to="{
name: 'memberView',
params: { id: props.member.id },
query: { projectGroup: selectedProjectGroup?.id },
}"
target="_blank"
>
<lf-button type="secondary" size="small" @click="isModalOpen = true; detailsOffset = si">
<lf-icon name="external-link-line" />Review profile
</lf-button>
</router-link>
</div>
<p class="text-small mt-2 text-gray-500" v-html="$sanitize(config.description(props.member))" />
</article>
</template>
<script lang="ts" setup>
import LfAvatar from '@/ui-kit/avatar/Avatar.vue';
import { Contributor } from '@/modules/contributor/types/Contributor';
import useContributorHelpers from '@/modules/contributor/helpers/contributor.helpers';
import LfBadge from '@/ui-kit/badge/Badge.vue';
import { computed } from 'vue';
import { DataIssueTypeConfig, dataIssueTypes } from '@/modules/data-quality/config/data-issue-types';
import LfIcon from '@/ui-kit/icon/Icon.vue';
import LfButton from '@/ui-kit/button/Button.vue';
import { storeToRefs } from 'pinia';
import { useLfSegmentsStore } from '@/modules/lf/segments/store';
const props = defineProps<{
member: Contributor,
type: SelectedIssueType,
}>();
const { avatar } = useContributorHelpers();
const { selectedProjectGroup } = storeToRefs(useLfSegmentsStore());
const config = computed<DataIssueTypeConfig>(() => dataIssueTypes[props.type]);
</script>
<script lang="ts">
export default {
name: 'LfDataQualityMemberIssuesItem',
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<template>
<div>
<div v-if="props.types.length > 1" class="pt-4 gap-2 flex flex-col pb-4">
<lf-radio
v-for="type of props.types"
:key="type"
v-model="selectedType"
:value="type"
name="issueType"
>
{{ dataIssueTypes[type].label }}
</lf-radio>
</div>
<div v-if="loading" class="flex justify-center py-20">
<lf-spinner />
</div>
<div v-else-if="members.length > 0">
<lf-data-quality-member-issues-item
v-for="(member) of members"
:key="member.id"
:member="member"
:type="selectedType"
/>
<div v-if="members.length < total" class="pt-4">
<lf-button type="primary-ghost" size="small" :loading="loading" @click="loadMore()">
<i class="ri-arrow-down-line" />Load more
</lf-button>
</div>
</div>
<div v-else class="flex flex-col items-center pt-16">
<div
class="ri-shuffle-line text-gray-200 text-10xl h-40 flex items-center mb-8"
/>
<h5 class="text-center text-lg font-semibold mb-4">
No member issues found
</h5>
<p class="text-sm text-center text-gray-600 leading-5">
We couldn't find any issues with members
</p>
</div>
</div>
</template>

<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import LfSpinner from '@/ui-kit/spinner/Spinner.vue';
import LfButton from '@/ui-kit/button/Button.vue';
import { DataIssueType } from '@/modules/data-quality/types/DataIssueType';
import LfRadio from '@/ui-kit/radio/Radio.vue';
import { DataQualityApiService } from '@/modules/data-quality/services/data-quality.api.service';
import LfDataQualityMemberIssuesItem
from '@/modules/data-quality/components/member/data-quality-member-issues-item.vue';
import { dataIssueTypes } from '../../config/data-issue-types';
const props = defineProps<{
types: DataIssueType[]
}>();
const loading = ref(true);
const limit = ref(20);
const offset = ref(0);
const total = ref(0);
const members = ref<any[]>([]);
const selectedType = ref<string>(props.types?.[0] || '');
const loadDataIssues = () => {
loading.value = true;
DataQualityApiService.findMemberIssues({
type: selectedType.value,
limit: limit.value,
offset: offset.value,
}, [])
.then((res) => {
if (offset.value > 0) {
members.value = [...members.value, ...res];
} else {
members.value = res;
}
if (res.length < limit.value) {
total.value = members.value.length;
} else {
total.value = members.value.length + 1;
}
})
.finally(() => {
loading.value = false;
});
};
const loadMore = () => {
offset.value = members.value.length;
loadDataIssues();
};
watch(selectedType, () => {
offset.value = 0;
loadDataIssues();
});
onMounted(() => {
loadDataIssues();
});
</script>

<script lang="ts">
export default {
name: 'LfDataQualityMemberIssues',
};
</script>
Loading

0 comments on commit 2d06e22

Please sign in to comment.