Skip to content

Commit

Permalink
Admin integration status page (#2686)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaspergrom authored Nov 18, 2024
1 parent 2f1abf0 commit 975492d
Show file tree
Hide file tree
Showing 20 changed files with 796 additions and 122 deletions.
5 changes: 5 additions & 0 deletions backend/src/api/integration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export default (app) => {
`/tenant/:tenantId/integration/autocomplete`,
safeWrap(require('./integrationAutocomplete').default),
)
app.get(`/tenant/:tenantId/integration/global`, safeWrap(require('./integrationGlobal').default))
app.get(
`/tenant/:tenantId/integration/global/status`,
safeWrap(require('./integrationGlobalStatus').default),
)
app.get(`/tenant/:tenantId/integration`, safeWrap(require('./integrationList').default))
app.get(`/tenant/:tenantId/integration/:id`, safeWrap(require('./integrationFind').default))

Expand Down
13 changes: 13 additions & 0 deletions backend/src/api/integration/integrationGlobal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Permissions from '../../security/permissions'
import IntegrationService from '../../services/integrationService'
import PermissionChecker from '../../services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.integrationRead)
const payload = await new IntegrationService(req).findGlobalIntegrations(
req.params.tenantId,
req.query,
)

await req.responseHandler.success(req, res, payload)
}
14 changes: 14 additions & 0 deletions backend/src/api/integration/integrationGlobalStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Permissions from '../../security/permissions'
import IntegrationService from '../../services/integrationService'
import PermissionChecker from '../../services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.integrationRead)

const payload = await new IntegrationService(req).findGlobalIntegrationsStatusCount(
req.params.tenantId,
req.query,
)

await req.responseHandler.success(req, res, payload)
}
65 changes: 65 additions & 0 deletions backend/src/database/repositories/integrationRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import Sequelize, { QueryTypes } from 'sequelize'

import { captureApiChange, integrationConnectAction } from '@crowd/audit-logs'
import { Error404 } from '@crowd/common'
import {
fetchGlobalIntegrations,
fetchGlobalIntegrationsCount,
fetchGlobalIntegrationsStatusCount,
fetchGlobalNotConnectedIntegrations,
fetchGlobalNotConnectedIntegrationsCount,
} from '@crowd/data-access-layer/src/integrations'
import { IntegrationRunState, PlatformType } from '@crowd/types'

import SequelizeFilterUtils from '../utils/sequelizeFilterUtils'
Expand Down Expand Up @@ -393,6 +400,64 @@ class IntegrationRepository {
})
}

/**
* Finds global integrations based on the provided parameters.
*
* @param {string} tenantId - The ID of the tenant for which integrations are to be found.
* @param {Object} filters - An object containing various filter options.
* @param {string} [filters.platform=null] - The platform to filter integrations by.
* @param {string[]} [filters.status=['done']] - The status of the integrations to be filtered.
* @param {string} [filters.query=''] - The search query to filter integrations.
* @param {number} [filters.limit=20] - The maximum number of integrations to return.
* @param {number} [filters.offset=0] - The offset for pagination.
* @param {IRepositoryOptions} options - The repository options for querying.
* @returns {Promise<Object>} The result containing the rows of integrations and metadata about the query.
*/
static async findGlobalIntegrations(
tenantId: string,
{ platform = null, status = ['done'], query = '', limit = 20, offset = 0 },
options: IRepositoryOptions,
) {
const qx = SequelizeRepository.getQueryExecutor(options)
if (status.includes('not-connected')) {
const rows = await fetchGlobalNotConnectedIntegrations(
qx,
tenantId,
platform,
query,
limit,
offset,
)
const [result] = await fetchGlobalNotConnectedIntegrationsCount(qx, tenantId, platform, query)
return { rows, count: +result.count, limit: +limit, offset: +offset }
}

const rows = await fetchGlobalIntegrations(qx, tenantId, status, platform, query, limit, offset)
const [result] = await fetchGlobalIntegrationsCount(qx, tenantId, status, platform, query)
return { rows, count: +result.count, limit: +limit, offset: +offset }
}

/**
* Retrieves the count of global integrations statuses for a specified tenant and platform.
* This method aggregates the count of different integration statuses including a 'not-connected' status.
*
* @param {string} tenantId - The unique identifier for the tenant.
* @param {Object} param1 - The optional parameters.
* @param {string|null} [param1.platform=null] - The platform to filter the integrations. Default is null.
* @param {IRepositoryOptions} options - The options for the repository operations.
* @return {Promise<Array<Object>>} A promise that resolves to an array of objects containing the statuses and their counts.
*/
static async findGlobalIntegrationsStatusCount(
tenantId: string,
{ platform = null },
options: IRepositoryOptions,
) {
const qx = SequelizeRepository.getQueryExecutor(options)
const [result] = await fetchGlobalNotConnectedIntegrationsCount(qx, tenantId, platform, '')
const rows = await fetchGlobalIntegrationsStatusCount(qx, tenantId, platform)
return [...rows, { status: 'not-connected', count: +result.count }]
}

