diff --git a/backend/src/database/repositories/organizationRepository.ts b/backend/src/database/repositories/organizationRepository.ts index 3295fa937d..546e898cec 100644 --- a/backend/src/database/repositories/organizationRepository.ts +++ b/backend/src/database/repositories/organizationRepository.ts @@ -479,7 +479,7 @@ class OrganizationRepository { segment_level as (select case when "parentSlug" is not null and "grandparentSlug" is not null then 'child' - when "parentSlug" is null and "grandparentSlug" is not null + when "parentSlug" is not null and "grandparentSlug" is null then 'parent' when "parentSlug" is null and "grandparentSlug" is null then 'grandparent' @@ -495,7 +495,7 @@ class OrganizationRepository { segment_level sl on (sl.level = 'child' and s.id = sl.id) or - (sl.level = 'parent' and s."parentSlug" = sl.slug) or + (sl.level = 'parent' and s."parentSlug" = sl.slug and s."grandparentSlug" is not null) or (sl.level = 'grandparent' and s."grandparentSlug" = sl.slug)` } diff --git a/frontend/public/images/integrations/slack-bot.png b/frontend/public/images/integrations/slack-bot.png new file mode 100644 index 0000000000..0f67d701b5 Binary files /dev/null and b/frontend/public/images/integrations/slack-bot.png differ diff --git a/frontend/src/integrations/github/components/github-connect.vue b/frontend/src/integrations/github/components/github-connect.vue index e5110ad4ba..6ad0651559 100644 --- a/frontend/src/integrations/github/components/github-connect.vue +++ b/frontend/src/integrations/github/components/github-connect.vue @@ -3,8 +3,10 @@ diff --git a/frontend/src/integrations/slack/components/slack-connect.vue b/frontend/src/integrations/slack/components/slack-connect.vue index a2c9f2614b..94e53afa7f 100644 --- a/frontend/src/integrations/slack/components/slack-connect.vue +++ b/frontend/src/integrations/slack/components/slack-connect.vue @@ -20,7 +20,7 @@ defineProps({ }); const connectUrl = computed(() => { - const redirectUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}?success=true`; + const redirectUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}?slack-success=true`; return `${config.backendUrl}/slack/${ store.getters['auth/currentTenant'].id diff --git a/frontend/src/modules/integration/components/integration-list-item.vue b/frontend/src/modules/integration/components/integration-list-item.vue index 628fe0f316..0f9f06f3e8 100644 --- a/frontend/src/modules/integration/components/integration-list-item.vue +++ b/frontend/src/modules/integration/components/integration-list-item.vue @@ -2,46 +2,60 @@
- Connected -
- Failed to connect -
-
- Not receiving activities -
-
- Action required -
-
- Waiting for approval -
-
- Needs to be reconnected -
-
-
- - In progress - - - +
+
+ Connected +
+ Failed to connect +
+
+ Not receiving activities +
+
+ Action required +
+
+ Waiting for approval +
+
+ Needs to be reconnected +
+
+
+ + In progress + + + +
+
+
+ + {{ lastSynced.relative }} + +
+
{{ integration.name }} @@ -101,11 +115,12 @@ + + diff --git a/frontend/src/modules/member/config/filters/jobTitle/config.ts b/frontend/src/modules/member/config/filters/jobTitle/config.ts new file mode 100644 index 0000000000..b3f825733e --- /dev/null +++ b/frontend/src/modules/member/config/filters/jobTitle/config.ts @@ -0,0 +1,24 @@ +import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; +import { + StringFilterConfig, + StringFilterOptions, + StringFilterValue, +} from '@/shared/modules/filters/types/filterTypes/StringFilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; + +const jobTitle: StringFilterConfig = { + id: 'jobTitle', + label: 'Job title', + iconClass: 'ri-suitcase-line', + type: FilterConfigType.STRING, + options: {}, + itemLabelRenderer(value: StringFilterValue, options: StringFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.STRING]('Job title', value, options); + }, + apiFilterRenderer(value: StringFilterValue): any[] { + return apiFilterRendererByType[FilterConfigType.STRING]('attributes.jobTitle.default', value); + }, +}; + +export default jobTitle; diff --git a/frontend/src/modules/member/config/filters/main.ts b/frontend/src/modules/member/config/filters/main.ts index ed005eb375..b974aa333c 100644 --- a/frontend/src/modules/member/config/filters/main.ts +++ b/frontend/src/modules/member/config/filters/main.ts @@ -13,10 +13,14 @@ import lastActivityDate from './lastActivityDate/config'; import reach from './reach/config'; import projects from './projects/config'; import tags from './tags/config'; +import memberName from './memberName/config'; +import jobTitle from './jobTitle/config'; export const memberFilters: Record = { + memberName, noOfActivities, noOfOSSContributions, + jobTitle, activeOn, activityType, avgSentiment, diff --git a/frontend/src/modules/member/config/filters/memberName/config.ts b/frontend/src/modules/member/config/filters/memberName/config.ts new file mode 100644 index 0000000000..5412486432 --- /dev/null +++ b/frontend/src/modules/member/config/filters/memberName/config.ts @@ -0,0 +1,31 @@ +import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; +import { + StringFilterConfig, + StringFilterOptions, + StringFilterValue, +} from '@/shared/modules/filters/types/filterTypes/StringFilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; + +const memberName: StringFilterConfig = { + id: 'memberName', + label: 'Member name', + iconClass: 'ri-account-circle-line', + type: FilterConfigType.STRING, + options: {}, + itemLabelRenderer( + value: StringFilterValue, + options: StringFilterOptions, + ): string { + return itemLabelRendererByType[FilterConfigType.STRING]( + 'Member name', + value, + options, + ); + }, + apiFilterRenderer(value: StringFilterValue): any[] { + return apiFilterRendererByType[FilterConfigType.STRING]('displayName', value); + }, +}; + +export default memberName; diff --git a/frontend/src/modules/organization/config/filters/headcount/config.ts b/frontend/src/modules/organization/config/filters/headcount/config.ts index 5e951fd13a..9a4333de53 100644 --- a/frontend/src/modules/organization/config/filters/headcount/config.ts +++ b/frontend/src/modules/organization/config/filters/headcount/config.ts @@ -1,25 +1,25 @@ import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; import { - SelectFilterConfig, SelectFilterOptions, - SelectFilterValue, -} from '@/shared/modules/filters/types/filterTypes/SelectFilterConfig'; + MultiSelectFilterConfig, MultiSelectFilterOptions, + MultiSelectFilterValue, +} from '@/shared/modules/filters/types/filterTypes/MultiSelectFilterConfig'; import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; import options from './options'; -const headcount: SelectFilterConfig = { +const headcount: MultiSelectFilterConfig = { id: 'headcount', label: 'Headcount', iconClass: 'ri-group-2-line', - type: FilterConfigType.SELECT, + type: FilterConfigType.MULTISELECT, options: { options, }, - itemLabelRenderer(value: SelectFilterValue, options: SelectFilterOptions): string { - return itemLabelRendererByType[FilterConfigType.SELECT]('Headcount', value, options); + itemLabelRenderer(value: MultiSelectFilterValue, options: MultiSelectFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.MULTISELECT]('Headcount', value, options); }, - apiFilterRenderer({ value, include }: SelectFilterValue): any[] { + apiFilterRenderer({ value, include }: MultiSelectFilterValue): any[] { const filter = { - size: { eq: value }, + or: value.map((count) => ({ size: { eq: count } })), }; return [ (include ? filter : { not: filter }), diff --git a/frontend/src/modules/organization/config/filters/headcount/options.ts b/frontend/src/modules/organization/config/filters/headcount/options.ts index 0d8012f190..b5b4853e9c 100644 --- a/frontend/src/modules/organization/config/filters/headcount/options.ts +++ b/frontend/src/modules/organization/config/filters/headcount/options.ts @@ -23,6 +23,18 @@ const options: MultiSelectFilterOptionGroup[] = [ label: '501-1000', value: '501-1000', }, + { + label: '1001-5000', + value: '1001-5001', + }, + { + label: '5001-10000', + value: '5001-10000', + }, + { + label: '> 10000', + value: '10000+', + }, ], }, ]; diff --git a/frontend/src/shared/dialog/confirm-dialog.js b/frontend/src/shared/dialog/confirm-dialog.js index 554683aaf3..10521aa706 100644 --- a/frontend/src/shared/dialog/confirm-dialog.js +++ b/frontend/src/shared/dialog/confirm-dialog.js @@ -17,6 +17,15 @@ export default ({ confirmButtonText = 'Discard', confirmButtonClass = 'btn btn--md btn--primary', icon = 'ri-error-warning-line', + distinguishCancelAndClose = false, + autofocus = true, + closeOnClickModal = true, + titleClass, + messageClass, + verticalCancelButtonClass, + verticalConfirmButtonClass, + verticalCustomClass, + hideCloseButton = false, }) => { let iconColorClass = 'text-yellow-600'; let iconBgColorClass = 'bg-yellow-100'; @@ -41,25 +50,32 @@ export default ({ class: 'flex', }, // props [ - h( - 'span', // type + type === 'custom' ? h( + 'span', { - class: `rounded-full ${iconBgColorClass} w-10 h-10 flex items-center justify-center absolute custom-icon`, - }, // props - [ - h( - 'i', // type - { - class: `${icon} text-lg ${iconColorClass} leading-none`, - }, // props - [], - ), - ], - ), + innerHTML: icon, + class: '', + }, + ) + : h( + 'span', // type + { + class: `rounded-full ${iconBgColorClass} w-10 h-10 flex items-center justify-center absolute custom-icon`, + }, // props + [ + h( + 'i', // type + { + class: `${icon} text-lg ${iconColorClass} leading-none`, + }, // props + [], + ), + ], + ), h('div', [ h('p', { innerHTML: message, - class: 'text-gray-500 text-sm', + class: `text-gray-500 text-sm ${messageClass}`, }), highlightedInfo ? h( @@ -90,22 +106,29 @@ export default ({ class: 'flex justify-between items-center mb-4', }, [ - h( - 'span', // type + type === 'custom' ? h( + 'span', { - class: `rounded-full ${iconBgColorClass} w-10 h-10 flex items-center justify-center custom-icon`, - }, // props - [ - h( - 'i', // type - { - class: `${icon} text-lg ${iconColorClass} leading-none`, - }, // props - [], - ), - ], - ), - h( + innerHTML: icon, + class: '', + }, + ) + : h( + 'span', // type + { + class: `rounded-full ${iconBgColorClass} w-10 h-10 flex items-center justify-center custom-icon`, + }, // props + [ + h( + 'i', // type + { + class: `${icon} text-lg ${iconColorClass} leading-none`, + }, // props + [], + ), + ], + ), + !hideCloseButton ? h( 'button', { class: @@ -128,12 +151,12 @@ export default ({ [], ), ], - ), + ) : null, ], ), h('h6', { innerHTML: title, - class: 'text-black mb-3', + class: `text-black mb-3 ${titleClass}`, }), badgeContent ? h('div', { @@ -144,7 +167,7 @@ export default ({ : undefined, h('p', { innerHTML: message, - class: 'text-gray-500 text-sm', + class: `text-gray-500 text-sm ${messageClass}`, }), highlightedInfo ? h( @@ -170,7 +193,7 @@ export default ({ ], ); - const overrideCustomClass = 'confirm-dialog confirm-dialog--vertical'; + const overrideCustomClass = `confirm-dialog confirm-dialog--vertical ${verticalCustomClass}`; const overrideConfirmButtonClass = 'btn btn--md btn--primary w-full'; const overrideCancelButtonClass = 'btn btn-link btn-link--md btn-link--primary w-full'; @@ -181,9 +204,12 @@ export default ({ showCancelButton, customClass: overrideCustomClass, confirmButtonText, - confirmButtonClass: overrideConfirmButtonClass, + confirmButtonClass: verticalConfirmButtonClass || overrideConfirmButtonClass, cancelButtonText, - cancelButtonClass: overrideCancelButtonClass, + cancelButtonClass: verticalCancelButtonClass || overrideCancelButtonClass, + distinguishCancelAndClose, + autofocus, + closeOnClickModal, }); } @@ -197,5 +223,8 @@ export default ({ cancelButtonClass, confirmButtonText, confirmButtonClass, + distinguishCancelAndClose, + autofocus, + closeOnClickModal, }); }; diff --git a/frontend/src/shared/modules/filters/components/filterTypes/StringFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/StringFilter.vue index b973b8cb53..ce6eb3585d 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/StringFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/StringFilter.vue @@ -44,6 +44,7 @@ const form = computed({ const defaultForm: StringFilterValue = { value: '', + include: true, operator: FilterStringOperator.LIKE, }; diff --git a/frontend/src/shared/modules/filters/components/partials/FilterIncludeSwitch.vue b/frontend/src/shared/modules/filters/components/partials/FilterIncludeSwitch.vue index a5d9804aad..e958a620e5 100644 --- a/frontend/src/shared/modules/filters/components/partials/FilterIncludeSwitch.vue +++ b/frontend/src/shared/modules/filters/components/partials/FilterIncludeSwitch.vue @@ -1,5 +1,5 @@ @@ -36,7 +43,10 @@ export default {