From ccd61b6b4dac2e7f2a2c7acc488e9b78edd940a5 Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Wed, 3 Dec 2025 17:17:57 +0800 Subject: [PATCH 01/51] chore: initial commit Signed-off-by: Efren Lim --- frontend/src/modules/admin/admin.routes.ts | 53 +-- .../components/fragments/overview-filter.vue | 359 ++++++++++++++++++ .../components/fragments/trend-display.vue | 91 +++++ .../sections/integration-details.vue | 14 + .../sections/integration-status.vue | 14 + .../overview/components/sections/summary.vue | 84 ++++ .../admin/modules/overview/pages/overview.vue | 28 ++ .../modules/overview/store/overview.store.ts | 13 + .../modules/overview/types/overview.types.ts | 5 + frontend/src/router/index.js | 136 ++++--- .../shared/modules/monitoring/types/event.ts | 2 +- frontend/src/ui-kit/index.scss | 2 + .../lfx/dropdown/dropdown-group-title.vue | 15 + .../src/ui-kit/lfx/dropdown/dropdown-item.vue | 63 +++ .../ui-kit/lfx/dropdown/dropdown-search.vue | 62 +++ .../ui-kit/lfx/dropdown/dropdown-select.vue | 101 +++++ .../ui-kit/lfx/dropdown/dropdown-selector.vue | 40 ++ .../lfx/dropdown/dropdown-separator.vue | 13 + .../src/ui-kit/lfx/dropdown/dropdown.scss | 74 ++++ frontend/src/ui-kit/lfx/dropdown/dropdown.vue | 79 ++++ .../lfx/dropdown/types/dropdown.types.ts | 37 ++ frontend/src/ui-kit/lfx/popover/popover.scss | 26 ++ frontend/src/ui-kit/lfx/popover/popover.vue | 217 +++++++++++ .../lfx/popover/types/PopoverPlacement.ts | 18 + .../lfx/popover/types/PopoverTrigger.ts | 5 + frontend/src/utils/responsive.ts | 29 ++ frontend/tailwind.config.js | 73 +--- 27 files changed, 1504 insertions(+), 149 deletions(-) create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/sections/summary.vue create mode 100644 frontend/src/modules/admin/modules/overview/pages/overview.vue create mode 100644 frontend/src/modules/admin/modules/overview/store/overview.store.ts create mode 100644 frontend/src/modules/admin/modules/overview/types/overview.types.ts create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-group-title.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-selector.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-separator.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown.scss create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/types/dropdown.types.ts create mode 100644 frontend/src/ui-kit/lfx/popover/popover.scss create mode 100644 frontend/src/ui-kit/lfx/popover/popover.vue create mode 100644 frontend/src/ui-kit/lfx/popover/types/PopoverPlacement.ts create mode 100644 frontend/src/ui-kit/lfx/popover/types/PopoverTrigger.ts create mode 100644 frontend/src/utils/responsive.ts diff --git a/frontend/src/modules/admin/admin.routes.ts b/frontend/src/modules/admin/admin.routes.ts index 41e01f1c5a..a008ac88fe 100644 --- a/frontend/src/modules/admin/admin.routes.ts +++ b/frontend/src/modules/admin/admin.routes.ts @@ -1,19 +1,16 @@ -import Layout from '@/modules/layout/components/layout.vue'; -import { PageEventKey } from '@/shared/modules/monitoring/types/event'; -import { PermissionGuard } from '@/shared/modules/permissions/router/PermissionGuard'; -import { LfPermission } from '@/shared/modules/permissions/types/Permissions'; +import Layout from '@/modules/layout/components/layout.vue' +import { PageEventKey } from '@/shared/modules/monitoring/types/event' +import { PermissionGuard } from '@/shared/modules/permissions/router/PermissionGuard' +import { LfPermission } from '@/shared/modules/permissions/types/Permissions' -const ProjectGroupsListPage = () => import( - '@/modules/admin/modules/projects/pages/project-groups-list.page.vue' -); +const OverviewPage = () => import('@/modules/admin/modules/overview/pages/overview.vue') -const ProjectsPage = () => import( - '@/modules/admin/modules/projects/pages/projects.page.vue' -); +// const ProjectGroupsListPage = () => +// import('@/modules/admin/modules/projects/pages/project-groups-list.page.vue') -const AdminPanelPage = () => import( - '@/modules/admin/pages/admin-panel.page.vue' -); +const ProjectsPage = () => import('@/modules/admin/modules/projects/pages/projects.page.vue') + +const AdminPanelPage = () => import('@/modules/admin/pages/admin-panel.page.vue') export default [ { @@ -26,15 +23,25 @@ export default [ }, children: [ { - name: 'projectGroupsList', - path: '/project-groups', - component: ProjectGroupsListPage, + name: 'overview', + path: '/overview', + component: OverviewPage, meta: { auth: true, - title: 'Project Groups', - eventKey: PageEventKey.PROJECT_GROUPS, + title: 'Overview', + eventKey: PageEventKey.OVERVIEW, }, }, + // { + // name: 'projectGroupsList', + // path: '/project-groups', + // component: ProjectGroupsListPage, + // meta: { + // auth: true, + // title: 'Project Groups', + // eventKey: PageEventKey.PROJECT_GROUPS, + // }, + // }, { name: 'adminPanel', path: '/admin', @@ -43,9 +50,7 @@ export default [ title: 'Admin Panel', eventKey: PageEventKey.ADMIN_PANEL, }, - beforeEnter: [ - PermissionGuard(LfPermission.projectGroupEdit), - ], + beforeEnter: [PermissionGuard(LfPermission.projectGroupEdit)], }, { name: 'adminProjects', @@ -60,10 +65,8 @@ export default [ parameter: 'id', }, }, - beforeEnter: [ - PermissionGuard(LfPermission.projectEdit), - ], + beforeEnter: [PermissionGuard(LfPermission.projectEdit)], }, ], }, -]; +] diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue new file mode 100644 index 0000000000..60a435389a --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue @@ -0,0 +1,359 @@ + + + + + + + diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue b/frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue new file mode 100644 index 0000000000..9dfd2f3449 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue new file mode 100644 index 0000000000..9e36fd84fd --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue new file mode 100644 index 0000000000..714ace7613 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue new file mode 100644 index 0000000000..6f541fe008 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue @@ -0,0 +1,84 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/pages/overview.vue b/frontend/src/modules/admin/modules/overview/pages/overview.vue new file mode 100644 index 0000000000..73de99f8b8 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/pages/overview.vue @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/store/overview.store.ts b/frontend/src/modules/admin/modules/overview/store/overview.store.ts new file mode 100644 index 0000000000..e3270bc786 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/store/overview.store.ts @@ -0,0 +1,13 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { Project, ProjectGroup } from '@/modules/lf/segments/types/Segments' + +export const useOverviewStore = defineStore('overview', () => { + const selectedProjectGroup = ref(null) + const selectedProject = ref(null) + + return { + selectedProjectGroup, + selectedProject, + } +}) diff --git a/frontend/src/modules/admin/modules/overview/types/overview.types.ts b/frontend/src/modules/admin/modules/overview/types/overview.types.ts new file mode 100644 index 0000000000..42196fdf61 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/types/overview.types.ts @@ -0,0 +1,5 @@ +export interface OverviewTrends { + current: number + previous: number + period: string +} diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 446e5a2029..1707c6bab6 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -1,16 +1,13 @@ -import { - createRouter as createVueRouter, - createWebHistory, -} from 'vue-router'; -import { storeToRefs } from 'pinia'; - -import { store } from '@/store'; -import authGuards from '@/middleware/auth'; -import modules from '@/modules'; -import ProgressBar from '@/shared/progress-bar/progress-bar'; -import { useLfSegmentsStore } from '@/modules/lf/segments/store'; -import auth from '@/modules/auth'; -import navigationGuard from '@/middleware/navigation/navigation-guard'; +import { createRouter as createVueRouter, createWebHistory } from 'vue-router' +import { storeToRefs } from 'pinia' + +import { store } from '@/store' +import authGuards from '@/middleware/auth' +import modules from '@/modules' +import ProgressBar from '@/shared/progress-bar/progress-bar' +import { useLfSegmentsStore } from '@/modules/lf/segments/store' +import auth from '@/modules/auth' +import navigationGuard from '@/middleware/navigation/navigation-guard' /** * Loads all the routes from src/modules/ folders, and adds the catch-all rule to handle 404s @@ -20,25 +17,27 @@ import navigationGuard from '@/middleware/navigation/navigation-guard'; const routes = [ { path: '', - redirect: '/project-groups', + redirect: '/overview', }, ...auth.routes, ...Object.keys(modules) .filter((key) => Boolean(modules[key].routes)) - .map((key) => modules[key].routes.map((r) => { - // eslint-disable-next-line no-param-reassign - r.meta = { - ...r.meta, - middleware: [...authGuards], - }; - return r; - })) + .map((key) => + modules[key].routes.map((r) => { + // eslint-disable-next-line no-param-reassign + r.meta = { + ...r.meta, + middleware: [...authGuards], + } + return r + }), + ) .reduce((a, b) => a.concat(b), []), { path: '/:catchAll(.*)', redirect: '/404' }, -]; +] // eslint-disable-next-line import/no-mutable-exports -let router; +let router /** * Creates/Sets Router @@ -50,65 +49,66 @@ export const createRouter = () => { history: createWebHistory(), routes, scrollBehavior() { - return { x: 0, y: 0 }; + return { x: 0, y: 0 } }, - }); + }) - const originalPush = router.push; + const originalPush = router.push router.push = function push(location) { - return originalPush - .call(this, location) - .catch((error) => { - console.error(error); - ProgressBar.done(); - }); - }; + return originalPush.call(this, location).catch((error) => { + console.error(error) + ProgressBar.done() + }) + } router.beforeEach(async (to, from, next) => { - const lsSegmentsStore = useLfSegmentsStore(); - const { selectedProjectGroup } = storeToRefs(lsSegmentsStore); - const { listProjectGroups, updateSelectedProjectGroup } = lsSegmentsStore; + const lsSegmentsStore = useLfSegmentsStore() + const { selectedProjectGroup } = storeToRefs(lsSegmentsStore) + const { listProjectGroups, updateSelectedProjectGroup } = lsSegmentsStore // Set title to pages - document.title = `LFX Community Data Platform${to.meta.title ? ` | ${to.meta.title}` : ''}`; + document.title = `LFX Community Data Platform${to.meta.title ? ` | ${to.meta.title}` : ''}` if (to.name && to.query.menu === from.query.menu && to.name !== from.name) { - ProgressBar.start(); + ProgressBar.start() } - const matchedRoute = to.matched.find( - (m) => m.meta.middleware, - ); + const matchedRoute = to.matched.find((m) => m.meta.middleware) if (matchedRoute !== undefined) { - const middlewareArray = Array.isArray( - matchedRoute.meta.middleware, - ) + const middlewareArray = Array.isArray(matchedRoute.meta.middleware) ? matchedRoute.meta.middleware - : [matchedRoute.meta.middleware]; + : [matchedRoute.meta.middleware] const context = { from, router, to, store, - }; + } await middlewareArray.forEach(async (middleware) => { - await middleware(context); - }); + await middleware(context) + }) // Redirect to project group landing pages if routes that require a selected project group // And no project group is selected - if (to.meta.segments?.requireSelectedProjectGroup || to.meta.segments?.optionalSelectedProjectGroup) { - if (!selectedProjectGroup.value && !to.query.projectGroup && !to.meta.segments?.optionalSelectedProjectGroup) { - next('/project-groups'); - return; + if ( + to.meta.segments?.requireSelectedProjectGroup || + to.meta.segments?.optionalSelectedProjectGroup + ) { + if ( + !selectedProjectGroup.value && + !to.query.projectGroup && + !to.meta.segments?.optionalSelectedProjectGroup + ) { + next('/project-groups') + return } if (!to.query.projectGroup && selectedProjectGroup.value?.id) { - next({ ...to, query: { ...to.query, projectGroup: selectedProjectGroup.value?.id } }); - return; + next({ ...to, query: { ...to.query, projectGroup: selectedProjectGroup.value?.id } }) + return } if (!selectedProjectGroup.value) { @@ -116,29 +116,27 @@ export const createRouter = () => { await listProjectGroups({ limit: null, reset: true, - }); + }) - updateSelectedProjectGroup(to.query.projectGroup, false); + updateSelectedProjectGroup(to.query.projectGroup, false) } catch (e) { - next('/project-groups'); - return; + next('/project-groups') + return } } } } - next(); - }); + next() + }) router.afterEach(async (to) => { - ProgressBar.done(); - await navigationGuard({ to }); - }); + ProgressBar.done() + await navigationGuard({ to }) + }) } - return router; -}; + return router +} -export { - router, -}; +export { router } diff --git a/frontend/src/shared/modules/monitoring/types/event.ts b/frontend/src/shared/modules/monitoring/types/event.ts index b54b34a471..89cbed85e0 100644 --- a/frontend/src/shared/modules/monitoring/types/event.ts +++ b/frontend/src/shared/modules/monitoring/types/event.ts @@ -13,8 +13,8 @@ export enum EventType { } export enum PageEventKey { - PROJECT_GROUPS = 'Project groups', OVERVIEW = 'Overview', + PROJECT_GROUPS = 'Project groups', MEMBERS = 'Contributors', ORGANIZATIONS = 'Organizations', ACTIVITIES = 'Activities', diff --git a/frontend/src/ui-kit/index.scss b/frontend/src/ui-kit/index.scss index ac1b2a8bba..400823aa34 100644 --- a/frontend/src/ui-kit/index.scss +++ b/frontend/src/ui-kit/index.scss @@ -22,4 +22,6 @@ @import 'timeline/timeline'; @import 'tooltip/tooltip'; @import 'tag/tag'; +@import 'lfx/dropdown/dropdown'; +@import 'lfx/popover/popover'; diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-group-title.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-group-title.vue new file mode 100644 index 0000000000..cdccb3cb46 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-group-title.vue @@ -0,0 +1,15 @@ + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue new file mode 100644 index 0000000000..f8b77de572 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue @@ -0,0 +1,63 @@ + + + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue new file mode 100644 index 0000000000..70f0cb6169 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue @@ -0,0 +1,62 @@ + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue new file mode 100644 index 0000000000..87b01a3b82 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue @@ -0,0 +1,101 @@ + + + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-selector.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-selector.vue new file mode 100644 index 0000000000..efc117e626 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-selector.vue @@ -0,0 +1,40 @@ + + + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-separator.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-separator.vue new file mode 100644 index 0000000000..92421509ee --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-separator.vue @@ -0,0 +1,13 @@ + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown.scss b/frontend/src/ui-kit/lfx/dropdown/dropdown.scss new file mode 100644 index 0000000000..0314cce6e1 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown.scss @@ -0,0 +1,74 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +.lfx-c-dropdown { + @apply bg-white border border-neutral-200 rounded-lg p-1 flex flex-col gap-1 max-h-96 shadow-lg; + + //@screen sm{ + // @apply ; + //} + + &__item { + @apply bg-white py-2 px-3 flex items-center gap-2 text-sm leading-5 cursor-pointer transition-all rounded-md; + + &:hover { + @apply bg-neutral-50; + } + + .c-icon { + @apply text-neutral-500; + } + + &.is-selected { + @apply font-semibold; + + .c-icon { + @apply text-neutral-900; + } + } + } + + &__separator { + @apply w-full border-b border-neutral-100 h-0; + } + + &__group-title { + @apply px-3 pt-1 text-xs font-semibold leading-5 text-neutral-400; + } + + &__selector { + @apply flex items-center gap-2 text-sm leading-5 font-medium transition-all; + @apply px-3 py-2 cursor-pointer transition-all rounded-lg select-none; + + &:hover { + @apply bg-neutral-50; + } + + &--small { + @apply px-2 py-1 text-xs gap-1.5 rounded-md; + } + + &--filled { + @apply bg-white outline outline-1 outline-neutral-200 shadow-xs; + } + } + + &__sub { + @apply relative; + + &-menu { + @apply absolute left-full top-0 z-10 ml-3 overflow-auto; + } + } +} + +.dropdown-popover.c-popover__content { + z-index: 9; +} + +.is-open { + .lfx-c-dropdown { + &__selector { + @apply bg-neutral-50; + } + } +} diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown.vue new file mode 100644 index 0000000000..e1ca4c1394 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown.vue @@ -0,0 +1,79 @@ + + + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/types/dropdown.types.ts b/frontend/src/ui-kit/lfx/dropdown/types/dropdown.types.ts new file mode 100644 index 0000000000..299a6b6497 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/types/dropdown.types.ts @@ -0,0 +1,37 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +export interface DropdownOption { + label: string; + value: string; + description?: string; +} + +export interface DropdownGroupOptions { + label: string; + items: DropdownOption[]; +} + +export const dropdownSizes = ['default', 'small'] as const; +export const dropdownTypes = ['filled', 'transparent'] as const; + +export type DropdownSize = (typeof dropdownSizes)[number]; +export type DropdownType = (typeof dropdownTypes)[number]; + +export interface DropdownProps { + modelValue?: string; + options: DropdownOption[] | DropdownGroupOptions[]; + dropdownIcon?: string; + placeholder?: string; + disabled?: boolean; + type?: DropdownType; + size?: DropdownSize; + showFilter?: boolean; + showGroupBreaks?: boolean; + icon?: string; + fullWidth?: boolean; + center?: boolean; + prefix?: string; + dropdownPosition?: 'left' | 'right'; + iconOnlyMobile?: boolean; + splitLines?: number[]; // index of the lines to split the dropdown +} diff --git a/frontend/src/ui-kit/lfx/popover/popover.scss b/frontend/src/ui-kit/lfx/popover/popover.scss new file mode 100644 index 0000000000..fe9b5feef4 --- /dev/null +++ b/frontend/src/ui-kit/lfx/popover/popover.scss @@ -0,0 +1,26 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +.lfx-c-popover { + &__trigger { + @apply w-fit; + } + + &__content { + @apply z-50 w-max overflow-visible; + width: auto !important; + + &.is-hidden { + @apply invisible opacity-0 pointer-events-none; + } + + &.is-modal { + background: linear-gradient(to bottom, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.25)); + @apply fixed top-0 left-0 h-full w-full transform translate-x-0 translate-y-0 p-12 pt-16 px-5 #{!important}; + @apply flex flex-wrap justify-center items-start; + + & > div { + @apply w-full max-h-full #{!important}; + } + } + } +} diff --git a/frontend/src/ui-kit/lfx/popover/popover.vue b/frontend/src/ui-kit/lfx/popover/popover.vue new file mode 100644 index 0000000000..f25041c78b --- /dev/null +++ b/frontend/src/ui-kit/lfx/popover/popover.vue @@ -0,0 +1,217 @@ + + + + + + diff --git a/frontend/src/ui-kit/lfx/popover/types/PopoverPlacement.ts b/frontend/src/ui-kit/lfx/popover/types/PopoverPlacement.ts new file mode 100644 index 0000000000..d07ed35dd7 --- /dev/null +++ b/frontend/src/ui-kit/lfx/popover/types/PopoverPlacement.ts @@ -0,0 +1,18 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +export const popoverPlacements = [ + 'bottom-start', + 'bottom', + 'bottom-end', + 'top', + 'top-start', + 'top-end', + 'left', + 'left-start', + 'left-end', + 'right', + 'right-start', + 'right-end', +] as const; + +export type PopoverPlacement = (typeof popoverPlacements)[number]; diff --git a/frontend/src/ui-kit/lfx/popover/types/PopoverTrigger.ts b/frontend/src/ui-kit/lfx/popover/types/PopoverTrigger.ts new file mode 100644 index 0000000000..f395b93ecf --- /dev/null +++ b/frontend/src/ui-kit/lfx/popover/types/PopoverTrigger.ts @@ -0,0 +1,5 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +export const popoverTrigger = ['click', 'hover'] as const; + +export type PopoverTrigger = (typeof popoverTrigger)[number]; diff --git a/frontend/src/utils/responsive.ts b/frontend/src/utils/responsive.ts new file mode 100644 index 0000000000..78ecab918b --- /dev/null +++ b/frontend/src/utils/responsive.ts @@ -0,0 +1,29 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +import { onMounted, onUnmounted, ref } from 'vue'; + +const useResponsive = () => { + const pageWidth = ref(0); + + const updatePageWidth = () => { + pageWidth.value = window.innerWidth; + }; + + const isMobileOrTablet = () => /Mobi|Android|iPhone|iPad|iPod/i.test(navigator?.userAgent); + + onMounted(() => { + updatePageWidth(); + window.addEventListener('resize', updatePageWidth); + }); + + onUnmounted(() => { + window.removeEventListener('resize', updatePageWidth); + }); + + return { + isMobileOrTablet, + pageWidth, + }; +}; + +export default useResponsive; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 9483bb6125..4787e63d16 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,10 +1,9 @@ /* eslint-disable */ -const { getThemeReplacementsValues } = require('./.tailwind/colorConverter.js'); +const { getThemeReplacementsValues } = require('./.tailwind/colorConverter.js') // tailwind.config.js const plugin = require('tailwindcss/plugin') - -const themeReplacements = getThemeReplacementsValues(); +const themeReplacements = getThemeReplacementsValues() const spacing = { 0.5: '0.125rem', @@ -63,7 +62,7 @@ const spacing = { 120: '30rem', 148: '37rem', 254: '63.5rem', -}; +} /** @type {import('tailwindcss').Config} */ module.exports = { @@ -189,12 +188,12 @@ module.exports = { 3: '3px', }, boxShadow: { - DEFAULT: - '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', + DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)', lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)', xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)', '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)', + xs: '0 1px 2px -1px rgba(0, 0, 0, 0.10), 0 1px 3px 0 rgba(0, 0, 0, 0.10)', }, fontSize: { '4xs': ['0.5rem'], @@ -209,50 +208,17 @@ module.exports = { '5xl': ['4rem'], '8xl': ['8rem'], '10xl': ['10rem'], - h1: [ - 'var(--lf-heading-1-font-size)', - 'var(--lf-heading-1-line-height)', - ], - h2: [ - 'var(--lf-heading-2-font-size)', - 'var(--lf-heading-2-line-height)', - ], - h3: [ - 'var(--lf-heading-3-font-size)', - 'var(--lf-heading-3-line-height)', - ], - h4: [ - 'var(--lf-heading-4-font-size)', - 'var(--lf-heading-4-line-height)', - ], - h5: [ - 'var(--lf-heading-5-font-size)', - 'var(--lf-heading-5-line-height)', - ], - h6: [ - 'var(--lf-heading-6-font-size)', - 'var(--lf-heading-6-line-height)', - ], - xtiny: [ - 'var(--lf-text-xtiny-font-size)', - 'var(--lf-text-xtiny-line-height)', - ], - tiny: [ - 'var(--lf-text-tiny-font-size)', - 'var(--lf-text-tiny-line-height)', - ], - small: [ - 'var(--lf-text-small-font-size)', - 'var(--lf-text-small-line-height)', - ], - medium: [ - 'var(--lf-text-medium-font-size)', - 'var(--lf-text-medium-line-height)', - ], - large: [ - 'var(--lf-text-large-font-size)', - 'var(--lf-text-large-line-height)', - ], + h1: ['var(--lf-heading-1-font-size)', 'var(--lf-heading-1-line-height)'], + h2: ['var(--lf-heading-2-font-size)', 'var(--lf-heading-2-line-height)'], + h3: ['var(--lf-heading-3-font-size)', 'var(--lf-heading-3-line-height)'], + h4: ['var(--lf-heading-4-font-size)', 'var(--lf-heading-4-line-height)'], + h5: ['var(--lf-heading-5-font-size)', 'var(--lf-heading-5-line-height)'], + h6: ['var(--lf-heading-6-font-size)', 'var(--lf-heading-6-line-height)'], + xtiny: ['var(--lf-text-xtiny-font-size)', 'var(--lf-text-xtiny-line-height)'], + tiny: ['var(--lf-text-tiny-font-size)', 'var(--lf-text-tiny-line-height)'], + small: ['var(--lf-text-small-font-size)', 'var(--lf-text-small-line-height)'], + medium: ['var(--lf-text-medium-font-size)', 'var(--lf-text-medium-line-height)'], + large: ['var(--lf-text-large-font-size)', 'var(--lf-text-large-line-height)'], }, letterSpacing: { 1: '0.0625rem', @@ -288,8 +254,7 @@ module.exports = { transitionProperty: { DEFAULT: 'color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter', - colors: - 'color, background-color, border-color, text-decoration-color, fill, stroke', + colors: 'color, background-color, border-color, text-decoration-color, fill, stroke', }, width: { fit: 'fit-content', @@ -315,7 +280,7 @@ module.exports = { '.overflow-y-unset': { 'overflow-y': 'unset', }, - }); + }) }), ], -}; +} From 0add8b7f4bbb1c79cf37e772db4d855f85cfc6df Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Thu, 4 Dec 2025 13:53:55 +0800 Subject: [PATCH 02/51] chore: added project filter Signed-off-by: Efren Lim --- .../components/fragments/load-more.vue | 87 +++++ .../components/fragments/overview-filter.vue | 359 ------------------ .../components/fragments/project-filter.vue | 134 +++++++ .../fragments/project-group-filter.vue | 188 +++++++++ .../components/sections/overview-filter.vue | 27 ++ .../admin/modules/overview/pages/overview.vue | 2 +- .../modules/layout/components/menu/menu.vue | 6 +- 7 files changed, 440 insertions(+), 363 deletions(-) create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/load-more.vue delete mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/load-more.vue b/frontend/src/modules/admin/modules/overview/components/fragments/load-more.vue new file mode 100644 index 0000000000..dc67e31c58 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/load-more.vue @@ -0,0 +1,87 @@ + + + + + + diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue deleted file mode 100644 index 60a435389a..0000000000 --- a/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - - diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue new file mode 100644 index 0000000000..ab835501f4 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue new file mode 100644 index 0000000000..9abcc37759 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue b/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue new file mode 100644 index 0000000000..95df6f6bb5 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/frontend/src/modules/admin/modules/overview/pages/overview.vue b/frontend/src/modules/admin/modules/overview/pages/overview.vue index 73de99f8b8..d436144264 100644 --- a/frontend/src/modules/admin/modules/overview/pages/overview.vue +++ b/frontend/src/modules/admin/modules/overview/pages/overview.vue @@ -18,7 +18,7 @@ import AppLfOverviewSummary from '@/modules/admin/modules/overview/components/sections/summary.vue'; import AppLfOverviewIntegrationStatus from '@/modules/admin/modules/overview/components/sections/integration-status.vue'; import AppLfOverviewIntegrationDetails from '@/modules/admin/modules/overview/components/sections/integration-details.vue'; -import AppLfOverviewFilter from '@/modules/admin/modules/overview/components/fragments/overview-filter.vue'; +import AppLfOverviewFilter from '@/modules/admin/modules/overview/components/sections/overview-filter.vue'; + + diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue index ab835501f4..3a51c3585f 100644 --- a/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue +++ b/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue @@ -17,7 +17,7 @@ >
diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue index 714ace7613..c3e677f062 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue @@ -1,10 +1,118 @@ + + diff --git a/frontend/src/ui-kit/lfx/chip/types/chip.types.ts b/frontend/src/ui-kit/lfx/chip/types/chip.types.ts new file mode 100644 index 0000000000..ab2fc6e994 --- /dev/null +++ b/frontend/src/ui-kit/lfx/chip/types/chip.types.ts @@ -0,0 +1,6 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +export const chipSizes = ['small', 'default'] as const; +export const chipTypes = ['bordered', 'default'] as const; +export type ChipSize = (typeof chipSizes)[number]; +export type ChipType = (typeof chipTypes)[number]; From a4db2b8b3c1843c5d480ac052428ca7fbe669412 Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Thu, 4 Dec 2025 14:42:47 +0800 Subject: [PATCH 04/51] chore: added integration details with mock data Signed-off-by: Efren Lim --- .../sections/integration-details.vue | 263 +++++++++++++++++- 1 file changed, 260 insertions(+), 3 deletions(-) diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index 9e36fd84fd..4db28d8f3f 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -1,14 +1,271 @@ \ No newline at end of file + + + \ No newline at end of file From 428fde1508d17451d239ae56bd02ef6b23ae545e Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Fri, 5 Dec 2025 15:31:46 +0800 Subject: [PATCH 05/51] chore: changed integration row display to match integration page Signed-off-by: Efren Lim --- .../pages/integration-status.page.vue | 1 + .../components/fragments/integration-row.vue | 103 ++++++++ .../components/fragments/integration-tabs.vue | 52 ++++ .../components/fragments/status-display.vue | 29 +++ .../sections/integration-details.vue | 229 ++---------------- .../overview/store/mock-overview-data.ts | 118 +++++++++ .../modules/overview/types/overview.types.ts | 30 +++ 7 files changed, 351 insertions(+), 211 deletions(-) create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/integration-row.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue create mode 100644 frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts diff --git a/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue b/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue index 5713743ebb..b8429a554d 100644 --- a/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue +++ b/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue @@ -213,6 +213,7 @@ const fetchGlobalIntegrations = () => { status: lfIntegrationStatusesTabs[status.value].statuses, }) .then((res) => { + console.log(res); if (res.offset > 0) { integrations.value = [...integrations.value, ...res.rows]; } else { diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/integration-row.vue b/frontend/src/modules/admin/modules/overview/components/fragments/integration-row.vue new file mode 100644 index 0000000000..5e15154cfd --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/integration-row.vue @@ -0,0 +1,103 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue b/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue new file mode 100644 index 0000000000..974ca1cee4 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue @@ -0,0 +1,52 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue b/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue new file mode 100644 index 0000000000..a852a4b102 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index 4db28d8f3f..1523c569b0 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -7,83 +7,23 @@ - - - - - - - - - - - +
-
-
Integration
-
Project
-
Status
-
+
+
Integration
+
Project
+
Status
-
- -
-
- -
- {{ integration.platform }} -
- - -
-
{{ integration.projectName }}
-
{{ integration.projectPath }}
-
- - -
-
- - {{ integration.status.label }} -
-
{{ integration.status.description }}
-
- - -
- -
-
+
@@ -98,112 +38,13 @@ - \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts b/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts new file mode 100644 index 0000000000..5dbbc8c213 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts @@ -0,0 +1,118 @@ +import { IntegrationStatus } from '../types/overview.types' +export const mockOverviewData: IntegrationStatus[] = [ + // In Progress integrations + { + grandparentId: 'd004b040-95d2-449b-b5a3-8e789418cc88', + grandparentName: 'Academy Software Foundation (ASWF)', + id: 'a92494e5-268e-44f1-bffc-f55240be77ed', + name: 'Academy Software Foundation (ASWF)', + parentId: '9d8f1f72-4d12-4502-8f2e-c2b8348c8209', + parentName: 'Academy Software Foundation (ASWF)', + platform: 'git', + segmentId: '8eb1d4d5-2536-435b-980c-f11e30943025', + settings: { remotes: ['https://github.com/AcademySoftwareFoundation/tac'] }, + status: 'in-progress', + statusDetails: '100 out of 1,000 data streams processed...', + }, + { + grandparentId: 'e005c041-96d3-450c-c6a4-9e890519dd89', + grandparentName: 'Academy Software Foundation', + id: 'b93595f6-369f-45g2-cgd4-g66351cf88fe', + name: 'Asset Repository Working Group', + parentId: 'ae9g2g73-5e13-4603-9g3f-d3c9459d9310', + parentName: 'Academy Software Foundation', + platform: 'stackoverflow', + segmentId: '9fc2e5e6-3647-546c-a91d-g22f41a54136', + settings: { tags: ['academy-software-foundation', 'asset-repository'] }, + status: 'in-progress', + statusDetails: '600 data streams being processed...', + }, + { + grandparentId: 'f106d142-a7e4-561d-d7b5-af901620ee90', + grandparentName: 'Academy Software Foundation', + id: 'ca4696g7-470g-46h3-dhf5-h77462dg99gf', + name: 'MaterialX', + parentId: 'bg0h3h84-6f24-5714-ah4g-e4da570ea421', + parentName: 'Academy Software Foundation', + platform: 'git', + segmentId: 'a0d3f6f7-4758-657d-ba2e-h33g52b65247', + settings: { remotes: ['https://github.com/AcademySoftwareFoundation/MaterialX'] }, + status: 'in-progress', + statusDetails: '600 data streams being processed...', + }, + { + grandparentId: 'g207e253-b8f5-672e-e8c6-bg012731ff01', + grandparentName: 'Academy Software Foundation', + id: 'db5707h8-581h-57i4-eig6-i88573eh00hg', + name: 'OpenAssetIO', + parentId: 'ch1i4i95-7g35-6825-bi5h-f5eb681fb532', + parentName: 'Academy Software Foundation', + platform: 'gitlab', + segmentId: 'b1e4g7g8-5869-768e-cb3f-i44h63c76358', + settings: { remotes: ['https://gitlab.com/AcademySoftwareFoundation/OpenAssetIO'] }, + status: 'in-progress', + statusDetails: '600 data streams being processed...', + }, + // Action Required integrations + { + grandparentId: 'h308f364-c9g6-783f-f9d7-ch123842gg12', + grandparentName: 'Academy Software Foundation', + id: 'ec6818i9-692i-68j5-fjh7-j99684fi11ih', + name: 'Community Management', + parentId: 'di2j5ja6-8h46-7936-cj6i-g6fc792gc643', + parentName: 'Academy Software Foundation', + platform: 'slack', + segmentId: 'c2f5h8h9-6970-879f-dc4g-j55i74d87469', + settings: { workspaceId: 'T0123456789', channels: ['general', 'community'] }, + status: 'mapping', + statusDetails: 'API key expired, please update credentials', + }, + { + grandparentId: 'i409g475-da07-894g-ga08-di234953hh23', + grandparentName: 'Academy Software Foundation', + id: 'fd7929ja-7a3j-79k6-gki8-ka0795gj22ji', + name: 'Developer Relations', + parentId: 'ej3k6kb7-9i57-8a47-dk7j-h7gd8a3hd754', + parentName: 'Academy Software Foundation', + platform: 'github', + segmentId: 'd3g6i9ia-7a81-98ag-ed5h-k66j85e98570', + settings: { guildId: '123456789012345678', channels: ['dev-chat', 'announcements'] }, + status: 'mapping', + statusDetails: 'Permission denied, check bot permissions', + }, + // Connection Failed integrations + { + grandparentId: 'j50ah586-eb18-9a5h-hb19-ej345064ii34', + grandparentName: 'Academy Software Foundation', + id: 'ge8a3akb-8b4k-8al7-hkj9-lb1806hk33kj', + name: 'Project Management', + parentId: 'fk4l7lc8-aj68-9b58-el8k-i8he9b4ie865', + parentName: 'Academy Software Foundation', + platform: 'jira', + segmentId: 'e4h7jajb-8b92-a9bh-fe6i-l77k96f9a681', + settings: { baseUrl: 'https://academysoftware.atlassian.net', projectKeys: ['PM', 'ASWF'] }, + status: 'error', + statusDetails: 'Unable to connect to server', + }, +] + +export const mockOverviewTabs = [ + { + label: 'In progress', + key: 'in-progress', + count: 4, + icon: 'clock', + }, + { + label: 'Action required', + key: 'action-required', + count: 2, + icon: 'triangle-exclamation', + }, + { + label: 'Connection failed', + key: 'connection-failed', + count: 1, + icon: 'circle-exclamation', + }, +] diff --git a/frontend/src/modules/admin/modules/overview/types/overview.types.ts b/frontend/src/modules/admin/modules/overview/types/overview.types.ts index 42196fdf61..a67bbf3def 100644 --- a/frontend/src/modules/admin/modules/overview/types/overview.types.ts +++ b/frontend/src/modules/admin/modules/overview/types/overview.types.ts @@ -1,5 +1,35 @@ +// import { IntegrationConfig } from '@/config/integrations' +// import { IntegrationStatusConfig } from '../../integration/config/status' export interface OverviewTrends { current: number previous: number period: string } + +export interface IntegrationTabs { + label: string + key: string + count: number + icon: string +} + +// TODO: Check with backend team about the data structure +export interface IntegrationStatus { + grandparentId: string + grandparentName: string + id: string + name: string + parentId: string + parentName: string + platform: string + segmentId: string + settings: any + status: string + + // integration: IntegrationConfig + // projectGroupName: string + // projectName: string + // projectPath: string + // status: IntegrationStatusConfig + statusDetails: string +} From 3beb014c720026a195692859cc47d054df4acc10 Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Fri, 5 Dec 2025 17:25:56 +0800 Subject: [PATCH 06/51] chore: wire integration tab to api Signed-off-by: Efren Lim --- .../pages/integration-status.page.vue | 1 - .../fragments/integrations-filter.vue | 78 ++++------ .../components/fragments/status-display.vue | 6 +- .../sections/integration-details.vue | 40 +++++- .../sections/integration-status.vue | 136 ++++++++---------- .../overview/services/overview.api.service.ts | 42 ++++++ .../overview/store/mock-overview-data.ts | 21 --- .../modules/overview/store/overview.store.ts | 2 + .../modules/overview/types/overview.types.ts | 11 +- frontend/src/shared/types/tanstack.ts | 13 +- 10 files changed, 176 insertions(+), 174 deletions(-) create mode 100644 frontend/src/modules/admin/modules/overview/services/overview.api.service.ts diff --git a/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue b/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue index b8429a554d..5713743ebb 100644 --- a/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue +++ b/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue @@ -213,7 +213,6 @@ const fetchGlobalIntegrations = () => { status: lfIntegrationStatusesTabs[status.value].statuses, }) .then((res) => { - console.log(res); if (res.offset > 0) { integrations.value = [...integrations.value, ...res.rows]; } else { diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/integrations-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/integrations-filter.vue index 5958594850..bb058b67dd 100644 --- a/frontend/src/modules/admin/modules/overview/components/fragments/integrations-filter.vue +++ b/frontend/src/modules/admin/modules/overview/components/fragments/integrations-filter.vue @@ -1,6 +1,6 @@ @@ -34,25 +30,26 @@
+ v-for="(integration, key) in lfIntegrations()" + :key="key" + :selected="selectedIntegrationId === key" + @click="selectedIntegrationId = key" + > +
+ + {{ integration.name }} +
+
+ @@ -66,37 +63,18 @@ import LfxDropdownSelect from '@/ui-kit/lfx/dropdown/dropdown-select.vue'; import LfxDropdownSelector from '@/ui-kit/lfx/dropdown/dropdown-selector.vue'; import LfxDropdownItem from '@/ui-kit/lfx/dropdown/dropdown-item.vue'; import { useOverviewStore } from '@/modules/admin/modules/overview/store/overview.store'; +import { lfIntegrations } from '@/config/integrations'; import { storeToRefs } from 'pinia'; + const overviewStore = useOverviewStore(); const { selectedIntegrationId } = storeToRefs(overviewStore); -interface Integration { - id: string; - name: string; -} - -const props = defineProps<{ - integrations: Integration[]; -}>(); - -// Mock data for integrations (can be replaced with props.integrations) -const mockIntegrations: Integration[] = [ - { id: 'github', name: 'GitHub' }, - { id: 'slack', name: 'Slack' }, - { id: 'discord', name: 'Discord' }, - { id: 'linkedin', name: 'LinkedIn' }, - { id: 'twitter', name: 'Twitter' }, -]; - -const selectIntegration = (integrationId: string) => { - selectedIntegrationId.value = integrationId; -}; +const integrations = computed(() => lfIntegrations()); const selectedIntegration = computed(() => { - if (selectedIntegrationId.value === 'all') return null; - const integrations = props.integrations || mockIntegrations; - return integrations.find(i => i.id === selectedIntegrationId.value) || null; + if (selectedIntegrationId.value === 'all' || !selectedIntegrationId.value) return null; + return integrations.value[selectedIntegrationId.value]; }); diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue b/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue index a852a4b102..d594513929 100644 --- a/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue +++ b/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue @@ -4,7 +4,8 @@ {{ status.status.text }} @@ -23,7 +24,4 @@ const props = defineProps<{ }>(); const status = computed(() => getIntegrationStatus(props.integrationStatus)); -const statusIconClass = computed(() => { - return status.value.status.color + (status.value.key === 'in-progress' ? ' animate-spin' : ''); -}); \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index 1523c569b0..00af8e08c1 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -7,7 +7,7 @@ - + @@ -40,22 +40,27 @@ import { ref, computed } from 'vue'; import { mockOverviewData } from '@/modules/admin/modules/overview/store/mock-overview-data'; import IntegrationTabs from '@/modules/admin/modules/overview/components/fragments/integration-tabs.vue'; -import { mockOverviewTabs } from '@/modules/admin/modules/overview/store/mock-overview-data'; import IntegrationRow from '@/modules/admin/modules/overview/components/fragments/integration-row.vue'; +import { useOverviewStore } from '@/modules/admin/modules/overview/store/overview.store'; +import { storeToRefs } from 'pinia'; +import { lfIntegrationStatusesTabs } from '@/modules/admin/modules/integration/config/status'; + +const { integrationStatusCount } = storeToRefs(useOverviewStore()); // Mock data for integrations const integrations = ref(mockOverviewData); -const activeTab = ref('in-progress'); +const activeTab = ref(lfIntegrationStatusesTabs.connecting.key); +// TODO: remove mock data and use the API const filteredIntegrations = computed(() => { return integrations.value.filter(integration => { switch (activeTab.value) { - case 'in-progress': + case lfIntegrationStatusesTabs.connecting.key: return integration.status === 'in-progress'; - case 'action-required': + case lfIntegrationStatusesTabs.waitingForAction.key: return integration.status === 'mapping' || integration.status === 'pending-action'; - case 'connection-failed': + case lfIntegrationStatusesTabs.error.key: return integration.status === 'error'; default: return true; @@ -68,6 +73,29 @@ const paginationText = computed(() => { const showing = filteredIntegrations.value.length; return `${showing} out of ${total} integrations`; }); + +const overviewTabs = computed(() => { + return [ + { + label: 'In progress', + key: lfIntegrationStatusesTabs.connecting.key, + count: integrationStatusCount.value[lfIntegrationStatusesTabs.connecting.key] || 0, + icon: 'clock', + }, + { + label: 'Action required', + key: lfIntegrationStatusesTabs.waitingForAction.key, + count: integrationStatusCount.value[lfIntegrationStatusesTabs.waitingForAction.key] || 0, + icon: 'triangle-exclamation', + }, + { + label: 'Connection failed', + key: lfIntegrationStatusesTabs.error.key, + count: integrationStatusCount.value[lfIntegrationStatusesTabs.error.key] || 0, + icon: 'circle-exclamation', + }, +] +}); diff --git a/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts new file mode 100644 index 0000000000..793932d1d3 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts @@ -0,0 +1,42 @@ +import type { QueryFunction } from '@tanstack/vue-query' +import { type ComputedRef, computed } from 'vue' +import { useQuery } from '@tanstack/vue-query' +import { TanstackKey } from '@/shared/types/tanstack' +import authAxios from '@/shared/axios/auth-axios' +import { GlobalIntegrationStatusCount } from '../types/overview.types' + +export interface GlobalIntegrationStatusCountQueryParams { + platform: string | undefined +} + +class OverviewApiService { + fetchGlobalIntegrationStatusCount(params: ComputedRef) { + const queryKey = computed(() => [ + TanstackKey.GLOBAL_INTEGRATION_STATUS_COUNT, + params.value.platform, + ]) + const queryFn = computed>(() => + this.fetchGlobalIntegrationStatusCountQueryFn(() => ({ + platform: params.value.platform, + })), + ) + + return useQuery({ + queryKey, + queryFn, + }) + } + + fetchGlobalIntegrationStatusCountQueryFn( + query: () => Record, + ): QueryFunction { + return () => + authAxios + .get('/integration/global/status', { + params: query(), + }) + .then((res) => res.data) + } +} + +export const OVERVIEW_API_SERVICE = new OverviewApiService() diff --git a/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts b/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts index 5dbbc8c213..f8e5e4c062 100644 --- a/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts +++ b/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts @@ -95,24 +95,3 @@ export const mockOverviewData: IntegrationStatus[] = [ statusDetails: 'Unable to connect to server', }, ] - -export const mockOverviewTabs = [ - { - label: 'In progress', - key: 'in-progress', - count: 4, - icon: 'clock', - }, - { - label: 'Action required', - key: 'action-required', - count: 2, - icon: 'triangle-exclamation', - }, - { - label: 'Connection failed', - key: 'connection-failed', - count: 1, - icon: 'circle-exclamation', - }, -] diff --git a/frontend/src/modules/admin/modules/overview/store/overview.store.ts b/frontend/src/modules/admin/modules/overview/store/overview.store.ts index 08dba74073..5e969ed3a5 100644 --- a/frontend/src/modules/admin/modules/overview/store/overview.store.ts +++ b/frontend/src/modules/admin/modules/overview/store/overview.store.ts @@ -6,10 +6,12 @@ export const useOverviewStore = defineStore('overview', () => { const selectedProjectGroup = ref(null) const selectedProject = ref(null) const selectedIntegrationId = ref(null) + const integrationStatusCount = ref>({}) return { selectedProjectGroup, selectedProject, selectedIntegrationId, + integrationStatusCount, } }) diff --git a/frontend/src/modules/admin/modules/overview/types/overview.types.ts b/frontend/src/modules/admin/modules/overview/types/overview.types.ts index a67bbf3def..07015c68b2 100644 --- a/frontend/src/modules/admin/modules/overview/types/overview.types.ts +++ b/frontend/src/modules/admin/modules/overview/types/overview.types.ts @@ -25,11 +25,10 @@ export interface IntegrationStatus { segmentId: string settings: any status: string - - // integration: IntegrationConfig - // projectGroupName: string - // projectName: string - // projectPath: string - // status: IntegrationStatusConfig statusDetails: string } + +export interface GlobalIntegrationStatusCount { + status: string + count: number +} diff --git a/frontend/src/shared/types/tanstack.ts b/frontend/src/shared/types/tanstack.ts index 36942b9345..83be487772 100644 --- a/frontend/src/shared/types/tanstack.ts +++ b/frontend/src/shared/types/tanstack.ts @@ -1,8 +1,9 @@ export enum TanstackKey { - ADMIN_PROJECT_GROUPS = 'admin-project-groups', - ADMIN_COLLECTIONS = 'admin-collections', - ADMIN_INSIGHTS_PROJECTS = 'admin-insights-projects', - ADMIN_SUB_PROJECTS = 'admin-sub-projects', - MEMBERS_LIST= 'members-list', - MEMBER_MERGE_SUGGESTIONS_COUNT = 'member-merge-suggestions-count', + ADMIN_PROJECT_GROUPS = 'admin-project-groups', + ADMIN_COLLECTIONS = 'admin-collections', + ADMIN_INSIGHTS_PROJECTS = 'admin-insights-projects', + ADMIN_SUB_PROJECTS = 'admin-sub-projects', + MEMBERS_LIST = 'members-list', + MEMBER_MERGE_SUGGESTIONS_COUNT = 'member-merge-suggestions-count', + GLOBAL_INTEGRATION_STATUS_COUNT = 'global-integration-status-count', } From 82939a91540dc40857f972e3064c3cffadd5f5f8 Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Tue, 9 Dec 2025 12:20:14 +0800 Subject: [PATCH 07/51] chore: wired integration details list to api Signed-off-by: Efren Lim --- .../integration/config/status/connecting.ts | 11 +- .../modules/integration/config/status/done.ts | 11 +- .../integration/config/status/error.ts | 11 +- .../integration/config/status/index.ts | 55 +-- .../config/status/not-connected.ts | 11 +- .../config/status/waiting-for-action.ts | 11 +- .../components/fragments/integration-tabs.vue | 2 +- .../sections/integration-details.vue | 64 ++- .../sections/integration-status.vue | 8 +- .../overview/services/overview.api.service.ts | 57 ++- .../overview/store/mock-overview-data.ts | 390 ++++++++++++++++++ .../modules/overview/types/overview.types.ts | 7 + frontend/src/shared/types/tanstack.ts | 1 + 13 files changed, 581 insertions(+), 58 deletions(-) diff --git a/frontend/src/modules/admin/modules/integration/config/status/connecting.ts b/frontend/src/modules/admin/modules/integration/config/status/connecting.ts index 929f43e3ce..7f6ba81646 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/connecting.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/connecting.ts @@ -1,4 +1,4 @@ -import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index'; +import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index' const connecting: IntegrationStatusConfig = { key: 'connecting', @@ -19,6 +19,11 @@ const connecting: IntegrationStatusConfig = { empty: 'No integrations with connection in progress', badge: 'bg-primary-50', }, -}; + chipStatus: { + icon: 'clock', + iconType: 'solid', + color: 'text-primary-500', + }, +} -export default connecting; +export default connecting diff --git a/frontend/src/modules/admin/modules/integration/config/status/done.ts b/frontend/src/modules/admin/modules/integration/config/status/done.ts index bdb0c6ce8d..70df589997 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/done.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/done.ts @@ -1,4 +1,4 @@ -import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index'; +import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index' const done: IntegrationStatusConfig = { key: 'done', @@ -19,6 +19,11 @@ const done: IntegrationStatusConfig = { empty: 'No integrations connected', badge: 'bg-green-100', }, -}; + chipStatus: { + icon: 'circle-check', + iconType: 'solid', + color: 'text-green-500', + }, +} -export default done; +export default done diff --git a/frontend/src/modules/admin/modules/integration/config/status/error.ts b/frontend/src/modules/admin/modules/integration/config/status/error.ts index 41f9705fb9..8feb99ace6 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/error.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/error.ts @@ -1,4 +1,4 @@ -import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index'; +import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index' const error: IntegrationStatusConfig = { key: 'error', @@ -19,6 +19,11 @@ const error: IntegrationStatusConfig = { empty: 'No integrations with a failed connection', badge: 'bg-red-100', }, -}; + chipStatus: { + icon: 'circle-exclamation', + iconType: 'solid', + color: 'text-red-600', + }, +} -export default error; +export default error diff --git a/frontend/src/modules/admin/modules/integration/config/status/index.ts b/frontend/src/modules/admin/modules/integration/config/status/index.ts index 41ddd8a2be..52654e01e1 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/index.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/index.ts @@ -1,28 +1,33 @@ -import done from './done'; -import error from './error'; -import waitingForAction from './waiting-for-action'; -import connecting from './connecting'; -import notConnected from './not-connected'; +import done from './done' +import error from './error' +import waitingForAction from './waiting-for-action' +import connecting from './connecting' +import notConnected from './not-connected' export interface IntegrationStatusConfig { - key: string; - show: (integration: any) => boolean; - statuses: string[], + key: string + show: (integration: any) => boolean + statuses: string[] status: { - text: string; - icon: string; - iconType?: string; - color: string; - }, + text: string + icon: string + iconType?: string + color: string + } actionBar: { - background: string; - color: string; - }, + background: string + color: string + } tabs: { - text: string; - empty: string; - badge: string; - }, + text: string + empty: string + badge: string + } + chipStatus?: { + icon: string + iconType?: string + color: string + } } export const lfIntegrationStatuses: Record = { @@ -30,7 +35,7 @@ export const lfIntegrationStatuses: Record = { error, waitingForAction, connecting, -}; +} export const lfIntegrationStatusesTabs: Record = { done, @@ -38,14 +43,14 @@ export const lfIntegrationStatusesTabs: Record waitingForAction, error, notConnected, -}; +} export const getIntegrationStatus = (integration: any): IntegrationStatusConfig => { // eslint-disable-next-line no-restricted-syntax for (const key in lfIntegrationStatuses) { if (lfIntegrationStatuses[key].show(integration)) { - return lfIntegrationStatuses[key]; + return lfIntegrationStatuses[key] } } - return connecting; -}; + return connecting +} diff --git a/frontend/src/modules/admin/modules/integration/config/status/not-connected.ts b/frontend/src/modules/admin/modules/integration/config/status/not-connected.ts index 797f79c364..4bd316238c 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/not-connected.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/not-connected.ts @@ -1,4 +1,4 @@ -import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index'; +import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index' const notConnected: IntegrationStatusConfig = { key: 'notConnected', @@ -18,6 +18,11 @@ const notConnected: IntegrationStatusConfig = { empty: 'No integrations to be connected', badge: 'bg-gray-100', }, -}; + chipStatus: { + icon: 'link-simple-slash', + iconType: 'light', + color: 'text-gray-600', + }, +} -export default notConnected; +export default notConnected diff --git a/frontend/src/modules/admin/modules/integration/config/status/waiting-for-action.ts b/frontend/src/modules/admin/modules/integration/config/status/waiting-for-action.ts index 53f8994d8a..7a514fd7d3 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/waiting-for-action.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/waiting-for-action.ts @@ -1,4 +1,4 @@ -import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index'; +import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index' const waitingForAction: IntegrationStatusConfig = { key: 'waitingForAction', @@ -19,6 +19,11 @@ const waitingForAction: IntegrationStatusConfig = { empty: 'No integrations with a pending action', badge: 'bg-yellow-100', }, -}; + chipStatus: { + icon: 'triangle-exclamation', + iconType: 'solid', + color: 'text-yellow-500', + }, +} -export default waitingForAction; +export default waitingForAction diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue b/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue index 974ca1cee4..0253f0ce7f 100644 --- a/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue +++ b/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue @@ -3,7 +3,7 @@ diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index 00af8e08c1..bfe6b15a2c 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -20,7 +20,7 @@
@@ -29,28 +29,53 @@
{{ paginationText }} -
+ + diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index bfe6b15a2c..8e4ed5b25d 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -7,39 +7,63 @@ - + - -
-
Integration
-
Project
-
Status
-
- - -
- -
- - -
- {{ paginationText }} - -
+ + @@ -53,55 +77,12 @@ import { storeToRefs } from 'pinia'; import { lfIntegrationStatusesTabs } from '@/modules/admin/modules/integration/config/status'; import { OVERVIEW_API_SERVICE } from '@/modules/admin/modules/overview/services/overview.api.service'; import LfIcon from '@/ui-kit/icon/Icon.vue'; +import LfSpinner from '@/ui-kit/spinner/Spinner.vue'; const { integrationStatusCount, selectedIntegrationId } = storeToRefs(useOverviewStore()); -// Mock data for integrations -const integrations = ref(mockOverviewData); - const activeTab = ref(lfIntegrationStatusesTabs.connecting.key); -const limit = ref(3); - -const params = computed(() => { - return { - platform: selectedIntegrationId.value || undefined, //activeTab.value, - status: ['done'], //lfIntegrationStatusesTabs[activeTab.value].statuses, - query: '', - limit: limit.value - }; -}); - -const { data, isPending, isError, isFetchingNextPage, fetchNextPage, hasNextPage } = OVERVIEW_API_SERVICE.fetchGlobalIntegrations(params); -const totalCount = computed(() => { - // @ts-expect-error - TanStack Query type inference issue with Vue - return data.value?.pages[0].count || 0; -}); - -// TODO: remove mock data and use the API -const filteredIntegrations = computed(() => { - return integrations.value.filter(integration => { - switch (activeTab.value) { - case lfIntegrationStatusesTabs.connecting.key: - return integration.status === 'in-progress'; - case lfIntegrationStatusesTabs.waitingForAction.key: - return integration.status === 'mapping' || integration.status === 'pending-action'; - case lfIntegrationStatusesTabs.error.key: - return integration.status === 'error'; - default: - return true; - } - }); -}); - -const paginatedIntegrations = computed(() => { - // @ts-expect-error - TanStack Query type inference issue with Vue - return data.value?.pages.flatMap(page => page.rows) || []; -}); - -const paginationText = computed(() => { - const showing = paginatedIntegrations.value.length; - return `${showing} out of ${totalCount.value} integrations`; -}); +const limit = ref(10); const overviewTabs = computed(() => { return [ @@ -126,12 +107,34 @@ const overviewTabs = computed(() => { ] }); -watch(data, () => { - // if (data.value) { - // integrations.value = data.value.rows; - // } - console.log(data.value); -}, { immediate: true }); +const params = computed(() => { + return { + platform: selectedIntegrationId.value || undefined, //activeTab.value, + status: ['done'], //lfIntegrationStatusesTabs[activeTab.value].statuses, + query: '', + limit: limit.value + }; +}); + +const { data, isError, isPending, isFetchingNextPage, fetchNextPage, hasNextPage } = OVERVIEW_API_SERVICE.fetchGlobalIntegrations(params); +const totalCount = computed(() => { + // @ts-expect-error - TanStack Query type inference issue with Vue + return data.value?.pages[0].count || 0; +}); + +const paginatedIntegrations = computed(() => { + // @ts-expect-error - TanStack Query type inference issue with Vue + return data.value?.pages.flatMap(page => page.rows) || []; +}); + +const isEmpty = computed(() => { + return overviewTabs.value.every(tab => tab.count === 0); +}); + +const paginationText = computed(() => { + const showing = paginatedIntegrations.value.length; + return `${showing} out of ${totalCount.value} integrations`; +}); // watch(isError, () => { // if (isError.value) { diff --git a/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue b/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue index 95df6f6bb5..009dda22db 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue @@ -2,6 +2,7 @@
+
@@ -9,11 +10,12 @@ import { computed } from 'vue'; import AppLfProjectGroupFilter from '../fragments/project-group-filter.vue'; import AppLfProjectFilter from '../fragments/project-filter.vue'; +import AppLfSubProjectFilter from '../fragments/sub-project-filter.vue'; import { useOverviewStore } from '../../store/overview.store'; import { storeToRefs } from 'pinia'; const overviewStore = useOverviewStore(); -const { selectedProjectGroup } = storeToRefs(overviewStore); +const { selectedProjectGroup, selectedProject, selectedProjectId } = storeToRefs(overviewStore); const projects = computed(() => { return selectedProjectGroup.value?.projects || []; diff --git a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue index 6f541fe008..e9346776a6 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue @@ -32,7 +32,7 @@ - +
Activities @@ -51,6 +51,11 @@ import LfCard from '@/ui-kit/card/Card.vue'; import LfIcon from '@/ui-kit/icon/Icon.vue'; import AppLfOverviewTrendDisplay from '@/modules/admin/modules/overview/components/fragments/trend-display.vue'; import type { OverviewTrends } from '@/modules/admin/modules/overview/types/overview.types'; +import { useOverviewStore } from '../../store/overview.store'; +import { storeToRefs } from 'pinia'; + +const overviewStore = useOverviewStore(); +const { selectedProject } = storeToRefs(overviewStore); const projectsTrends = ref({ current: 640, diff --git a/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts index 84629105b1..de85188ed4 100644 --- a/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts +++ b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts @@ -4,6 +4,7 @@ import { useInfiniteQuery, useQuery } from '@tanstack/vue-query' import { TanstackKey } from '@/shared/types/tanstack' import authAxios from '@/shared/axios/auth-axios' import { GlobalIntegrationStatusCount, IntegrationStatusResponse } from '../types/overview.types' +import { Project } from '@/modules/lf/segments/types/Segments' export interface GlobalIntegrationStatusCountQueryParams { platform: string | undefined @@ -17,6 +18,33 @@ export interface GlobalIntegrationIntegrationsQueryParams { } class OverviewApiService { + fetchProjectById(params: ComputedRef) { + const queryKey = computed(() => [TanstackKey.PROJECT_BY_ID, params.value]) + const queryFn = computed>(() => + this.fetchProjectByIdQueryFn(() => ({ + id: params.value, + })), + ) + + return useQuery({ + queryKey, + queryFn, + }) + } + + fetchProjectByIdQueryFn( + query: () => Record, + ): QueryFunction { + return () => + authAxios + .get(`/segment/${query().id}`, { + params: { + segments: [query().id], + }, + }) + .then((res) => res.data) + } + fetchGlobalIntegrationStatusCount(params: ComputedRef) { const queryKey = computed(() => [ TanstackKey.GLOBAL_INTEGRATION_STATUS_COUNT, diff --git a/frontend/src/modules/admin/modules/overview/store/overview.store.ts b/frontend/src/modules/admin/modules/overview/store/overview.store.ts index 5e969ed3a5..add648587a 100644 --- a/frontend/src/modules/admin/modules/overview/store/overview.store.ts +++ b/frontend/src/modules/admin/modules/overview/store/overview.store.ts @@ -1,16 +1,24 @@ import { defineStore } from 'pinia' import { ref } from 'vue' -import { Project, ProjectGroup } from '@/modules/lf/segments/types/Segments' +import { Project, ProjectGroup, SubProject } from '@/modules/lf/segments/types/Segments' export const useOverviewStore = defineStore('overview', () => { + const selectedProjectGroupId = ref('') const selectedProjectGroup = ref(null) + const selectedProjectId = ref('') const selectedProject = ref(null) + const selectedSubProjectId = ref('') + const selectedSubProject = ref(null) const selectedIntegrationId = ref(null) const integrationStatusCount = ref>({}) return { + selectedProjectGroupId, selectedProjectGroup, + selectedProjectId, selectedProject, + selectedSubProjectId, + selectedSubProject, selectedIntegrationId, integrationStatusCount, } diff --git a/frontend/src/shared/types/tanstack.ts b/frontend/src/shared/types/tanstack.ts index b751a6a312..4f528405a6 100644 --- a/frontend/src/shared/types/tanstack.ts +++ b/frontend/src/shared/types/tanstack.ts @@ -7,4 +7,5 @@ export enum TanstackKey { MEMBER_MERGE_SUGGESTIONS_COUNT = 'member-merge-suggestions-count', GLOBAL_INTEGRATION_STATUS_COUNT = 'global-integration-status-count', GLOBAL_INTEGRATIONS = 'global-integrations', + PROJECT_BY_ID = 'project-by-id', } From 26e94c9d7a778b4a415f731dd781590641447f53 Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Thu, 11 Dec 2025 12:36:45 +0800 Subject: [PATCH 09/51] fix: hide project card when project is selected Signed-off-by: Efren Lim --- .../admin/modules/overview/components/sections/summary.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue index e9346776a6..78b4360414 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue @@ -2,7 +2,7 @@
- +
Projects @@ -32,7 +32,7 @@
- +
Activities From c155198f18d7f566589d775b1868c2c4b7ac6116 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Thu, 11 Dec 2025 15:51:04 +0100 Subject: [PATCH 10/51] feat: add metrics api --- .../src/api/dashboard/dashboardMetricsGet.ts | 12 ++++ backend/src/api/dashboard/index.ts | 1 + backend/src/services/dashboardService.ts | 24 +++++++ .../data-access-layer/src/dashboards/base.ts | 63 +++++++++++++++++++ .../data-access-layer/src/dashboards/index.ts | 2 + .../data-access-layer/src/dashboards/types.ts | 10 +++ services/libs/data-access-layer/src/index.ts | 1 + 7 files changed, 113 insertions(+) create mode 100644 backend/src/api/dashboard/dashboardMetricsGet.ts create mode 100644 services/libs/data-access-layer/src/dashboards/base.ts create mode 100644 services/libs/data-access-layer/src/dashboards/index.ts create mode 100644 services/libs/data-access-layer/src/dashboards/types.ts diff --git a/backend/src/api/dashboard/dashboardMetricsGet.ts b/backend/src/api/dashboard/dashboardMetricsGet.ts new file mode 100644 index 0000000000..ddf277da0a --- /dev/null +++ b/backend/src/api/dashboard/dashboardMetricsGet.ts @@ -0,0 +1,12 @@ +import DashboardService from '@/services/dashboardService' + +import Permissions from '../../security/permissions' +import PermissionChecker from '../../services/user/permissionChecker' + +export default async (req, res) => { + new PermissionChecker(req).validateHas(Permissions.values.memberRead) + + const payload = await new DashboardService(req).getMetrics(req.query) + + await req.responseHandler.success(req, res, payload) +} diff --git a/backend/src/api/dashboard/index.ts b/backend/src/api/dashboard/index.ts index 0fd01a65c3..7d370cfe07 100644 --- a/backend/src/api/dashboard/index.ts +++ b/backend/src/api/dashboard/index.ts @@ -2,4 +2,5 @@ import { safeWrap } from '../../middlewares/errorMiddleware' export default (app) => { app.get(`/dashboard`, safeWrap(require('./dashboardGet').default)) + app.get(`/dashboard/metrics`, safeWrap(require('./dashboardMetricsGet').default)) } diff --git a/backend/src/services/dashboardService.ts b/backend/src/services/dashboardService.ts index 8569634fb3..8bf70a2864 100644 --- a/backend/src/services/dashboardService.ts +++ b/backend/src/services/dashboardService.ts @@ -1,6 +1,9 @@ +import { getMetrics } from '@crowd/data-access-layer/src/dashboards' import { RedisCache } from '@crowd/redis' import { DashboardTimeframe } from '@crowd/types' +import SequelizeRepository from '../database/repositories/sequelizeRepository' + import { IServiceOptions } from './IServiceOptions' interface IDashboardQueryParams { @@ -9,6 +12,10 @@ interface IDashboardQueryParams { timeframe: DashboardTimeframe } +interface IDashboardMetricsQueryParams { + segment?: string +} + export default class DashboardService { options: IServiceOptions @@ -75,4 +82,21 @@ export default class DashboardService { return JSON.parse(data) } + + async getMetrics(params: IDashboardMetricsQueryParams) { + try { + const segmentId = params.segment || this.options.currentSegments[0]?.id + + if (!segmentId) { + this.options.log.warn('No segment ID provided for metrics query') + } + + const qx = SequelizeRepository.getQueryExecutor(this.options) + const metrics = await getMetrics(qx, segmentId) + return metrics + } catch (error) { + this.options.log.error('Failed to fetch dashboard metrics', { error, params }) + throw new Error('Unable to fetch dashboard metrics') + } + } } diff --git a/services/libs/data-access-layer/src/dashboards/base.ts b/services/libs/data-access-layer/src/dashboards/base.ts new file mode 100644 index 0000000000..60586320d1 --- /dev/null +++ b/services/libs/data-access-layer/src/dashboards/base.ts @@ -0,0 +1,63 @@ +import { QueryExecutor } from '../queryExecutor' + +import { IDashboardMetrics } from './types' + +export async function getMetrics( + qx: QueryExecutor, + segmentId?: string, +): Promise { + const tableName = segmentId + ? 'dashboardMetricsPerSegmentSnapshot' + : 'dashboardMetricsTotalSnapshot' + + const query = segmentId + ? ` + SELECT * + FROM "${tableName}" + WHERE "segmentId" = $(segmentId) + LIMIT 1 + ` + : ` + SELECT * + FROM "${tableName}" + LIMIT 1 + ` + + const params = segmentId ? { segmentId } : {} + + try { + const [row] = (await qx.select(query, params)) as IDashboardMetrics[] + + if (!row) { + // TODO: remove this mock once Tinybird sinks are available + return getMockMetrics() + } + + return row + } catch (error: any) { + const msg = error?.message ?? '' + + // Detect missing table + const isMissingTable = error?.code === '42P01' || /does not exist/i.test(msg) + + if (isMissingTable) { + // TODO: remove this mock once Tinybird sinks are available + return getMockMetrics() + } + + throw error + } +} + +function getMockMetrics(): IDashboardMetrics { + return { + activitiesTotal: 9926553, + activitiesLast30Days: 64329, + organizationsTotal: 104300, + organizationsLast30Days: 36, + membersTotal: 798730, + membersLast30Days: 2694, + projectsTotal: 123, + projectsLast30Days: 12312, + } +} diff --git a/services/libs/data-access-layer/src/dashboards/index.ts b/services/libs/data-access-layer/src/dashboards/index.ts new file mode 100644 index 0000000000..b097b26bf7 --- /dev/null +++ b/services/libs/data-access-layer/src/dashboards/index.ts @@ -0,0 +1,2 @@ +export * from './base' +export * from './types' diff --git a/services/libs/data-access-layer/src/dashboards/types.ts b/services/libs/data-access-layer/src/dashboards/types.ts new file mode 100644 index 0000000000..a8b3eb1066 --- /dev/null +++ b/services/libs/data-access-layer/src/dashboards/types.ts @@ -0,0 +1,10 @@ +export interface IDashboardMetrics { + activitiesTotal: number + activitiesLast30Days: number + organizationsTotal: number + organizationsLast30Days: number + membersTotal: number + membersLast30Days: number + projectsTotal: number + projectsLast30Days: number +} diff --git a/services/libs/data-access-layer/src/index.ts b/services/libs/data-access-layer/src/index.ts index c7963a6a40..05ac370cca 100644 --- a/services/libs/data-access-layer/src/index.ts +++ b/services/libs/data-access-layer/src/index.ts @@ -1,5 +1,6 @@ export * from './activities' export * from './activityRelations' +export * from './dashboards' export * from './members' export * from './organizations' export * from './prompt-history' From 404a959d082bc732ac999e1f784ed9a56dad8a67 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Thu, 11 Dec 2025 15:54:37 +0100 Subject: [PATCH 11/51] fix: lint --- services/libs/data-access-layer/src/dashboards/base.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/libs/data-access-layer/src/dashboards/base.ts b/services/libs/data-access-layer/src/dashboards/base.ts index 60586320d1..77422da998 100644 --- a/services/libs/data-access-layer/src/dashboards/base.ts +++ b/services/libs/data-access-layer/src/dashboards/base.ts @@ -34,11 +34,12 @@ export async function getMetrics( } return row - } catch (error: any) { - const msg = error?.message ?? '' + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : '' + const code = error && typeof error === 'object' && 'code' in error ? error.code : null // Detect missing table - const isMissingTable = error?.code === '42P01' || /does not exist/i.test(msg) + const isMissingTable = code === '42P01' || /does not exist/i.test(msg) if (isMissingTable) { // TODO: remove this mock once Tinybird sinks are available From 5cdf2e7ebe2bdb899b97e69b39f454b4abbe21f7 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Thu, 11 Dec 2025 16:11:02 +0100 Subject: [PATCH 12/51] fix: adjust return type from query --- backend/src/services/dashboardService.ts | 6 ++---- services/libs/data-access-layer/src/dashboards/base.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/src/services/dashboardService.ts b/backend/src/services/dashboardService.ts index 8bf70a2864..42590e26cd 100644 --- a/backend/src/services/dashboardService.ts +++ b/backend/src/services/dashboardService.ts @@ -85,14 +85,12 @@ export default class DashboardService { async getMetrics(params: IDashboardMetricsQueryParams) { try { - const segmentId = params.segment || this.options.currentSegments[0]?.id - - if (!segmentId) { + if (!params.segment) { this.options.log.warn('No segment ID provided for metrics query') } const qx = SequelizeRepository.getQueryExecutor(this.options) - const metrics = await getMetrics(qx, segmentId) + const metrics = await getMetrics(qx, params.segment) return metrics } catch (error) { this.options.log.error('Failed to fetch dashboard metrics', { error, params }) diff --git a/services/libs/data-access-layer/src/dashboards/base.ts b/services/libs/data-access-layer/src/dashboards/base.ts index 77422da998..9653de35d6 100644 --- a/services/libs/data-access-layer/src/dashboards/base.ts +++ b/services/libs/data-access-layer/src/dashboards/base.ts @@ -26,7 +26,7 @@ export async function getMetrics( const params = segmentId ? { segmentId } : {} try { - const [row] = (await qx.select(query, params)) as IDashboardMetrics[] + const row = await qx.select(query, params) if (!row) { // TODO: remove this mock once Tinybird sinks are available From bcd4407105479dd7138c2073606ea3a2f3069df1 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Thu, 11 Dec 2025 16:22:10 +0100 Subject: [PATCH 13/51] fix: return just first value --- .../libs/data-access-layer/src/dashboards/base.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/services/libs/data-access-layer/src/dashboards/base.ts b/services/libs/data-access-layer/src/dashboards/base.ts index 9653de35d6..6367587dff 100644 --- a/services/libs/data-access-layer/src/dashboards/base.ts +++ b/services/libs/data-access-layer/src/dashboards/base.ts @@ -18,7 +18,14 @@ export async function getMetrics( LIMIT 1 ` : ` - SELECT * + SELECT + "activitiesLast30Days", + "activitiesTotal", + "membersLast30Days", + "membersTotal", + "organizationsLast30Days", + "organizationsTotal", + "updatedAt" FROM "${tableName}" LIMIT 1 ` @@ -26,7 +33,7 @@ export async function getMetrics( const params = segmentId ? { segmentId } : {} try { - const row = await qx.select(query, params) + const [row] = await qx.select(query, params) if (!row) { // TODO: remove this mock once Tinybird sinks are available From d81be1a29ebd94b54cb7e94e5887cefe6ad12049 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Thu, 11 Dec 2025 17:44:37 +0100 Subject: [PATCH 14/51] feat: add project metric --- .../data-access-layer/src/dashboards/base.ts | 99 +++++++++++++++---- 1 file changed, 80 insertions(+), 19 deletions(-) diff --git a/services/libs/data-access-layer/src/dashboards/base.ts b/services/libs/data-access-layer/src/dashboards/base.ts index 6367587dff..2033f643d0 100644 --- a/services/libs/data-access-layer/src/dashboards/base.ts +++ b/services/libs/data-access-layer/src/dashboards/base.ts @@ -6,6 +6,53 @@ export async function getMetrics( qx: QueryExecutor, segmentId?: string, ): Promise { + try { + const [snapshotData, projectsData] = await Promise.all([ + getSnapshotMetrics(qx, segmentId), + getProjectsCount(qx, segmentId), + ]) + + if (!snapshotData) { + // TODO: remove this mock once Tinybird sinks are available + const mockMetrics = getMockMetrics() + return { + ...mockMetrics, + projectsTotal: projectsData.projectsTotal, + projectsLast30Days: projectsData.projectsLast30Days, + } + } + + return { + ...snapshotData, + projectsTotal: projectsData.projectsTotal, + projectsLast30Days: projectsData.projectsLast30Days, + } + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : '' + const code = error && typeof error === 'object' && 'code' in error ? error.code : null + + // Detect missing table + const isMissingTable = code === '42P01' || /does not exist/i.test(msg) + + if (isMissingTable) { + // TODO: remove this mock once Tinybird sinks are available + const mockMetrics = getMockMetrics() + const projectsData = await getProjectsCount(qx, segmentId) + return { + ...mockMetrics, + projectsTotal: projectsData.projectsTotal, + projectsLast30Days: projectsData.projectsLast30Days, + } + } + + throw error + } +} + +async function getSnapshotMetrics( + qx: QueryExecutor, + segmentId?: string, +): Promise | null> { const tableName = segmentId ? 'dashboardMetricsPerSegmentSnapshot' : 'dashboardMetricsTotalSnapshot' @@ -31,29 +78,43 @@ export async function getMetrics( ` const params = segmentId ? { segmentId } : {} + const [row] = await qx.select(query, params) - try { - const [row] = await qx.select(query, params) - - if (!row) { - // TODO: remove this mock once Tinybird sinks are available - return getMockMetrics() - } - - return row - } catch (error: unknown) { - const msg = error instanceof Error ? error.message : '' - const code = error && typeof error === 'object' && 'code' in error ? error.code : null + return row || null +} - // Detect missing table - const isMissingTable = code === '42P01' || /does not exist/i.test(msg) +async function getProjectsCount( + qx: QueryExecutor, + segmentId?: string, +): Promise<{ projectsTotal: number; projectsLast30Days: number }> { + let query: string + let params: Record - if (isMissingTable) { - // TODO: remove this mock once Tinybird sinks are available - return getMockMetrics() - } + if (!segmentId) { + // Count all segments + query = ` + SELECT + COUNT(*) as "projectsTotal", + COUNT(CASE WHEN "createdAt" >= NOW() - INTERVAL '30 days' THEN 1 END) as "projectsLast30Days" + FROM segments + ` + params = {} + } else { + // Count segments where the provided segmentId is current, parent, or grandparent + query = ` + SELECT + COUNT(*) as "projectsTotal", + COUNT(CASE WHEN s."createdAt" >= NOW() - INTERVAL '30 days' THEN 1 END) as "projectsLast30Days" + FROM segments s + WHERE (s.id = $(segmentId) OR s."parentId" = $(segmentId) OR s."grandparentId" = $(segmentId)) + ` + params = { segmentId } + } - throw error + const [result] = await qx.select(query, params) + return { + projectsTotal: parseInt(result.projectsTotal) || 0, + projectsLast30Days: parseInt(result.projectsLast30Days) || 0, } } From fdf2ea0c2188a7b20a01e0d0eeceb8480947873f Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Thu, 11 Dec 2025 17:48:50 +0100 Subject: [PATCH 15/51] fix: lint --- services/libs/data-access-layer/src/dashboards/base.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/libs/data-access-layer/src/dashboards/base.ts b/services/libs/data-access-layer/src/dashboards/base.ts index 2033f643d0..ef1def2929 100644 --- a/services/libs/data-access-layer/src/dashboards/base.ts +++ b/services/libs/data-access-layer/src/dashboards/base.ts @@ -88,7 +88,7 @@ async function getProjectsCount( segmentId?: string, ): Promise<{ projectsTotal: number; projectsLast30Days: number }> { let query: string - let params: Record + let params: Record if (!segmentId) { // Count all segments From 3bc2bda2473cbdd6557d48508f607f8dbb88e9f0 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Thu, 11 Dec 2025 18:27:28 +0100 Subject: [PATCH 16/51] refactor: using semgnets entity --- .../data-access-layer/src/dashboards/base.ts | 36 +----------------- .../data-access-layer/src/segments/index.ts | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/services/libs/data-access-layer/src/dashboards/base.ts b/services/libs/data-access-layer/src/dashboards/base.ts index ef1def2929..dbb724c93b 100644 --- a/services/libs/data-access-layer/src/dashboards/base.ts +++ b/services/libs/data-access-layer/src/dashboards/base.ts @@ -1,4 +1,5 @@ import { QueryExecutor } from '../queryExecutor' +import { getProjectsCount } from '../segments' import { IDashboardMetrics } from './types' @@ -83,41 +84,6 @@ async function getSnapshotMetrics( return row || null } -async function getProjectsCount( - qx: QueryExecutor, - segmentId?: string, -): Promise<{ projectsTotal: number; projectsLast30Days: number }> { - let query: string - let params: Record - - if (!segmentId) { - // Count all segments - query = ` - SELECT - COUNT(*) as "projectsTotal", - COUNT(CASE WHEN "createdAt" >= NOW() - INTERVAL '30 days' THEN 1 END) as "projectsLast30Days" - FROM segments - ` - params = {} - } else { - // Count segments where the provided segmentId is current, parent, or grandparent - query = ` - SELECT - COUNT(*) as "projectsTotal", - COUNT(CASE WHEN s."createdAt" >= NOW() - INTERVAL '30 days' THEN 1 END) as "projectsLast30Days" - FROM segments s - WHERE (s.id = $(segmentId) OR s."parentId" = $(segmentId) OR s."grandparentId" = $(segmentId)) - ` - params = { segmentId } - } - - const [result] = await qx.select(query, params) - return { - projectsTotal: parseInt(result.projectsTotal) || 0, - projectsLast30Days: parseInt(result.projectsLast30Days) || 0, - } -} - function getMockMetrics(): IDashboardMetrics { return { activitiesTotal: 9926553, diff --git a/services/libs/data-access-layer/src/segments/index.ts b/services/libs/data-access-layer/src/segments/index.ts index f0eed84a21..ea368ca4cb 100644 --- a/services/libs/data-access-layer/src/segments/index.ts +++ b/services/libs/data-access-layer/src/segments/index.ts @@ -260,3 +260,40 @@ export async function getGitlabRepoUrlsMappedToOtherSegments( return rows.map((r) => r.url) } + +export async function getProjectsCount( + qx: QueryExecutor, + segmentId?: string, +): Promise<{ projectsTotal: number; projectsLast30Days: number }> { + let query: string + let params: Record + + if (!segmentId) { + // Count all segments not deleted + query = ` + SELECT + COUNT(*) as "projectsTotal", + COUNT(CASE WHEN "createdAt" >= NOW() - INTERVAL '30 days' THEN 1 END) as "projectsLast30Days" + FROM segments + WHERE "deletedAt" IS NULL + ` + params = {} + } else { + // Count segments where the provided segmentId is current, parent, or grandparent + query = ` + SELECT + COUNT(*) as "projectsTotal", + COUNT(CASE WHEN s."createdAt" >= NOW() - INTERVAL '30 days' THEN 1 END) as "projectsLast30Days" + FROM segments s + WHERE s."deletedAt" IS NULL + AND (s.id = $(segmentId) OR s."parentId" = $(segmentId) OR s."grandparentId" = $(segmentId)) + ` + params = { segmentId } + } + + const [result] = await qx.select(query, params) + return { + projectsTotal: parseInt(result.projectsTotal) || 0, + projectsLast30Days: parseInt(result.projectsLast30Days) || 0, + } +} From 220ea4bb1c6168dcc68ca4572aff1df1dbc9afb7 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Thu, 11 Dec 2025 19:05:30 +0100 Subject: [PATCH 17/51] fix: remove deleted at --- services/libs/data-access-layer/src/segments/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/libs/data-access-layer/src/segments/index.ts b/services/libs/data-access-layer/src/segments/index.ts index ea368ca4cb..ee2bf592f8 100644 --- a/services/libs/data-access-layer/src/segments/index.ts +++ b/services/libs/data-access-layer/src/segments/index.ts @@ -275,7 +275,6 @@ export async function getProjectsCount( COUNT(*) as "projectsTotal", COUNT(CASE WHEN "createdAt" >= NOW() - INTERVAL '30 days' THEN 1 END) as "projectsLast30Days" FROM segments - WHERE "deletedAt" IS NULL ` params = {} } else { @@ -285,8 +284,7 @@ export async function getProjectsCount( COUNT(*) as "projectsTotal", COUNT(CASE WHEN s."createdAt" >= NOW() - INTERVAL '30 days' THEN 1 END) as "projectsLast30Days" FROM segments s - WHERE s."deletedAt" IS NULL - AND (s.id = $(segmentId) OR s."parentId" = $(segmentId) OR s."grandparentId" = $(segmentId)) + WHERE (s.id = $(segmentId) OR s."parentId" = $(segmentId) OR s."grandparentId" = $(segmentId)) ` params = { segmentId } } From dbb706ff995ef604459d51189acb89b16654fc4f Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Wed, 3 Dec 2025 17:17:57 +0800 Subject: [PATCH 18/51] chore: initial commit Signed-off-by: Efren Lim --- frontend/src/modules/admin/admin.routes.ts | 53 +-- .../components/fragments/overview-filter.vue | 359 ++++++++++++++++++ .../components/fragments/trend-display.vue | 91 +++++ .../sections/integration-details.vue | 14 + .../sections/integration-status.vue | 14 + .../overview/components/sections/summary.vue | 84 ++++ .../admin/modules/overview/pages/overview.vue | 28 ++ .../modules/overview/store/overview.store.ts | 13 + .../modules/overview/types/overview.types.ts | 5 + frontend/src/router/index.js | 136 ++++--- .../shared/modules/monitoring/types/event.ts | 2 +- frontend/src/ui-kit/index.scss | 2 + .../lfx/dropdown/dropdown-group-title.vue | 15 + .../src/ui-kit/lfx/dropdown/dropdown-item.vue | 63 +++ .../ui-kit/lfx/dropdown/dropdown-search.vue | 62 +++ .../ui-kit/lfx/dropdown/dropdown-select.vue | 101 +++++ .../ui-kit/lfx/dropdown/dropdown-selector.vue | 40 ++ .../lfx/dropdown/dropdown-separator.vue | 13 + .../src/ui-kit/lfx/dropdown/dropdown.scss | 74 ++++ frontend/src/ui-kit/lfx/dropdown/dropdown.vue | 79 ++++ .../lfx/dropdown/types/dropdown.types.ts | 37 ++ frontend/src/ui-kit/lfx/popover/popover.scss | 26 ++ frontend/src/ui-kit/lfx/popover/popover.vue | 217 +++++++++++ .../lfx/popover/types/PopoverPlacement.ts | 18 + .../lfx/popover/types/PopoverTrigger.ts | 5 + frontend/src/utils/responsive.ts | 29 ++ frontend/tailwind.config.js | 73 +--- 27 files changed, 1504 insertions(+), 149 deletions(-) create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/sections/summary.vue create mode 100644 frontend/src/modules/admin/modules/overview/pages/overview.vue create mode 100644 frontend/src/modules/admin/modules/overview/store/overview.store.ts create mode 100644 frontend/src/modules/admin/modules/overview/types/overview.types.ts create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-group-title.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-selector.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown-separator.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown.scss create mode 100644 frontend/src/ui-kit/lfx/dropdown/dropdown.vue create mode 100644 frontend/src/ui-kit/lfx/dropdown/types/dropdown.types.ts create mode 100644 frontend/src/ui-kit/lfx/popover/popover.scss create mode 100644 frontend/src/ui-kit/lfx/popover/popover.vue create mode 100644 frontend/src/ui-kit/lfx/popover/types/PopoverPlacement.ts create mode 100644 frontend/src/ui-kit/lfx/popover/types/PopoverTrigger.ts create mode 100644 frontend/src/utils/responsive.ts diff --git a/frontend/src/modules/admin/admin.routes.ts b/frontend/src/modules/admin/admin.routes.ts index 41e01f1c5a..a008ac88fe 100644 --- a/frontend/src/modules/admin/admin.routes.ts +++ b/frontend/src/modules/admin/admin.routes.ts @@ -1,19 +1,16 @@ -import Layout from '@/modules/layout/components/layout.vue'; -import { PageEventKey } from '@/shared/modules/monitoring/types/event'; -import { PermissionGuard } from '@/shared/modules/permissions/router/PermissionGuard'; -import { LfPermission } from '@/shared/modules/permissions/types/Permissions'; +import Layout from '@/modules/layout/components/layout.vue' +import { PageEventKey } from '@/shared/modules/monitoring/types/event' +import { PermissionGuard } from '@/shared/modules/permissions/router/PermissionGuard' +import { LfPermission } from '@/shared/modules/permissions/types/Permissions' -const ProjectGroupsListPage = () => import( - '@/modules/admin/modules/projects/pages/project-groups-list.page.vue' -); +const OverviewPage = () => import('@/modules/admin/modules/overview/pages/overview.vue') -const ProjectsPage = () => import( - '@/modules/admin/modules/projects/pages/projects.page.vue' -); +// const ProjectGroupsListPage = () => +// import('@/modules/admin/modules/projects/pages/project-groups-list.page.vue') -const AdminPanelPage = () => import( - '@/modules/admin/pages/admin-panel.page.vue' -); +const ProjectsPage = () => import('@/modules/admin/modules/projects/pages/projects.page.vue') + +const AdminPanelPage = () => import('@/modules/admin/pages/admin-panel.page.vue') export default [ { @@ -26,15 +23,25 @@ export default [ }, children: [ { - name: 'projectGroupsList', - path: '/project-groups', - component: ProjectGroupsListPage, + name: 'overview', + path: '/overview', + component: OverviewPage, meta: { auth: true, - title: 'Project Groups', - eventKey: PageEventKey.PROJECT_GROUPS, + title: 'Overview', + eventKey: PageEventKey.OVERVIEW, }, }, + // { + // name: 'projectGroupsList', + // path: '/project-groups', + // component: ProjectGroupsListPage, + // meta: { + // auth: true, + // title: 'Project Groups', + // eventKey: PageEventKey.PROJECT_GROUPS, + // }, + // }, { name: 'adminPanel', path: '/admin', @@ -43,9 +50,7 @@ export default [ title: 'Admin Panel', eventKey: PageEventKey.ADMIN_PANEL, }, - beforeEnter: [ - PermissionGuard(LfPermission.projectGroupEdit), - ], + beforeEnter: [PermissionGuard(LfPermission.projectGroupEdit)], }, { name: 'adminProjects', @@ -60,10 +65,8 @@ export default [ parameter: 'id', }, }, - beforeEnter: [ - PermissionGuard(LfPermission.projectEdit), - ], + beforeEnter: [PermissionGuard(LfPermission.projectEdit)], }, ], }, -]; +] diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue new file mode 100644 index 0000000000..60a435389a --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue @@ -0,0 +1,359 @@ + + + + + + + diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue b/frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue new file mode 100644 index 0000000000..9dfd2f3449 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue new file mode 100644 index 0000000000..9e36fd84fd --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue new file mode 100644 index 0000000000..714ace7613 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue new file mode 100644 index 0000000000..6f541fe008 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue @@ -0,0 +1,84 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/pages/overview.vue b/frontend/src/modules/admin/modules/overview/pages/overview.vue new file mode 100644 index 0000000000..73de99f8b8 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/pages/overview.vue @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/store/overview.store.ts b/frontend/src/modules/admin/modules/overview/store/overview.store.ts new file mode 100644 index 0000000000..e3270bc786 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/store/overview.store.ts @@ -0,0 +1,13 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { Project, ProjectGroup } from '@/modules/lf/segments/types/Segments' + +export const useOverviewStore = defineStore('overview', () => { + const selectedProjectGroup = ref(null) + const selectedProject = ref(null) + + return { + selectedProjectGroup, + selectedProject, + } +}) diff --git a/frontend/src/modules/admin/modules/overview/types/overview.types.ts b/frontend/src/modules/admin/modules/overview/types/overview.types.ts new file mode 100644 index 0000000000..42196fdf61 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/types/overview.types.ts @@ -0,0 +1,5 @@ +export interface OverviewTrends { + current: number + previous: number + period: string +} diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 446e5a2029..1707c6bab6 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -1,16 +1,13 @@ -import { - createRouter as createVueRouter, - createWebHistory, -} from 'vue-router'; -import { storeToRefs } from 'pinia'; - -import { store } from '@/store'; -import authGuards from '@/middleware/auth'; -import modules from '@/modules'; -import ProgressBar from '@/shared/progress-bar/progress-bar'; -import { useLfSegmentsStore } from '@/modules/lf/segments/store'; -import auth from '@/modules/auth'; -import navigationGuard from '@/middleware/navigation/navigation-guard'; +import { createRouter as createVueRouter, createWebHistory } from 'vue-router' +import { storeToRefs } from 'pinia' + +import { store } from '@/store' +import authGuards from '@/middleware/auth' +import modules from '@/modules' +import ProgressBar from '@/shared/progress-bar/progress-bar' +import { useLfSegmentsStore } from '@/modules/lf/segments/store' +import auth from '@/modules/auth' +import navigationGuard from '@/middleware/navigation/navigation-guard' /** * Loads all the routes from src/modules/ folders, and adds the catch-all rule to handle 404s @@ -20,25 +17,27 @@ import navigationGuard from '@/middleware/navigation/navigation-guard'; const routes = [ { path: '', - redirect: '/project-groups', + redirect: '/overview', }, ...auth.routes, ...Object.keys(modules) .filter((key) => Boolean(modules[key].routes)) - .map((key) => modules[key].routes.map((r) => { - // eslint-disable-next-line no-param-reassign - r.meta = { - ...r.meta, - middleware: [...authGuards], - }; - return r; - })) + .map((key) => + modules[key].routes.map((r) => { + // eslint-disable-next-line no-param-reassign + r.meta = { + ...r.meta, + middleware: [...authGuards], + } + return r + }), + ) .reduce((a, b) => a.concat(b), []), { path: '/:catchAll(.*)', redirect: '/404' }, -]; +] // eslint-disable-next-line import/no-mutable-exports -let router; +let router /** * Creates/Sets Router @@ -50,65 +49,66 @@ export const createRouter = () => { history: createWebHistory(), routes, scrollBehavior() { - return { x: 0, y: 0 }; + return { x: 0, y: 0 } }, - }); + }) - const originalPush = router.push; + const originalPush = router.push router.push = function push(location) { - return originalPush - .call(this, location) - .catch((error) => { - console.error(error); - ProgressBar.done(); - }); - }; + return originalPush.call(this, location).catch((error) => { + console.error(error) + ProgressBar.done() + }) + } router.beforeEach(async (to, from, next) => { - const lsSegmentsStore = useLfSegmentsStore(); - const { selectedProjectGroup } = storeToRefs(lsSegmentsStore); - const { listProjectGroups, updateSelectedProjectGroup } = lsSegmentsStore; + const lsSegmentsStore = useLfSegmentsStore() + const { selectedProjectGroup } = storeToRefs(lsSegmentsStore) + const { listProjectGroups, updateSelectedProjectGroup } = lsSegmentsStore // Set title to pages - document.title = `LFX Community Data Platform${to.meta.title ? ` | ${to.meta.title}` : ''}`; + document.title = `LFX Community Data Platform${to.meta.title ? ` | ${to.meta.title}` : ''}` if (to.name && to.query.menu === from.query.menu && to.name !== from.name) { - ProgressBar.start(); + ProgressBar.start() } - const matchedRoute = to.matched.find( - (m) => m.meta.middleware, - ); + const matchedRoute = to.matched.find((m) => m.meta.middleware) if (matchedRoute !== undefined) { - const middlewareArray = Array.isArray( - matchedRoute.meta.middleware, - ) + const middlewareArray = Array.isArray(matchedRoute.meta.middleware) ? matchedRoute.meta.middleware - : [matchedRoute.meta.middleware]; + : [matchedRoute.meta.middleware] const context = { from, router, to, store, - }; + } await middlewareArray.forEach(async (middleware) => { - await middleware(context); - }); + await middleware(context) + }) // Redirect to project group landing pages if routes that require a selected project group // And no project group is selected - if (to.meta.segments?.requireSelectedProjectGroup || to.meta.segments?.optionalSelectedProjectGroup) { - if (!selectedProjectGroup.value && !to.query.projectGroup && !to.meta.segments?.optionalSelectedProjectGroup) { - next('/project-groups'); - return; + if ( + to.meta.segments?.requireSelectedProjectGroup || + to.meta.segments?.optionalSelectedProjectGroup + ) { + if ( + !selectedProjectGroup.value && + !to.query.projectGroup && + !to.meta.segments?.optionalSelectedProjectGroup + ) { + next('/project-groups') + return } if (!to.query.projectGroup && selectedProjectGroup.value?.id) { - next({ ...to, query: { ...to.query, projectGroup: selectedProjectGroup.value?.id } }); - return; + next({ ...to, query: { ...to.query, projectGroup: selectedProjectGroup.value?.id } }) + return } if (!selectedProjectGroup.value) { @@ -116,29 +116,27 @@ export const createRouter = () => { await listProjectGroups({ limit: null, reset: true, - }); + }) - updateSelectedProjectGroup(to.query.projectGroup, false); + updateSelectedProjectGroup(to.query.projectGroup, false) } catch (e) { - next('/project-groups'); - return; + next('/project-groups') + return } } } } - next(); - }); + next() + }) router.afterEach(async (to) => { - ProgressBar.done(); - await navigationGuard({ to }); - }); + ProgressBar.done() + await navigationGuard({ to }) + }) } - return router; -}; + return router +} -export { - router, -}; +export { router } diff --git a/frontend/src/shared/modules/monitoring/types/event.ts b/frontend/src/shared/modules/monitoring/types/event.ts index b54b34a471..89cbed85e0 100644 --- a/frontend/src/shared/modules/monitoring/types/event.ts +++ b/frontend/src/shared/modules/monitoring/types/event.ts @@ -13,8 +13,8 @@ export enum EventType { } export enum PageEventKey { - PROJECT_GROUPS = 'Project groups', OVERVIEW = 'Overview', + PROJECT_GROUPS = 'Project groups', MEMBERS = 'Contributors', ORGANIZATIONS = 'Organizations', ACTIVITIES = 'Activities', diff --git a/frontend/src/ui-kit/index.scss b/frontend/src/ui-kit/index.scss index ac1b2a8bba..400823aa34 100644 --- a/frontend/src/ui-kit/index.scss +++ b/frontend/src/ui-kit/index.scss @@ -22,4 +22,6 @@ @import 'timeline/timeline'; @import 'tooltip/tooltip'; @import 'tag/tag'; +@import 'lfx/dropdown/dropdown'; +@import 'lfx/popover/popover'; diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-group-title.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-group-title.vue new file mode 100644 index 0000000000..cdccb3cb46 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-group-title.vue @@ -0,0 +1,15 @@ + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue new file mode 100644 index 0000000000..f8b77de572 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue @@ -0,0 +1,63 @@ + + + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue new file mode 100644 index 0000000000..70f0cb6169 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue @@ -0,0 +1,62 @@ + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue new file mode 100644 index 0000000000..87b01a3b82 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue @@ -0,0 +1,101 @@ + + + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-selector.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-selector.vue new file mode 100644 index 0000000000..efc117e626 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-selector.vue @@ -0,0 +1,40 @@ + + + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-separator.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-separator.vue new file mode 100644 index 0000000000..92421509ee --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-separator.vue @@ -0,0 +1,13 @@ + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown.scss b/frontend/src/ui-kit/lfx/dropdown/dropdown.scss new file mode 100644 index 0000000000..0314cce6e1 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown.scss @@ -0,0 +1,74 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +.lfx-c-dropdown { + @apply bg-white border border-neutral-200 rounded-lg p-1 flex flex-col gap-1 max-h-96 shadow-lg; + + //@screen sm{ + // @apply ; + //} + + &__item { + @apply bg-white py-2 px-3 flex items-center gap-2 text-sm leading-5 cursor-pointer transition-all rounded-md; + + &:hover { + @apply bg-neutral-50; + } + + .c-icon { + @apply text-neutral-500; + } + + &.is-selected { + @apply font-semibold; + + .c-icon { + @apply text-neutral-900; + } + } + } + + &__separator { + @apply w-full border-b border-neutral-100 h-0; + } + + &__group-title { + @apply px-3 pt-1 text-xs font-semibold leading-5 text-neutral-400; + } + + &__selector { + @apply flex items-center gap-2 text-sm leading-5 font-medium transition-all; + @apply px-3 py-2 cursor-pointer transition-all rounded-lg select-none; + + &:hover { + @apply bg-neutral-50; + } + + &--small { + @apply px-2 py-1 text-xs gap-1.5 rounded-md; + } + + &--filled { + @apply bg-white outline outline-1 outline-neutral-200 shadow-xs; + } + } + + &__sub { + @apply relative; + + &-menu { + @apply absolute left-full top-0 z-10 ml-3 overflow-auto; + } + } +} + +.dropdown-popover.c-popover__content { + z-index: 9; +} + +.is-open { + .lfx-c-dropdown { + &__selector { + @apply bg-neutral-50; + } + } +} diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown.vue new file mode 100644 index 0000000000..e1ca4c1394 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown.vue @@ -0,0 +1,79 @@ + + + + + + diff --git a/frontend/src/ui-kit/lfx/dropdown/types/dropdown.types.ts b/frontend/src/ui-kit/lfx/dropdown/types/dropdown.types.ts new file mode 100644 index 0000000000..299a6b6497 --- /dev/null +++ b/frontend/src/ui-kit/lfx/dropdown/types/dropdown.types.ts @@ -0,0 +1,37 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +export interface DropdownOption { + label: string; + value: string; + description?: string; +} + +export interface DropdownGroupOptions { + label: string; + items: DropdownOption[]; +} + +export const dropdownSizes = ['default', 'small'] as const; +export const dropdownTypes = ['filled', 'transparent'] as const; + +export type DropdownSize = (typeof dropdownSizes)[number]; +export type DropdownType = (typeof dropdownTypes)[number]; + +export interface DropdownProps { + modelValue?: string; + options: DropdownOption[] | DropdownGroupOptions[]; + dropdownIcon?: string; + placeholder?: string; + disabled?: boolean; + type?: DropdownType; + size?: DropdownSize; + showFilter?: boolean; + showGroupBreaks?: boolean; + icon?: string; + fullWidth?: boolean; + center?: boolean; + prefix?: string; + dropdownPosition?: 'left' | 'right'; + iconOnlyMobile?: boolean; + splitLines?: number[]; // index of the lines to split the dropdown +} diff --git a/frontend/src/ui-kit/lfx/popover/popover.scss b/frontend/src/ui-kit/lfx/popover/popover.scss new file mode 100644 index 0000000000..fe9b5feef4 --- /dev/null +++ b/frontend/src/ui-kit/lfx/popover/popover.scss @@ -0,0 +1,26 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +.lfx-c-popover { + &__trigger { + @apply w-fit; + } + + &__content { + @apply z-50 w-max overflow-visible; + width: auto !important; + + &.is-hidden { + @apply invisible opacity-0 pointer-events-none; + } + + &.is-modal { + background: linear-gradient(to bottom, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.25)); + @apply fixed top-0 left-0 h-full w-full transform translate-x-0 translate-y-0 p-12 pt-16 px-5 #{!important}; + @apply flex flex-wrap justify-center items-start; + + & > div { + @apply w-full max-h-full #{!important}; + } + } + } +} diff --git a/frontend/src/ui-kit/lfx/popover/popover.vue b/frontend/src/ui-kit/lfx/popover/popover.vue new file mode 100644 index 0000000000..f25041c78b --- /dev/null +++ b/frontend/src/ui-kit/lfx/popover/popover.vue @@ -0,0 +1,217 @@ + + + + + + diff --git a/frontend/src/ui-kit/lfx/popover/types/PopoverPlacement.ts b/frontend/src/ui-kit/lfx/popover/types/PopoverPlacement.ts new file mode 100644 index 0000000000..d07ed35dd7 --- /dev/null +++ b/frontend/src/ui-kit/lfx/popover/types/PopoverPlacement.ts @@ -0,0 +1,18 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +export const popoverPlacements = [ + 'bottom-start', + 'bottom', + 'bottom-end', + 'top', + 'top-start', + 'top-end', + 'left', + 'left-start', + 'left-end', + 'right', + 'right-start', + 'right-end', +] as const; + +export type PopoverPlacement = (typeof popoverPlacements)[number]; diff --git a/frontend/src/ui-kit/lfx/popover/types/PopoverTrigger.ts b/frontend/src/ui-kit/lfx/popover/types/PopoverTrigger.ts new file mode 100644 index 0000000000..f395b93ecf --- /dev/null +++ b/frontend/src/ui-kit/lfx/popover/types/PopoverTrigger.ts @@ -0,0 +1,5 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +export const popoverTrigger = ['click', 'hover'] as const; + +export type PopoverTrigger = (typeof popoverTrigger)[number]; diff --git a/frontend/src/utils/responsive.ts b/frontend/src/utils/responsive.ts new file mode 100644 index 0000000000..78ecab918b --- /dev/null +++ b/frontend/src/utils/responsive.ts @@ -0,0 +1,29 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +import { onMounted, onUnmounted, ref } from 'vue'; + +const useResponsive = () => { + const pageWidth = ref(0); + + const updatePageWidth = () => { + pageWidth.value = window.innerWidth; + }; + + const isMobileOrTablet = () => /Mobi|Android|iPhone|iPad|iPod/i.test(navigator?.userAgent); + + onMounted(() => { + updatePageWidth(); + window.addEventListener('resize', updatePageWidth); + }); + + onUnmounted(() => { + window.removeEventListener('resize', updatePageWidth); + }); + + return { + isMobileOrTablet, + pageWidth, + }; +}; + +export default useResponsive; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 9483bb6125..4787e63d16 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,10 +1,9 @@ /* eslint-disable */ -const { getThemeReplacementsValues } = require('./.tailwind/colorConverter.js'); +const { getThemeReplacementsValues } = require('./.tailwind/colorConverter.js') // tailwind.config.js const plugin = require('tailwindcss/plugin') - -const themeReplacements = getThemeReplacementsValues(); +const themeReplacements = getThemeReplacementsValues() const spacing = { 0.5: '0.125rem', @@ -63,7 +62,7 @@ const spacing = { 120: '30rem', 148: '37rem', 254: '63.5rem', -}; +} /** @type {import('tailwindcss').Config} */ module.exports = { @@ -189,12 +188,12 @@ module.exports = { 3: '3px', }, boxShadow: { - DEFAULT: - '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', + DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)', lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)', xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)', '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)', + xs: '0 1px 2px -1px rgba(0, 0, 0, 0.10), 0 1px 3px 0 rgba(0, 0, 0, 0.10)', }, fontSize: { '4xs': ['0.5rem'], @@ -209,50 +208,17 @@ module.exports = { '5xl': ['4rem'], '8xl': ['8rem'], '10xl': ['10rem'], - h1: [ - 'var(--lf-heading-1-font-size)', - 'var(--lf-heading-1-line-height)', - ], - h2: [ - 'var(--lf-heading-2-font-size)', - 'var(--lf-heading-2-line-height)', - ], - h3: [ - 'var(--lf-heading-3-font-size)', - 'var(--lf-heading-3-line-height)', - ], - h4: [ - 'var(--lf-heading-4-font-size)', - 'var(--lf-heading-4-line-height)', - ], - h5: [ - 'var(--lf-heading-5-font-size)', - 'var(--lf-heading-5-line-height)', - ], - h6: [ - 'var(--lf-heading-6-font-size)', - 'var(--lf-heading-6-line-height)', - ], - xtiny: [ - 'var(--lf-text-xtiny-font-size)', - 'var(--lf-text-xtiny-line-height)', - ], - tiny: [ - 'var(--lf-text-tiny-font-size)', - 'var(--lf-text-tiny-line-height)', - ], - small: [ - 'var(--lf-text-small-font-size)', - 'var(--lf-text-small-line-height)', - ], - medium: [ - 'var(--lf-text-medium-font-size)', - 'var(--lf-text-medium-line-height)', - ], - large: [ - 'var(--lf-text-large-font-size)', - 'var(--lf-text-large-line-height)', - ], + h1: ['var(--lf-heading-1-font-size)', 'var(--lf-heading-1-line-height)'], + h2: ['var(--lf-heading-2-font-size)', 'var(--lf-heading-2-line-height)'], + h3: ['var(--lf-heading-3-font-size)', 'var(--lf-heading-3-line-height)'], + h4: ['var(--lf-heading-4-font-size)', 'var(--lf-heading-4-line-height)'], + h5: ['var(--lf-heading-5-font-size)', 'var(--lf-heading-5-line-height)'], + h6: ['var(--lf-heading-6-font-size)', 'var(--lf-heading-6-line-height)'], + xtiny: ['var(--lf-text-xtiny-font-size)', 'var(--lf-text-xtiny-line-height)'], + tiny: ['var(--lf-text-tiny-font-size)', 'var(--lf-text-tiny-line-height)'], + small: ['var(--lf-text-small-font-size)', 'var(--lf-text-small-line-height)'], + medium: ['var(--lf-text-medium-font-size)', 'var(--lf-text-medium-line-height)'], + large: ['var(--lf-text-large-font-size)', 'var(--lf-text-large-line-height)'], }, letterSpacing: { 1: '0.0625rem', @@ -288,8 +254,7 @@ module.exports = { transitionProperty: { DEFAULT: 'color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter', - colors: - 'color, background-color, border-color, text-decoration-color, fill, stroke', + colors: 'color, background-color, border-color, text-decoration-color, fill, stroke', }, width: { fit: 'fit-content', @@ -315,7 +280,7 @@ module.exports = { '.overflow-y-unset': { 'overflow-y': 'unset', }, - }); + }) }), ], -}; +} From dcd5758d3640e7092cef2477d02623738220f406 Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Thu, 4 Dec 2025 13:53:55 +0800 Subject: [PATCH 19/51] chore: added project filter Signed-off-by: Efren Lim --- .../components/fragments/load-more.vue | 87 +++++ .../components/fragments/overview-filter.vue | 359 ------------------ .../components/fragments/project-filter.vue | 134 +++++++ .../fragments/project-group-filter.vue | 188 +++++++++ .../components/sections/overview-filter.vue | 27 ++ .../admin/modules/overview/pages/overview.vue | 2 +- .../modules/layout/components/menu/menu.vue | 6 +- 7 files changed, 440 insertions(+), 363 deletions(-) create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/load-more.vue delete mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/load-more.vue b/frontend/src/modules/admin/modules/overview/components/fragments/load-more.vue new file mode 100644 index 0000000000..dc67e31c58 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/load-more.vue @@ -0,0 +1,87 @@ + + + + + + diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue deleted file mode 100644 index 60a435389a..0000000000 --- a/frontend/src/modules/admin/modules/overview/components/fragments/overview-filter.vue +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - - diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue new file mode 100644 index 0000000000..ab835501f4 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue new file mode 100644 index 0000000000..9abcc37759 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue b/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue new file mode 100644 index 0000000000..95df6f6bb5 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/frontend/src/modules/admin/modules/overview/pages/overview.vue b/frontend/src/modules/admin/modules/overview/pages/overview.vue index 73de99f8b8..d436144264 100644 --- a/frontend/src/modules/admin/modules/overview/pages/overview.vue +++ b/frontend/src/modules/admin/modules/overview/pages/overview.vue @@ -18,7 +18,7 @@ import AppLfOverviewSummary from '@/modules/admin/modules/overview/components/sections/summary.vue'; import AppLfOverviewIntegrationStatus from '@/modules/admin/modules/overview/components/sections/integration-status.vue'; import AppLfOverviewIntegrationDetails from '@/modules/admin/modules/overview/components/sections/integration-details.vue'; -import AppLfOverviewFilter from '@/modules/admin/modules/overview/components/fragments/overview-filter.vue'; +import AppLfOverviewFilter from '@/modules/admin/modules/overview/components/sections/overview-filter.vue'; + + diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue index ab835501f4..3a51c3585f 100644 --- a/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue +++ b/frontend/src/modules/admin/modules/overview/components/fragments/project-filter.vue @@ -17,7 +17,7 @@ >
diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue index 714ace7613..c3e677f062 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue @@ -1,10 +1,118 @@ + + diff --git a/frontend/src/ui-kit/lfx/chip/types/chip.types.ts b/frontend/src/ui-kit/lfx/chip/types/chip.types.ts new file mode 100644 index 0000000000..ab2fc6e994 --- /dev/null +++ b/frontend/src/ui-kit/lfx/chip/types/chip.types.ts @@ -0,0 +1,6 @@ +// Copyright (c) 2025 The Linux Foundation and each contributor. +// SPDX-License-Identifier: MIT +export const chipSizes = ['small', 'default'] as const; +export const chipTypes = ['bordered', 'default'] as const; +export type ChipSize = (typeof chipSizes)[number]; +export type ChipType = (typeof chipTypes)[number]; From 4dc78d7fc298a6831d3006a604eb6aaca23e903b Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Thu, 4 Dec 2025 14:42:47 +0800 Subject: [PATCH 21/51] chore: added integration details with mock data Signed-off-by: Efren Lim --- .../sections/integration-details.vue | 263 +++++++++++++++++- 1 file changed, 260 insertions(+), 3 deletions(-) diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index 9e36fd84fd..4db28d8f3f 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -1,14 +1,271 @@ \ No newline at end of file + + + \ No newline at end of file From df239fb8be245617f9e95346e13e281d14b56dfb Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Fri, 5 Dec 2025 15:31:46 +0800 Subject: [PATCH 22/51] chore: changed integration row display to match integration page Signed-off-by: Efren Lim --- .../pages/integration-status.page.vue | 1 + .../components/fragments/integration-row.vue | 103 ++++++++ .../components/fragments/integration-tabs.vue | 52 ++++ .../components/fragments/status-display.vue | 29 +++ .../sections/integration-details.vue | 229 ++---------------- .../overview/store/mock-overview-data.ts | 118 +++++++++ .../modules/overview/types/overview.types.ts | 30 +++ 7 files changed, 351 insertions(+), 211 deletions(-) create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/integration-row.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue create mode 100644 frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue create mode 100644 frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts diff --git a/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue b/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue index 5713743ebb..b8429a554d 100644 --- a/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue +++ b/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue @@ -213,6 +213,7 @@ const fetchGlobalIntegrations = () => { status: lfIntegrationStatusesTabs[status.value].statuses, }) .then((res) => { + console.log(res); if (res.offset > 0) { integrations.value = [...integrations.value, ...res.rows]; } else { diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/integration-row.vue b/frontend/src/modules/admin/modules/overview/components/fragments/integration-row.vue new file mode 100644 index 0000000000..5e15154cfd --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/integration-row.vue @@ -0,0 +1,103 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue b/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue new file mode 100644 index 0000000000..974ca1cee4 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue @@ -0,0 +1,52 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue b/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue new file mode 100644 index 0000000000..a852a4b102 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index 4db28d8f3f..1523c569b0 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -7,83 +7,23 @@ - - - - - - - - - - - +
-
-
Integration
-
Project
-
Status
-
+
+
Integration
+
Project
+
Status
-
- -
-
- -
- {{ integration.platform }} -
- - -
-
{{ integration.projectName }}
-
{{ integration.projectPath }}
-
- - -
-
- - {{ integration.status.label }} -
-
{{ integration.status.description }}
-
- - -
- -
-
+
@@ -98,112 +38,13 @@ - \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts b/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts new file mode 100644 index 0000000000..5dbbc8c213 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts @@ -0,0 +1,118 @@ +import { IntegrationStatus } from '../types/overview.types' +export const mockOverviewData: IntegrationStatus[] = [ + // In Progress integrations + { + grandparentId: 'd004b040-95d2-449b-b5a3-8e789418cc88', + grandparentName: 'Academy Software Foundation (ASWF)', + id: 'a92494e5-268e-44f1-bffc-f55240be77ed', + name: 'Academy Software Foundation (ASWF)', + parentId: '9d8f1f72-4d12-4502-8f2e-c2b8348c8209', + parentName: 'Academy Software Foundation (ASWF)', + platform: 'git', + segmentId: '8eb1d4d5-2536-435b-980c-f11e30943025', + settings: { remotes: ['https://github.com/AcademySoftwareFoundation/tac'] }, + status: 'in-progress', + statusDetails: '100 out of 1,000 data streams processed...', + }, + { + grandparentId: 'e005c041-96d3-450c-c6a4-9e890519dd89', + grandparentName: 'Academy Software Foundation', + id: 'b93595f6-369f-45g2-cgd4-g66351cf88fe', + name: 'Asset Repository Working Group', + parentId: 'ae9g2g73-5e13-4603-9g3f-d3c9459d9310', + parentName: 'Academy Software Foundation', + platform: 'stackoverflow', + segmentId: '9fc2e5e6-3647-546c-a91d-g22f41a54136', + settings: { tags: ['academy-software-foundation', 'asset-repository'] }, + status: 'in-progress', + statusDetails: '600 data streams being processed...', + }, + { + grandparentId: 'f106d142-a7e4-561d-d7b5-af901620ee90', + grandparentName: 'Academy Software Foundation', + id: 'ca4696g7-470g-46h3-dhf5-h77462dg99gf', + name: 'MaterialX', + parentId: 'bg0h3h84-6f24-5714-ah4g-e4da570ea421', + parentName: 'Academy Software Foundation', + platform: 'git', + segmentId: 'a0d3f6f7-4758-657d-ba2e-h33g52b65247', + settings: { remotes: ['https://github.com/AcademySoftwareFoundation/MaterialX'] }, + status: 'in-progress', + statusDetails: '600 data streams being processed...', + }, + { + grandparentId: 'g207e253-b8f5-672e-e8c6-bg012731ff01', + grandparentName: 'Academy Software Foundation', + id: 'db5707h8-581h-57i4-eig6-i88573eh00hg', + name: 'OpenAssetIO', + parentId: 'ch1i4i95-7g35-6825-bi5h-f5eb681fb532', + parentName: 'Academy Software Foundation', + platform: 'gitlab', + segmentId: 'b1e4g7g8-5869-768e-cb3f-i44h63c76358', + settings: { remotes: ['https://gitlab.com/AcademySoftwareFoundation/OpenAssetIO'] }, + status: 'in-progress', + statusDetails: '600 data streams being processed...', + }, + // Action Required integrations + { + grandparentId: 'h308f364-c9g6-783f-f9d7-ch123842gg12', + grandparentName: 'Academy Software Foundation', + id: 'ec6818i9-692i-68j5-fjh7-j99684fi11ih', + name: 'Community Management', + parentId: 'di2j5ja6-8h46-7936-cj6i-g6fc792gc643', + parentName: 'Academy Software Foundation', + platform: 'slack', + segmentId: 'c2f5h8h9-6970-879f-dc4g-j55i74d87469', + settings: { workspaceId: 'T0123456789', channels: ['general', 'community'] }, + status: 'mapping', + statusDetails: 'API key expired, please update credentials', + }, + { + grandparentId: 'i409g475-da07-894g-ga08-di234953hh23', + grandparentName: 'Academy Software Foundation', + id: 'fd7929ja-7a3j-79k6-gki8-ka0795gj22ji', + name: 'Developer Relations', + parentId: 'ej3k6kb7-9i57-8a47-dk7j-h7gd8a3hd754', + parentName: 'Academy Software Foundation', + platform: 'github', + segmentId: 'd3g6i9ia-7a81-98ag-ed5h-k66j85e98570', + settings: { guildId: '123456789012345678', channels: ['dev-chat', 'announcements'] }, + status: 'mapping', + statusDetails: 'Permission denied, check bot permissions', + }, + // Connection Failed integrations + { + grandparentId: 'j50ah586-eb18-9a5h-hb19-ej345064ii34', + grandparentName: 'Academy Software Foundation', + id: 'ge8a3akb-8b4k-8al7-hkj9-lb1806hk33kj', + name: 'Project Management', + parentId: 'fk4l7lc8-aj68-9b58-el8k-i8he9b4ie865', + parentName: 'Academy Software Foundation', + platform: 'jira', + segmentId: 'e4h7jajb-8b92-a9bh-fe6i-l77k96f9a681', + settings: { baseUrl: 'https://academysoftware.atlassian.net', projectKeys: ['PM', 'ASWF'] }, + status: 'error', + statusDetails: 'Unable to connect to server', + }, +] + +export const mockOverviewTabs = [ + { + label: 'In progress', + key: 'in-progress', + count: 4, + icon: 'clock', + }, + { + label: 'Action required', + key: 'action-required', + count: 2, + icon: 'triangle-exclamation', + }, + { + label: 'Connection failed', + key: 'connection-failed', + count: 1, + icon: 'circle-exclamation', + }, +] diff --git a/frontend/src/modules/admin/modules/overview/types/overview.types.ts b/frontend/src/modules/admin/modules/overview/types/overview.types.ts index 42196fdf61..a67bbf3def 100644 --- a/frontend/src/modules/admin/modules/overview/types/overview.types.ts +++ b/frontend/src/modules/admin/modules/overview/types/overview.types.ts @@ -1,5 +1,35 @@ +// import { IntegrationConfig } from '@/config/integrations' +// import { IntegrationStatusConfig } from '../../integration/config/status' export interface OverviewTrends { current: number previous: number period: string } + +export interface IntegrationTabs { + label: string + key: string + count: number + icon: string +} + +// TODO: Check with backend team about the data structure +export interface IntegrationStatus { + grandparentId: string + grandparentName: string + id: string + name: string + parentId: string + parentName: string + platform: string + segmentId: string + settings: any + status: string + + // integration: IntegrationConfig + // projectGroupName: string + // projectName: string + // projectPath: string + // status: IntegrationStatusConfig + statusDetails: string +} From 20a17f6e969b480e704313d02af557df7752e175 Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Fri, 5 Dec 2025 17:25:56 +0800 Subject: [PATCH 23/51] chore: wire integration tab to api Signed-off-by: Efren Lim --- .../pages/integration-status.page.vue | 1 - .../fragments/integrations-filter.vue | 78 ++++------ .../components/fragments/status-display.vue | 6 +- .../sections/integration-details.vue | 40 +++++- .../sections/integration-status.vue | 136 ++++++++---------- .../overview/services/overview.api.service.ts | 42 ++++++ .../overview/store/mock-overview-data.ts | 21 --- .../modules/overview/store/overview.store.ts | 2 + .../modules/overview/types/overview.types.ts | 11 +- frontend/src/shared/types/tanstack.ts | 17 +-- 10 files changed, 178 insertions(+), 176 deletions(-) create mode 100644 frontend/src/modules/admin/modules/overview/services/overview.api.service.ts diff --git a/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue b/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue index b8429a554d..5713743ebb 100644 --- a/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue +++ b/frontend/src/modules/admin/modules/integration/pages/integration-status.page.vue @@ -213,7 +213,6 @@ const fetchGlobalIntegrations = () => { status: lfIntegrationStatusesTabs[status.value].statuses, }) .then((res) => { - console.log(res); if (res.offset > 0) { integrations.value = [...integrations.value, ...res.rows]; } else { diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/integrations-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/integrations-filter.vue index 5958594850..bb058b67dd 100644 --- a/frontend/src/modules/admin/modules/overview/components/fragments/integrations-filter.vue +++ b/frontend/src/modules/admin/modules/overview/components/fragments/integrations-filter.vue @@ -1,6 +1,6 @@ @@ -34,25 +30,26 @@
+ v-for="(integration, key) in lfIntegrations()" + :key="key" + :selected="selectedIntegrationId === key" + @click="selectedIntegrationId = key" + > +
+ + {{ integration.name }} +
+
+ @@ -66,37 +63,18 @@ import LfxDropdownSelect from '@/ui-kit/lfx/dropdown/dropdown-select.vue'; import LfxDropdownSelector from '@/ui-kit/lfx/dropdown/dropdown-selector.vue'; import LfxDropdownItem from '@/ui-kit/lfx/dropdown/dropdown-item.vue'; import { useOverviewStore } from '@/modules/admin/modules/overview/store/overview.store'; +import { lfIntegrations } from '@/config/integrations'; import { storeToRefs } from 'pinia'; + const overviewStore = useOverviewStore(); const { selectedIntegrationId } = storeToRefs(overviewStore); -interface Integration { - id: string; - name: string; -} - -const props = defineProps<{ - integrations: Integration[]; -}>(); - -// Mock data for integrations (can be replaced with props.integrations) -const mockIntegrations: Integration[] = [ - { id: 'github', name: 'GitHub' }, - { id: 'slack', name: 'Slack' }, - { id: 'discord', name: 'Discord' }, - { id: 'linkedin', name: 'LinkedIn' }, - { id: 'twitter', name: 'Twitter' }, -]; - -const selectIntegration = (integrationId: string) => { - selectedIntegrationId.value = integrationId; -}; +const integrations = computed(() => lfIntegrations()); const selectedIntegration = computed(() => { - if (selectedIntegrationId.value === 'all') return null; - const integrations = props.integrations || mockIntegrations; - return integrations.find(i => i.id === selectedIntegrationId.value) || null; + if (selectedIntegrationId.value === 'all' || !selectedIntegrationId.value) return null; + return integrations.value[selectedIntegrationId.value]; }); diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue b/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue index a852a4b102..d594513929 100644 --- a/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue +++ b/frontend/src/modules/admin/modules/overview/components/fragments/status-display.vue @@ -4,7 +4,8 @@ {{ status.status.text }}
@@ -23,7 +24,4 @@ const props = defineProps<{ }>(); const status = computed(() => getIntegrationStatus(props.integrationStatus)); -const statusIconClass = computed(() => { - return status.value.status.color + (status.value.key === 'in-progress' ? ' animate-spin' : ''); -}); \ No newline at end of file diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index 1523c569b0..00af8e08c1 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -7,7 +7,7 @@ - +
@@ -40,22 +40,27 @@ import { ref, computed } from 'vue'; import { mockOverviewData } from '@/modules/admin/modules/overview/store/mock-overview-data'; import IntegrationTabs from '@/modules/admin/modules/overview/components/fragments/integration-tabs.vue'; -import { mockOverviewTabs } from '@/modules/admin/modules/overview/store/mock-overview-data'; import IntegrationRow from '@/modules/admin/modules/overview/components/fragments/integration-row.vue'; +import { useOverviewStore } from '@/modules/admin/modules/overview/store/overview.store'; +import { storeToRefs } from 'pinia'; +import { lfIntegrationStatusesTabs } from '@/modules/admin/modules/integration/config/status'; + +const { integrationStatusCount } = storeToRefs(useOverviewStore()); // Mock data for integrations const integrations = ref(mockOverviewData); -const activeTab = ref('in-progress'); +const activeTab = ref(lfIntegrationStatusesTabs.connecting.key); +// TODO: remove mock data and use the API const filteredIntegrations = computed(() => { return integrations.value.filter(integration => { switch (activeTab.value) { - case 'in-progress': + case lfIntegrationStatusesTabs.connecting.key: return integration.status === 'in-progress'; - case 'action-required': + case lfIntegrationStatusesTabs.waitingForAction.key: return integration.status === 'mapping' || integration.status === 'pending-action'; - case 'connection-failed': + case lfIntegrationStatusesTabs.error.key: return integration.status === 'error'; default: return true; @@ -68,6 +73,29 @@ const paginationText = computed(() => { const showing = filteredIntegrations.value.length; return `${showing} out of ${total} integrations`; }); + +const overviewTabs = computed(() => { + return [ + { + label: 'In progress', + key: lfIntegrationStatusesTabs.connecting.key, + count: integrationStatusCount.value[lfIntegrationStatusesTabs.connecting.key] || 0, + icon: 'clock', + }, + { + label: 'Action required', + key: lfIntegrationStatusesTabs.waitingForAction.key, + count: integrationStatusCount.value[lfIntegrationStatusesTabs.waitingForAction.key] || 0, + icon: 'triangle-exclamation', + }, + { + label: 'Connection failed', + key: lfIntegrationStatusesTabs.error.key, + count: integrationStatusCount.value[lfIntegrationStatusesTabs.error.key] || 0, + icon: 'circle-exclamation', + }, +] +}); diff --git a/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts new file mode 100644 index 0000000000..793932d1d3 --- /dev/null +++ b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts @@ -0,0 +1,42 @@ +import type { QueryFunction } from '@tanstack/vue-query' +import { type ComputedRef, computed } from 'vue' +import { useQuery } from '@tanstack/vue-query' +import { TanstackKey } from '@/shared/types/tanstack' +import authAxios from '@/shared/axios/auth-axios' +import { GlobalIntegrationStatusCount } from '../types/overview.types' + +export interface GlobalIntegrationStatusCountQueryParams { + platform: string | undefined +} + +class OverviewApiService { + fetchGlobalIntegrationStatusCount(params: ComputedRef) { + const queryKey = computed(() => [ + TanstackKey.GLOBAL_INTEGRATION_STATUS_COUNT, + params.value.platform, + ]) + const queryFn = computed>(() => + this.fetchGlobalIntegrationStatusCountQueryFn(() => ({ + platform: params.value.platform, + })), + ) + + return useQuery({ + queryKey, + queryFn, + }) + } + + fetchGlobalIntegrationStatusCountQueryFn( + query: () => Record, + ): QueryFunction { + return () => + authAxios + .get('/integration/global/status', { + params: query(), + }) + .then((res) => res.data) + } +} + +export const OVERVIEW_API_SERVICE = new OverviewApiService() diff --git a/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts b/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts index 5dbbc8c213..f8e5e4c062 100644 --- a/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts +++ b/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts @@ -95,24 +95,3 @@ export const mockOverviewData: IntegrationStatus[] = [ statusDetails: 'Unable to connect to server', }, ] - -export const mockOverviewTabs = [ - { - label: 'In progress', - key: 'in-progress', - count: 4, - icon: 'clock', - }, - { - label: 'Action required', - key: 'action-required', - count: 2, - icon: 'triangle-exclamation', - }, - { - label: 'Connection failed', - key: 'connection-failed', - count: 1, - icon: 'circle-exclamation', - }, -] diff --git a/frontend/src/modules/admin/modules/overview/store/overview.store.ts b/frontend/src/modules/admin/modules/overview/store/overview.store.ts index 08dba74073..5e969ed3a5 100644 --- a/frontend/src/modules/admin/modules/overview/store/overview.store.ts +++ b/frontend/src/modules/admin/modules/overview/store/overview.store.ts @@ -6,10 +6,12 @@ export const useOverviewStore = defineStore('overview', () => { const selectedProjectGroup = ref(null) const selectedProject = ref(null) const selectedIntegrationId = ref(null) + const integrationStatusCount = ref>({}) return { selectedProjectGroup, selectedProject, selectedIntegrationId, + integrationStatusCount, } }) diff --git a/frontend/src/modules/admin/modules/overview/types/overview.types.ts b/frontend/src/modules/admin/modules/overview/types/overview.types.ts index a67bbf3def..07015c68b2 100644 --- a/frontend/src/modules/admin/modules/overview/types/overview.types.ts +++ b/frontend/src/modules/admin/modules/overview/types/overview.types.ts @@ -25,11 +25,10 @@ export interface IntegrationStatus { segmentId: string settings: any status: string - - // integration: IntegrationConfig - // projectGroupName: string - // projectName: string - // projectPath: string - // status: IntegrationStatusConfig statusDetails: string } + +export interface GlobalIntegrationStatusCount { + status: string + count: number +} diff --git a/frontend/src/shared/types/tanstack.ts b/frontend/src/shared/types/tanstack.ts index c6683f7fba..7e2f55a2b5 100644 --- a/frontend/src/shared/types/tanstack.ts +++ b/frontend/src/shared/types/tanstack.ts @@ -1,10 +1,11 @@ export enum TanstackKey { - ADMIN_COLLECTIONS = 'admin-collections', - ADMIN_INSIGHTS_PROJECTS = 'admin-insights-projects', - ADMIN_PROJECT_GROUPS = 'admin-project-groups', - ADMIN_SUB_PROJECTS = 'admin-sub-projects', - MEMBER_MERGE_SUGGESTIONS_COUNT = 'member-merge-suggestions-count', - MEMBERS_LIST= 'members-list', - ORGANIZATION_MERGE_SUGGESTIONS_COUNT = 'organization-merge-suggestions-count', - ORGANIZATIONS_LIST = 'organizations-list', + ADMIN_COLLECTIONS = 'admin-collections', + ADMIN_INSIGHTS_PROJECTS = 'admin-insights-projects', + ADMIN_PROJECT_GROUPS = 'admin-project-groups', + ADMIN_SUB_PROJECTS = 'admin-sub-projects', + MEMBER_MERGE_SUGGESTIONS_COUNT = 'member-merge-suggestions-count', + MEMBERS_LIST = 'members-list', + ORGANIZATION_MERGE_SUGGESTIONS_COUNT = 'organization-merge-suggestions-count', + ORGANIZATIONS_LIST = 'organizations-list', + GLOBAL_INTEGRATION_STATUS_COUNT = 'global-integration-status-count', } From 4c231e5ade7970aa4f0bccf2a3d5918faaf5d1e0 Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Tue, 9 Dec 2025 12:20:14 +0800 Subject: [PATCH 24/51] chore: wired integration details list to api Signed-off-by: Efren Lim --- .../integration/config/status/connecting.ts | 11 +- .../modules/integration/config/status/done.ts | 11 +- .../integration/config/status/error.ts | 11 +- .../integration/config/status/index.ts | 55 +-- .../config/status/not-connected.ts | 11 +- .../config/status/waiting-for-action.ts | 11 +- .../components/fragments/integration-tabs.vue | 2 +- .../sections/integration-details.vue | 64 ++- .../sections/integration-status.vue | 8 +- .../overview/services/overview.api.service.ts | 57 ++- .../overview/store/mock-overview-data.ts | 390 ++++++++++++++++++ .../modules/overview/types/overview.types.ts | 7 + frontend/src/shared/types/tanstack.ts | 1 + 13 files changed, 581 insertions(+), 58 deletions(-) diff --git a/frontend/src/modules/admin/modules/integration/config/status/connecting.ts b/frontend/src/modules/admin/modules/integration/config/status/connecting.ts index 929f43e3ce..7f6ba81646 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/connecting.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/connecting.ts @@ -1,4 +1,4 @@ -import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index'; +import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index' const connecting: IntegrationStatusConfig = { key: 'connecting', @@ -19,6 +19,11 @@ const connecting: IntegrationStatusConfig = { empty: 'No integrations with connection in progress', badge: 'bg-primary-50', }, -}; + chipStatus: { + icon: 'clock', + iconType: 'solid', + color: 'text-primary-500', + }, +} -export default connecting; +export default connecting diff --git a/frontend/src/modules/admin/modules/integration/config/status/done.ts b/frontend/src/modules/admin/modules/integration/config/status/done.ts index bdb0c6ce8d..70df589997 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/done.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/done.ts @@ -1,4 +1,4 @@ -import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index'; +import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index' const done: IntegrationStatusConfig = { key: 'done', @@ -19,6 +19,11 @@ const done: IntegrationStatusConfig = { empty: 'No integrations connected', badge: 'bg-green-100', }, -}; + chipStatus: { + icon: 'circle-check', + iconType: 'solid', + color: 'text-green-500', + }, +} -export default done; +export default done diff --git a/frontend/src/modules/admin/modules/integration/config/status/error.ts b/frontend/src/modules/admin/modules/integration/config/status/error.ts index 41f9705fb9..8feb99ace6 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/error.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/error.ts @@ -1,4 +1,4 @@ -import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index'; +import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index' const error: IntegrationStatusConfig = { key: 'error', @@ -19,6 +19,11 @@ const error: IntegrationStatusConfig = { empty: 'No integrations with a failed connection', badge: 'bg-red-100', }, -}; + chipStatus: { + icon: 'circle-exclamation', + iconType: 'solid', + color: 'text-red-600', + }, +} -export default error; +export default error diff --git a/frontend/src/modules/admin/modules/integration/config/status/index.ts b/frontend/src/modules/admin/modules/integration/config/status/index.ts index 41ddd8a2be..52654e01e1 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/index.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/index.ts @@ -1,28 +1,33 @@ -import done from './done'; -import error from './error'; -import waitingForAction from './waiting-for-action'; -import connecting from './connecting'; -import notConnected from './not-connected'; +import done from './done' +import error from './error' +import waitingForAction from './waiting-for-action' +import connecting from './connecting' +import notConnected from './not-connected' export interface IntegrationStatusConfig { - key: string; - show: (integration: any) => boolean; - statuses: string[], + key: string + show: (integration: any) => boolean + statuses: string[] status: { - text: string; - icon: string; - iconType?: string; - color: string; - }, + text: string + icon: string + iconType?: string + color: string + } actionBar: { - background: string; - color: string; - }, + background: string + color: string + } tabs: { - text: string; - empty: string; - badge: string; - }, + text: string + empty: string + badge: string + } + chipStatus?: { + icon: string + iconType?: string + color: string + } } export const lfIntegrationStatuses: Record = { @@ -30,7 +35,7 @@ export const lfIntegrationStatuses: Record = { error, waitingForAction, connecting, -}; +} export const lfIntegrationStatusesTabs: Record = { done, @@ -38,14 +43,14 @@ export const lfIntegrationStatusesTabs: Record waitingForAction, error, notConnected, -}; +} export const getIntegrationStatus = (integration: any): IntegrationStatusConfig => { // eslint-disable-next-line no-restricted-syntax for (const key in lfIntegrationStatuses) { if (lfIntegrationStatuses[key].show(integration)) { - return lfIntegrationStatuses[key]; + return lfIntegrationStatuses[key] } } - return connecting; -}; + return connecting +} diff --git a/frontend/src/modules/admin/modules/integration/config/status/not-connected.ts b/frontend/src/modules/admin/modules/integration/config/status/not-connected.ts index 797f79c364..4bd316238c 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/not-connected.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/not-connected.ts @@ -1,4 +1,4 @@ -import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index'; +import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index' const notConnected: IntegrationStatusConfig = { key: 'notConnected', @@ -18,6 +18,11 @@ const notConnected: IntegrationStatusConfig = { empty: 'No integrations to be connected', badge: 'bg-gray-100', }, -}; + chipStatus: { + icon: 'link-simple-slash', + iconType: 'light', + color: 'text-gray-600', + }, +} -export default notConnected; +export default notConnected diff --git a/frontend/src/modules/admin/modules/integration/config/status/waiting-for-action.ts b/frontend/src/modules/admin/modules/integration/config/status/waiting-for-action.ts index 53f8994d8a..7a514fd7d3 100644 --- a/frontend/src/modules/admin/modules/integration/config/status/waiting-for-action.ts +++ b/frontend/src/modules/admin/modules/integration/config/status/waiting-for-action.ts @@ -1,4 +1,4 @@ -import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index'; +import { IntegrationStatusConfig } from '@/modules/admin/modules/integration/config/status/index' const waitingForAction: IntegrationStatusConfig = { key: 'waitingForAction', @@ -19,6 +19,11 @@ const waitingForAction: IntegrationStatusConfig = { empty: 'No integrations with a pending action', badge: 'bg-yellow-100', }, -}; + chipStatus: { + icon: 'triangle-exclamation', + iconType: 'solid', + color: 'text-yellow-500', + }, +} -export default waitingForAction; +export default waitingForAction diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue b/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue index 974ca1cee4..0253f0ce7f 100644 --- a/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue +++ b/frontend/src/modules/admin/modules/overview/components/fragments/integration-tabs.vue @@ -3,7 +3,7 @@ diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index 00af8e08c1..bfe6b15a2c 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -20,7 +20,7 @@
@@ -29,28 +29,53 @@
{{ paginationText }} -
+ + diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index bfe6b15a2c..8e4ed5b25d 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -7,39 +7,63 @@ - +
- -
-
Integration
-
Project
-
Status
-
- - -
- -
- - -
- {{ paginationText }} - -
+ + @@ -53,55 +77,12 @@ import { storeToRefs } from 'pinia'; import { lfIntegrationStatusesTabs } from '@/modules/admin/modules/integration/config/status'; import { OVERVIEW_API_SERVICE } from '@/modules/admin/modules/overview/services/overview.api.service'; import LfIcon from '@/ui-kit/icon/Icon.vue'; +import LfSpinner from '@/ui-kit/spinner/Spinner.vue'; const { integrationStatusCount, selectedIntegrationId } = storeToRefs(useOverviewStore()); -// Mock data for integrations -const integrations = ref(mockOverviewData); - const activeTab = ref(lfIntegrationStatusesTabs.connecting.key); -const limit = ref(3); - -const params = computed(() => { - return { - platform: selectedIntegrationId.value || undefined, //activeTab.value, - status: ['done'], //lfIntegrationStatusesTabs[activeTab.value].statuses, - query: '', - limit: limit.value - }; -}); - -const { data, isPending, isError, isFetchingNextPage, fetchNextPage, hasNextPage } = OVERVIEW_API_SERVICE.fetchGlobalIntegrations(params); -const totalCount = computed(() => { - // @ts-expect-error - TanStack Query type inference issue with Vue - return data.value?.pages[0].count || 0; -}); - -// TODO: remove mock data and use the API -const filteredIntegrations = computed(() => { - return integrations.value.filter(integration => { - switch (activeTab.value) { - case lfIntegrationStatusesTabs.connecting.key: - return integration.status === 'in-progress'; - case lfIntegrationStatusesTabs.waitingForAction.key: - return integration.status === 'mapping' || integration.status === 'pending-action'; - case lfIntegrationStatusesTabs.error.key: - return integration.status === 'error'; - default: - return true; - } - }); -}); - -const paginatedIntegrations = computed(() => { - // @ts-expect-error - TanStack Query type inference issue with Vue - return data.value?.pages.flatMap(page => page.rows) || []; -}); - -const paginationText = computed(() => { - const showing = paginatedIntegrations.value.length; - return `${showing} out of ${totalCount.value} integrations`; -}); +const limit = ref(10); const overviewTabs = computed(() => { return [ @@ -126,12 +107,34 @@ const overviewTabs = computed(() => { ] }); -watch(data, () => { - // if (data.value) { - // integrations.value = data.value.rows; - // } - console.log(data.value); -}, { immediate: true }); +const params = computed(() => { + return { + platform: selectedIntegrationId.value || undefined, //activeTab.value, + status: ['done'], //lfIntegrationStatusesTabs[activeTab.value].statuses, + query: '', + limit: limit.value + }; +}); + +const { data, isError, isPending, isFetchingNextPage, fetchNextPage, hasNextPage } = OVERVIEW_API_SERVICE.fetchGlobalIntegrations(params); +const totalCount = computed(() => { + // @ts-expect-error - TanStack Query type inference issue with Vue + return data.value?.pages[0].count || 0; +}); + +const paginatedIntegrations = computed(() => { + // @ts-expect-error - TanStack Query type inference issue with Vue + return data.value?.pages.flatMap(page => page.rows) || []; +}); + +const isEmpty = computed(() => { + return overviewTabs.value.every(tab => tab.count === 0); +}); + +const paginationText = computed(() => { + const showing = paginatedIntegrations.value.length; + return `${showing} out of ${totalCount.value} integrations`; +}); // watch(isError, () => { // if (isError.value) { diff --git a/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue b/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue index 95df6f6bb5..009dda22db 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue @@ -2,6 +2,7 @@
+
@@ -9,11 +10,12 @@ import { computed } from 'vue'; import AppLfProjectGroupFilter from '../fragments/project-group-filter.vue'; import AppLfProjectFilter from '../fragments/project-filter.vue'; +import AppLfSubProjectFilter from '../fragments/sub-project-filter.vue'; import { useOverviewStore } from '../../store/overview.store'; import { storeToRefs } from 'pinia'; const overviewStore = useOverviewStore(); -const { selectedProjectGroup } = storeToRefs(overviewStore); +const { selectedProjectGroup, selectedProject, selectedProjectId } = storeToRefs(overviewStore); const projects = computed(() => { return selectedProjectGroup.value?.projects || []; diff --git a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue index 6f541fe008..e9346776a6 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue @@ -32,7 +32,7 @@
- +
Activities @@ -51,6 +51,11 @@ import LfCard from '@/ui-kit/card/Card.vue'; import LfIcon from '@/ui-kit/icon/Icon.vue'; import AppLfOverviewTrendDisplay from '@/modules/admin/modules/overview/components/fragments/trend-display.vue'; import type { OverviewTrends } from '@/modules/admin/modules/overview/types/overview.types'; +import { useOverviewStore } from '../../store/overview.store'; +import { storeToRefs } from 'pinia'; + +const overviewStore = useOverviewStore(); +const { selectedProject } = storeToRefs(overviewStore); const projectsTrends = ref({ current: 640, diff --git a/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts index 84629105b1..de85188ed4 100644 --- a/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts +++ b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts @@ -4,6 +4,7 @@ import { useInfiniteQuery, useQuery } from '@tanstack/vue-query' import { TanstackKey } from '@/shared/types/tanstack' import authAxios from '@/shared/axios/auth-axios' import { GlobalIntegrationStatusCount, IntegrationStatusResponse } from '../types/overview.types' +import { Project } from '@/modules/lf/segments/types/Segments' export interface GlobalIntegrationStatusCountQueryParams { platform: string | undefined @@ -17,6 +18,33 @@ export interface GlobalIntegrationIntegrationsQueryParams { } class OverviewApiService { + fetchProjectById(params: ComputedRef) { + const queryKey = computed(() => [TanstackKey.PROJECT_BY_ID, params.value]) + const queryFn = computed>(() => + this.fetchProjectByIdQueryFn(() => ({ + id: params.value, + })), + ) + + return useQuery({ + queryKey, + queryFn, + }) + } + + fetchProjectByIdQueryFn( + query: () => Record, + ): QueryFunction { + return () => + authAxios + .get(`/segment/${query().id}`, { + params: { + segments: [query().id], + }, + }) + .then((res) => res.data) + } + fetchGlobalIntegrationStatusCount(params: ComputedRef) { const queryKey = computed(() => [ TanstackKey.GLOBAL_INTEGRATION_STATUS_COUNT, diff --git a/frontend/src/modules/admin/modules/overview/store/overview.store.ts b/frontend/src/modules/admin/modules/overview/store/overview.store.ts index 5e969ed3a5..add648587a 100644 --- a/frontend/src/modules/admin/modules/overview/store/overview.store.ts +++ b/frontend/src/modules/admin/modules/overview/store/overview.store.ts @@ -1,16 +1,24 @@ import { defineStore } from 'pinia' import { ref } from 'vue' -import { Project, ProjectGroup } from '@/modules/lf/segments/types/Segments' +import { Project, ProjectGroup, SubProject } from '@/modules/lf/segments/types/Segments' export const useOverviewStore = defineStore('overview', () => { + const selectedProjectGroupId = ref('') const selectedProjectGroup = ref(null) + const selectedProjectId = ref('') const selectedProject = ref(null) + const selectedSubProjectId = ref('') + const selectedSubProject = ref(null) const selectedIntegrationId = ref(null) const integrationStatusCount = ref>({}) return { + selectedProjectGroupId, selectedProjectGroup, + selectedProjectId, selectedProject, + selectedSubProjectId, + selectedSubProject, selectedIntegrationId, integrationStatusCount, } diff --git a/frontend/src/shared/types/tanstack.ts b/frontend/src/shared/types/tanstack.ts index b63217f8e1..15c922e8fa 100644 --- a/frontend/src/shared/types/tanstack.ts +++ b/frontend/src/shared/types/tanstack.ts @@ -9,4 +9,5 @@ export enum TanstackKey { ORGANIZATIONS_LIST = 'organizations-list', GLOBAL_INTEGRATION_STATUS_COUNT = 'global-integration-status-count', GLOBAL_INTEGRATIONS = 'global-integrations', + PROJECT_BY_ID = 'project-by-id', } From 9f24898c77a95fe6040fa418cd419d3796a06121 Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Thu, 11 Dec 2025 12:36:45 +0800 Subject: [PATCH 26/51] fix: hide project card when project is selected Signed-off-by: Efren Lim --- .../admin/modules/overview/components/sections/summary.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue index e9346776a6..78b4360414 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue @@ -2,7 +2,7 @@
- +
Projects @@ -32,7 +32,7 @@
- +
Activities From 2d68eb033d16adda3c1e659a536ba16a7c526bd0 Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Fri, 12 Dec 2025 16:42:56 +0800 Subject: [PATCH 27/51] chore: wired summary cards to backend Signed-off-by: Efren Lim --- .../overview/components/sections/summary.vue | 46 +++++++++++++------ .../overview/services/overview.api.service.ts | 35 +++++++++++++- .../modules/overview/types/overview.types.ts | 11 +++++ frontend/src/shared/types/tanstack.ts | 1 + 4 files changed, 77 insertions(+), 16 deletions(-) diff --git a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue index 78b4360414..d9dd4d533d 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/summary.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/summary.vue @@ -1,5 +1,8 @@ diff --git a/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts index 3e66bf0a30..c036a4dae8 100644 --- a/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts +++ b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts @@ -12,6 +12,7 @@ import { Project } from '@/modules/lf/segments/types/Segments' export interface GlobalIntegrationStatusCountQueryParams { platform: string | undefined + segment?: string } export interface GlobalIntegrationIntegrationsQueryParams { @@ -19,6 +20,7 @@ export interface GlobalIntegrationIntegrationsQueryParams { status: string[] query: string limit: number + segment?: string } export interface DashboardMetricsQueryParams { @@ -61,6 +63,7 @@ class OverviewApiService { const queryFn = computed>(() => this.fetchGlobalIntegrationStatusCountQueryFn(() => ({ platform: params.value.platform, + segment: params.value.segment, })), ) @@ -88,6 +91,7 @@ class OverviewApiService { params.value.status, params.value.query, params.value.limit, + params.value.segment, ]) const queryFn = computed>(() => this.fetchGlobalIntegrationsQueryFn(() => ({ From 3742769342d1f11c26a87fbc64c6a3525fd445ca Mon Sep 17 00:00:00 2001 From: Efren Lim Date: Fri, 12 Dec 2025 17:57:10 +0800 Subject: [PATCH 30/51] fix: summary data not showing Signed-off-by: Efren Lim --- .../components/sections/integration-status.vue | 2 +- .../overview/components/sections/summary.vue | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue index 0ea38d3ae9..90c3530e34 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue @@ -16,7 +16,7 @@
-
+
@@ -106,9 +106,7 @@ const props = defineProps<{ projects: Project[]; }>(); -const trimDisplay = (name: string) => { - return name.length > 20 ? `${name.slice(0, 20)}...` : name; -}; +const trimDisplay = (name: string) => (name.length > 20 ? `${name.slice(0, 20)}...` : name); const selectProject = (projectId: string) => { selectedProjectId.value = projectId; @@ -119,7 +117,7 @@ const projectsList = computed(() => { if (!searchValue.value) return true; return project.name.toLowerCase().includes(searchValue.value.toLowerCase()); }); - + return filtered.map((project) => ({ id: project.id, name: project.name, @@ -128,7 +126,7 @@ const projectsList = computed(() => { watch(selectedProjectId, (newVal) => { if (newVal && newVal !== '') { - selectedProject.value = props.projects.find(p => p.id === newVal) || null; + selectedProject.value = props.projects.find((p) => p.id === newVal) || null; } else { selectedProject.value = null; } diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue b/frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue index 782c99d88e..431d76e064 100644 --- a/frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue +++ b/frontend/src/modules/admin/modules/overview/components/fragments/project-group-filter.vue @@ -6,7 +6,7 @@ dropdown-class="max-h-80" placement="bottom-end" > - @@ -87,7 +87,7 @@ import { useOverviewStore } from '@/modules/admin/modules/overview/store/overvie import LfIcon from '@/ui-kit/icon/Icon.vue'; import { useDebounce } from '@vueuse/core'; -import { Project, SubProject } from '@/modules/lf/segments/types/Segments'; +import { SubProject } from '@/modules/lf/segments/types/Segments'; import LfxDropdownSelect from '@/ui-kit/lfx/dropdown/dropdown-select.vue'; import LfxDropdownSelector from '@/ui-kit/lfx/dropdown/dropdown-selector.vue'; import LfxDropdownItem from '@/ui-kit/lfx/dropdown/dropdown-item.vue'; @@ -109,20 +109,16 @@ const params = computed(() => selectedProjectId.value || ''); const { data, isPending } = OVERVIEW_API_SERVICE.fetchProjectById(params); -const trimDisplay = (name: string) => { - return name.length > 20 ? `${name.slice(0, 20)}...` : name; -}; +const trimDisplay = (name: string) => (name.length > 20 ? `${name.slice(0, 20)}...` : name); -const subProjectsList = computed(() => { - return (data.value?.subprojects || []).filter((subProject) => { - if (!searchValue.value) return true; - return subProject.name.toLowerCase().includes(searchValue.value.toLowerCase()); - }); -}); +const subProjectsList = computed(() => (data.value?.subprojects || []).filter((subProject) => { + if (!searchValue.value) return true; + return subProject.name.toLowerCase().includes(searchValue.value.toLowerCase()); +})); watch(selectedSubProjectId, (newVal) => { if (newVal && newVal !== '') { - selectedSubProject.value = subProjectsList.value?.find(p => p.id === newVal) || null; + selectedSubProject.value = subProjectsList.value?.find((p) => p.id === newVal) || null; } else { selectedSubProject.value = null; } diff --git a/frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue b/frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue index 9dfd2f3449..d625b5006b 100644 --- a/frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue +++ b/frontend/src/modules/admin/modules/overview/components/fragments/trend-display.vue @@ -46,9 +46,7 @@ const props = withDefaults( }, ); -const trend = computed(() => { - return props.data.current - props.data.previous; -}); +const trend = computed(() => props.data.current - props.data.previous); const trendPercentage = computed(() => { if (!props.data.previous) { diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index fcefd18a5e..9ad17a6245 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -5,13 +5,14 @@

In progress & troubleshooting

- + - + :tabs="overviewTabs" + :model-value="activeTab" + @update:model-value="activeTab = $event" + />
@@ -68,7 +76,7 @@ - diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue index e769f9dd0d..129ca016d0 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-status.vue @@ -54,14 +54,14 @@ import LfxChip from '@/ui-kit/lfx/chip/chip.vue'; import IntegrationsFilter from '@/modules/admin/modules/overview/components/fragments/integrations-filter.vue'; import { lfIntegrationStatusesTabs } from '@/modules/admin/modules/integration/config/status'; import { useRouter } from 'vue-router'; -import { OVERVIEW_API_SERVICE } from '../../services/overview.api.service'; import { ToastStore } from '@/shared/message/notification'; import { useOverviewStore } from '@/modules/admin/modules/overview/store/overview.store'; import { storeToRefs } from 'pinia'; -import { GlobalIntegrationStatusCount } from '../../types/overview.types'; import LfSpinner from '@/ui-kit/spinner/Spinner.vue'; +import { GlobalIntegrationStatusCount } from '../../types/overview.types'; +import { OVERVIEW_API_SERVICE } from '../../services/overview.api.service'; -const { +const { selectedIntegrationId, integrationStatusCount, selectedSubProjectId, @@ -87,7 +87,7 @@ const navigateTo = (path: string, key: string) => { watch(data, () => { if (data.value) { - integrationStatusCount.value = data.value?.reduce((acc: Record, item: GlobalIntegrationStatusCount) => { + integrationStatusCount.value = data.value?.reduce((acc: Record, item: GlobalIntegrationStatusCount) => { acc[item.status] = +item.count; return acc; }, {}); @@ -105,4 +105,4 @@ watch(isError, () => { export default { name: 'AppLfOverviewIntegrationStatus', }; - \ No newline at end of file + diff --git a/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue b/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue index 009dda22db..bfd7a71009 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/overview-filter.vue @@ -1,25 +1,23 @@ \ No newline at end of file + diff --git a/frontend/src/modules/admin/modules/overview/pages/overview.vue b/frontend/src/modules/admin/modules/overview/pages/overview.vue index d436144264..d2e894a3ca 100644 --- a/frontend/src/modules/admin/modules/overview/pages/overview.vue +++ b/frontend/src/modules/admin/modules/overview/pages/overview.vue @@ -25,4 +25,4 @@ import AppLfOverviewFilter from '@/modules/admin/modules/overview/components/sec export default { name: 'AppLfOverviewPage', }; - \ No newline at end of file + diff --git a/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts index c036a4dae8..8cb0f949d4 100644 --- a/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts +++ b/frontend/src/modules/admin/modules/overview/services/overview.api.service.ts @@ -1,14 +1,14 @@ -import type { QueryFunction } from '@tanstack/vue-query' -import { type ComputedRef, computed } from 'vue' -import { useInfiniteQuery, useQuery } from '@tanstack/vue-query' -import { TanstackKey } from '@/shared/types/tanstack' -import authAxios from '@/shared/axios/auth-axios' +import type { QueryFunction } from '@tanstack/vue-query'; +import { type ComputedRef, computed } from 'vue'; +import { useInfiniteQuery, useQuery } from '@tanstack/vue-query'; +import { TanstackKey } from '@/shared/types/tanstack'; +import authAxios from '@/shared/axios/auth-axios'; +import { Project } from '@/modules/lf/segments/types/Segments'; import { DashboardMetrics, GlobalIntegrationStatusCount, IntegrationStatusResponse, -} from '../types/overview.types' -import { Project } from '@/modules/lf/segments/types/Segments' +} from '../types/overview.types'; export interface GlobalIntegrationStatusCountQueryParams { platform: string | undefined @@ -29,59 +29,53 @@ export interface DashboardMetricsQueryParams { class OverviewApiService { fetchProjectById(params: ComputedRef) { - const queryKey = computed(() => [TanstackKey.PROJECT_BY_ID, params.value]) - const queryFn = computed>(() => - this.fetchProjectByIdQueryFn(() => ({ - id: params.value, - })), - ) + const queryKey = computed(() => [TanstackKey.PROJECT_BY_ID, params.value]); + const queryFn = computed>(() => this.fetchProjectByIdQueryFn(() => ({ + id: params.value, + }))); return useQuery({ queryKey, queryFn, - }) + }); } fetchProjectByIdQueryFn( query: () => Record, ): QueryFunction { - return () => - authAxios - .get(`/segment/${query().id}`, { - params: { - segments: [query().id], - }, - }) - .then((res) => res.data) + return () => authAxios + .get(`/segment/${query().id}`, { + params: { + segments: [query().id], + }, + }) + .then((res) => res.data); } fetchGlobalIntegrationStatusCount(params: ComputedRef) { const queryKey = computed(() => [ TanstackKey.GLOBAL_INTEGRATION_STATUS_COUNT, params.value.platform, - ]) - const queryFn = computed>(() => - this.fetchGlobalIntegrationStatusCountQueryFn(() => ({ - platform: params.value.platform, - segment: params.value.segment, - })), - ) + ]); + const queryFn = computed>(() => this.fetchGlobalIntegrationStatusCountQueryFn(() => ({ + platform: params.value.platform, + segment: params.value.segment, + }))); return useQuery({ queryKey, queryFn, - }) + }); } fetchGlobalIntegrationStatusCountQueryFn( query: () => Record, ): QueryFunction { - return () => - authAxios - .get('/integration/global/status', { - params: query(), - }) - .then((res) => res.data) + return () => authAxios + .get('/integration/global/status', { + params: query(), + }) + .then((res) => res.data); } fetchGlobalIntegrations(params: ComputedRef) { @@ -92,12 +86,10 @@ class OverviewApiService { params.value.query, params.value.limit, params.value.segment, - ]) - const queryFn = computed>(() => - this.fetchGlobalIntegrationsQueryFn(() => ({ - ...params.value, - })), - ) + ]); + const queryFn = computed>(() => this.fetchGlobalIntegrationsQueryFn(() => ({ + ...params.value, + }))); return useInfiniteQuery< IntegrationStatusResponse, @@ -107,54 +99,50 @@ class OverviewApiService { number >({ queryKey, - //@ts-expect-error - TanStack Query type inference issue with Vue + // @ts-expect-error - TanStack Query type inference issue with Vue queryFn, getNextPageParam: this.getNextPageIntegrationsParam, initialPageParam: 0, - }) + }); } fetchGlobalIntegrationsQueryFn( query: () => Record, ): QueryFunction { - return ({ pageParam = 0 }) => - authAxios - .get('/integration/global', { - params: { offset: pageParam, ...query() }, - }) - .then((res) => res.data) + return ({ pageParam = 0 }) => authAxios + .get('/integration/global', { + params: { offset: pageParam, ...query() }, + }) + .then((res) => res.data); } getNextPageIntegrationsParam(lastPage: IntegrationStatusResponse): number | undefined { - const nextPage = lastPage.offset + lastPage.limit - const totalRows = lastPage.count - return nextPage < totalRows ? nextPage : undefined + const nextPage = lastPage.offset + lastPage.limit; + const totalRows = lastPage.count; + return nextPage < totalRows ? nextPage : undefined; } fetchDashboardMetrics(params: ComputedRef) { - const queryKey = computed(() => [TanstackKey.DASHBOARD_METRICS, params.value.segment]) - const queryFn = computed>(() => - this.fetchDashboardMetricsQueryFn(() => ({ - ...params.value, - })), - ) + const queryKey = computed(() => [TanstackKey.DASHBOARD_METRICS, params.value.segment]); + const queryFn = computed>(() => this.fetchDashboardMetricsQueryFn(() => ({ + ...params.value, + }))); return useQuery({ queryKey, queryFn, - }) + }); } fetchDashboardMetricsQueryFn( query: () => Record, ): QueryFunction { - return () => - authAxios - .get('/dashboard/metrics', { - params: query(), - }) - .then((res) => res.data) + return () => authAxios + .get('/dashboard/metrics', { + params: query(), + }) + .then((res) => res.data); } } -export const OVERVIEW_API_SERVICE = new OverviewApiService() +export const OVERVIEW_API_SERVICE = new OverviewApiService(); diff --git a/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts b/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts index 14b1ef0492..2951607195 100644 --- a/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts +++ b/frontend/src/modules/admin/modules/overview/store/mock-overview-data.ts @@ -1,4 +1,5 @@ -import { IntegrationStatus } from '../types/overview.types' +import { IntegrationStatus } from '../types/overview.types'; + export const mockOverviewData: IntegrationStatus[] = [ // In Progress integrations { @@ -484,4 +485,4 @@ export const mockOverviewData: IntegrationStatus[] = [ status: 'error', statusDetails: 'Unable to connect to server', }, -] +]; diff --git a/frontend/src/modules/admin/modules/overview/store/overview.store.ts b/frontend/src/modules/admin/modules/overview/store/overview.store.ts index add648587a..2a8ba9f839 100644 --- a/frontend/src/modules/admin/modules/overview/store/overview.store.ts +++ b/frontend/src/modules/admin/modules/overview/store/overview.store.ts @@ -1,16 +1,16 @@ -import { defineStore } from 'pinia' -import { ref } from 'vue' -import { Project, ProjectGroup, SubProject } from '@/modules/lf/segments/types/Segments' +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { Project, ProjectGroup, SubProject } from '@/modules/lf/segments/types/Segments'; export const useOverviewStore = defineStore('overview', () => { - const selectedProjectGroupId = ref('') - const selectedProjectGroup = ref(null) - const selectedProjectId = ref('') - const selectedProject = ref(null) - const selectedSubProjectId = ref('') - const selectedSubProject = ref(null) - const selectedIntegrationId = ref(null) - const integrationStatusCount = ref>({}) + const selectedProjectGroupId = ref(''); + const selectedProjectGroup = ref(null); + const selectedProjectId = ref(''); + const selectedProject = ref(null); + const selectedSubProjectId = ref(''); + const selectedSubProject = ref(null); + const selectedIntegrationId = ref(null); + const integrationStatusCount = ref>({}); return { selectedProjectGroupId, @@ -21,5 +21,5 @@ export const useOverviewStore = defineStore('overview', () => { selectedSubProject, selectedIntegrationId, integrationStatusCount, - } -}) + }; +}); diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 1707c6bab6..5b2513f5ca 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -1,13 +1,13 @@ -import { createRouter as createVueRouter, createWebHistory } from 'vue-router' -import { storeToRefs } from 'pinia' +import { createRouter as createVueRouter, createWebHistory } from 'vue-router'; +import { storeToRefs } from 'pinia'; -import { store } from '@/store' -import authGuards from '@/middleware/auth' -import modules from '@/modules' -import ProgressBar from '@/shared/progress-bar/progress-bar' -import { useLfSegmentsStore } from '@/modules/lf/segments/store' -import auth from '@/modules/auth' -import navigationGuard from '@/middleware/navigation/navigation-guard' +import { store } from '@/store'; +import authGuards from '@/middleware/auth'; +import modules from '@/modules'; +import ProgressBar from '@/shared/progress-bar/progress-bar'; +import { useLfSegmentsStore } from '@/modules/lf/segments/store'; +import auth from '@/modules/auth'; +import navigationGuard from '@/middleware/navigation/navigation-guard'; /** * Loads all the routes from src/modules/ folders, and adds the catch-all rule to handle 404s @@ -22,22 +22,20 @@ const routes = [ ...auth.routes, ...Object.keys(modules) .filter((key) => Boolean(modules[key].routes)) - .map((key) => - modules[key].routes.map((r) => { - // eslint-disable-next-line no-param-reassign - r.meta = { - ...r.meta, - middleware: [...authGuards], - } - return r - }), - ) + .map((key) => modules[key].routes.map((r) => { + // eslint-disable-next-line no-param-reassign + r.meta = { + ...r.meta, + middleware: [...authGuards], + }; + return r; + })) .reduce((a, b) => a.concat(b), []), { path: '/:catchAll(.*)', redirect: '/404' }, -] +]; // eslint-disable-next-line import/no-mutable-exports -let router +let router; /** * Creates/Sets Router @@ -49,66 +47,66 @@ export const createRouter = () => { history: createWebHistory(), routes, scrollBehavior() { - return { x: 0, y: 0 } + return { x: 0, y: 0 }; }, - }) + }); - const originalPush = router.push + const originalPush = router.push; router.push = function push(location) { return originalPush.call(this, location).catch((error) => { - console.error(error) - ProgressBar.done() - }) - } + console.error(error); + ProgressBar.done(); + }); + }; router.beforeEach(async (to, from, next) => { - const lsSegmentsStore = useLfSegmentsStore() - const { selectedProjectGroup } = storeToRefs(lsSegmentsStore) - const { listProjectGroups, updateSelectedProjectGroup } = lsSegmentsStore + const lsSegmentsStore = useLfSegmentsStore(); + const { selectedProjectGroup } = storeToRefs(lsSegmentsStore); + const { listProjectGroups, updateSelectedProjectGroup } = lsSegmentsStore; // Set title to pages - document.title = `LFX Community Data Platform${to.meta.title ? ` | ${to.meta.title}` : ''}` + document.title = `LFX Community Data Platform${to.meta.title ? ` | ${to.meta.title}` : ''}`; if (to.name && to.query.menu === from.query.menu && to.name !== from.name) { - ProgressBar.start() + ProgressBar.start(); } - const matchedRoute = to.matched.find((m) => m.meta.middleware) + const matchedRoute = to.matched.find((m) => m.meta.middleware); if (matchedRoute !== undefined) { const middlewareArray = Array.isArray(matchedRoute.meta.middleware) ? matchedRoute.meta.middleware - : [matchedRoute.meta.middleware] + : [matchedRoute.meta.middleware]; const context = { from, router, to, store, - } + }; await middlewareArray.forEach(async (middleware) => { - await middleware(context) - }) + await middleware(context); + }); // Redirect to project group landing pages if routes that require a selected project group // And no project group is selected if ( - to.meta.segments?.requireSelectedProjectGroup || - to.meta.segments?.optionalSelectedProjectGroup + to.meta.segments?.requireSelectedProjectGroup + || to.meta.segments?.optionalSelectedProjectGroup ) { if ( - !selectedProjectGroup.value && - !to.query.projectGroup && - !to.meta.segments?.optionalSelectedProjectGroup + !selectedProjectGroup.value + && !to.query.projectGroup + && !to.meta.segments?.optionalSelectedProjectGroup ) { - next('/project-groups') - return + next('/project-groups'); + return; } if (!to.query.projectGroup && selectedProjectGroup.value?.id) { - next({ ...to, query: { ...to.query, projectGroup: selectedProjectGroup.value?.id } }) - return + next({ ...to, query: { ...to.query, projectGroup: selectedProjectGroup.value?.id } }); + return; } if (!selectedProjectGroup.value) { @@ -116,27 +114,27 @@ export const createRouter = () => { await listProjectGroups({ limit: null, reset: true, - }) + }); - updateSelectedProjectGroup(to.query.projectGroup, false) + updateSelectedProjectGroup(to.query.projectGroup, false); } catch (e) { - next('/project-groups') - return + next('/project-groups'); + return; } } } } - next() - }) + next(); + }); router.afterEach(async (to) => { - ProgressBar.done() - await navigationGuard({ to }) - }) + ProgressBar.done(); + await navigationGuard({ to }); + }); } - return router -} + return router; +}; -export { router } +export { router }; diff --git a/frontend/src/ui-kit/lfx/chip/chip.vue b/frontend/src/ui-kit/lfx/chip/chip.vue index c0628fa3c5..d5b3d9356b 100644 --- a/frontend/src/ui-kit/lfx/chip/chip.vue +++ b/frontend/src/ui-kit/lfx/chip/chip.vue @@ -33,7 +33,7 @@ const props = withDefaults( removable: false, }, ); -const emit = defineEmits<{ (e: 'dismissed'): void }>(); +const emit = defineEmits<{(e: 'dismissed'): void }>(); const isVisible = ref(true); diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue index f8b77de572..d0e1c9e067 100644 --- a/frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-item.vue @@ -39,8 +39,7 @@ const props = defineProps<{ const attrs = useAttrs(); -const emit = defineEmits<{ - (e: 'click', value: { value?: string; label?: string; [key: string]: any }): void; +const emit = defineEmits<{(e: 'click', value: { value?: string; label?: string; [key: string]: any }): void; }>(); // Determine if the item is currently selected diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue index 70f0cb6169..2aa38e2674 100644 --- a/frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-search.vue @@ -44,7 +44,7 @@ const props = defineProps<{ lazy?: boolean; }>(); -const emit = defineEmits<{ (e: 'update:modelValue', value: string): void }>(); +const emit = defineEmits<{(e: 'update:modelValue', value: string): void }>(); const model = ref(props.modelValue); diff --git a/frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue b/frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue index 87b01a3b82..310211047c 100644 --- a/frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue +++ b/frontend/src/ui-kit/lfx/dropdown/dropdown-select.vue @@ -23,7 +23,9 @@ SPDX-License-Identifier: MIT diff --git a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue index 18b2fc50f0..1b134d5385 100644 --- a/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue +++ b/frontend/src/modules/admin/modules/overview/components/sections/integration-details.vue @@ -139,17 +139,6 @@ const totalCount = computed(() => data.value?.pages[0].count || 0); // @ts-expect-error - TanStack Query type inference issue with Vue const paginatedIntegrations = computed(() => data.value?.pages.flatMap((page) => page.rows) || []); -const statusParams = computed(() => ({ - // @ts-expect-error - TanStack Query type inference issue with Vue - segments: data.value?.pages.flatMap((page) => page.rows.map((row) => row.segmentId)) || [], -})); - -const { data: integrationProgressList } = OVERVIEW_API_SERVICE.fetchIntegrationProgressList(statusParams); - -watch(integrationProgressList, () => { - console.log('progress status list: ', integrationProgressList.value); -}, { immediate: true }); - const isEmpty = computed(() => overviewTabs.value.every((tab) => tab.count === 0)); const paginationText = computed(() => {