static async findAndCountAll(
{ filter = {} as any, advancedFilter = null as any, limit = 0, offset = 0, orderBy = '' },
options: IRepositoryOptions,
Expand Down
22 changes: 22 additions & 0 deletions backend/src/services/integrationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,28 @@ export default class IntegrationService {
return IntegrationRepository.findAndCountAll(args, this.options)
}

/**
* Retrieves global integrations for the specified tenant.
*
* @param {string} tenantId - The unique identifier of the tenant.
* @param {any} args - Additional arguments that define search criteria or constraints.
* @return {Promise<any>} A promise that resolves to the list of global integrations matching the criteria.
*/
async findGlobalIntegrations(tenantId: string, args: any) {
return IntegrationRepository.findGlobalIntegrations(tenantId, args, this.options)
}

/**
* Fetches the global count of integration statuses for a given tenant.
*
* @param {string} tenantId - The ID of the tenant for which to fetch the count.
* @param {Object} args - Additional arguments to refine the query.
* @return {Promise<number>} A promise that resolves to the count of global integration statuses.
*/
async findGlobalIntegrationsStatusCount(tenantId: string, args: any) {
return IntegrationRepository.findGlobalIntegrationsStatusCount(tenantId, args, this.options)
}

async query(data) {
const advancedFilter = data.filter
const orderBy = data.orderBy
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<template>
<lf-dropdown placement="bottom-end" width="20rem">
<template #trigger>
<lf-button type="secondary" class="!font-normal">
<template v-if="!model">
<lf-icon-old name="apps-2-line" :size="16" />
All integrations
</template>
<template v-else>
<img :src="lfIntegrations[model]?.image" :alt="lfIntegrations[model]?.name" class="w-4 h-4 object-contain">
{{ lfIntegrations[model]?.name }}
</template>
<lf-icon-old name="arrow-down-s-line" :size="16" />
</lf-button>
</template>
<div class="max-h-80 overflow-y-scroll -m-2 p-2">
<lf-dropdown-item :selected="!model" @click="model = ''">
<div class="flex items-center gap-2">
<lf-icon-old name="apps-2-line" :size="16" />
All integrations
</div>
</lf-dropdown-item>
<lf-dropdown-separator />
<lf-dropdown-item
v-for="(integration, key) in lfIntegrations"
:key="key"
:selected="model === key"
@click="model = key"
>
<div class="flex items-center gap-2">
<img :src="integration.image" :alt="integration.name" class="w-4 h-4 object-contain">
{{ integration.name }}
</div>
</lf-dropdown-item>
</div>
</lf-dropdown>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import LfButton from '@/ui-kit/button/Button.vue';
import LfIconOld from '@/ui-kit/icon/IconOld.vue';
import LfDropdown from '@/ui-kit/dropdown/Dropdown.vue';
import LfDropdownItem from '@/ui-kit/dropdown/DropdownItem.vue';
import LfDropdownSeparator from '@/ui-kit/dropdown/DropdownSeparator.vue';
import { lfIntegrations } from '@/config/integrations';
const props = defineProps<{
modelValue?: string,
}>();
const emit = defineEmits<{(e: 'update:modelValue', value: string)}>();
const model = computed({
get() {
return props.modelValue || '';
},
set(value: string) {
emit('update:modelValue', value);
},
});
</script>
<script lang="ts">
export default {
name: 'LfAdminIntegrationPlatformSelect',
};
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/con
const connecting: IntegrationStatusConfig = {
key: 'connecting',
show: (integration: any) => integration.status === 'in-progress',
statuses: ['in-progress'],
status: {
text: 'Connecting',
icon: 'loader-4-line animate-spin',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/con
const done: IntegrationStatusConfig = {
key: 'done',
show: (integration: any) => integration.status === 'done',
statuses: ['done'],
status: {
text: 'Connected',
icon: 'checkbox-circle-fill',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/con
const error: IntegrationStatusConfig = {
key: 'error',
show: (integration: any) => integration.status === 'error',
statuses: ['error'],
status: {
text: 'Connection failed',
icon: 'error-warning-fill',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import done from './done';
import error from './error';
import waitingForAction from './waiting-for-action';
import waitingApproval from './waiting-approval';
import connecting from './connecting';
import notConnected from './not-connected';

export interface IntegrationStatusConfig {
key: string;
show: (integration: any) => boolean;
statuses: string[],
status: {
text: string;
icon: string;
Expand All @@ -27,7 +28,6 @@ export const lfIntegrationStatuses: Record<string, IntegrationStatusConfig> = {
done,
error,
waitingForAction,
waitingApproval,
connecting,
};

Expand All @@ -36,6 +36,7 @@ export const lfIntegrationStatusesTabs: Record<string, IntegrationStatusConfig>
connecting,
waitingForAction,
error,
notConnected,
};

export const getIntegrationStatus = (integration: any): IntegrationStatusConfig => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index';

const notConnected: IntegrationStatusConfig = {
key: 'notConnected',
show: (integration: any) => !integration || integration.status === 'not-connected',
statuses: ['not-connected'],
status: {
text: 'Not-connected',
icon: '',
color: 'text-gray-600',
},
actionBar: {
background: 'bg-gray-50',
color: 'text-gray-900',
},
tabs: {
text: 'Not connected',
empty: 'No integrations to be connected',
badge: 'bg-gray-100',
},
};

export default notConnected;
Loading

0 comments on commit 975492d

Please sign in to comment.