mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: added issues filters & layout related events
This commit is contained in:
parent
b354eb836a
commit
7143c98b2e
@ -31,6 +31,16 @@ import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||
// constants
|
||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
import {
|
||||
DP_APPLIED,
|
||||
DP_REMOVED,
|
||||
elementFromPath,
|
||||
FILTER_APPLIED,
|
||||
FILTER_REMOVED,
|
||||
FILTER_SEARCHED,
|
||||
LAYOUT_CHANGED,
|
||||
LP_UPDATED,
|
||||
} from "constants/event-tracker";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { cn } from "helpers/common.helper";
|
||||
import { CycleMobileHeader } from "components/cycles/cycle-mobile-header";
|
||||
@ -74,7 +84,8 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
const {
|
||||
commandPalette: { toggleCreateIssueModal },
|
||||
} = useApplication();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { setTrackElement, captureEvent, captureIssuesFilterEvent, captureIssuesDisplayFilterEvent } =
|
||||
useEventTracker();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
@ -97,7 +108,13 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
const handleLayoutChange = useCallback(
|
||||
(layout: TIssueLayouts) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, cycleId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, cycleId).then(() =>
|
||||
captureEvent(LAYOUT_CHANGED, {
|
||||
layout: layout,
|
||||
element: elementFromPath(router.asPath),
|
||||
element_id: cycleId,
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, cycleId, updateFilters]
|
||||
);
|
||||
@ -106,17 +123,31 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||
|
||||
let isFilterRemoved = false;
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
else isFilterRemoved = true;
|
||||
});
|
||||
} else {
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) {
|
||||
newValues.splice(newValues.indexOf(value), 1);
|
||||
isFilterRemoved = true;
|
||||
} else newValues.push(value);
|
||||
}
|
||||
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, cycleId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, cycleId).then(() =>
|
||||
captureIssuesFilterEvent({
|
||||
eventName: isFilterRemoved ? FILTER_REMOVED : FILTER_APPLIED,
|
||||
payload: {
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: cycleId,
|
||||
filter_property: value,
|
||||
filter_type: key,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, cycleId, issueFilters, updateFilters]
|
||||
);
|
||||
@ -124,17 +155,39 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
const handleDisplayFilters = useCallback(
|
||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, cycleId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, cycleId).then(
|
||||
() =>
|
||||
captureIssuesDisplayFilterEvent({
|
||||
eventName: LP_UPDATED,
|
||||
payload: {
|
||||
property_type: Object.keys(updatedDisplayFilter).join(","),
|
||||
property: Object.values(updatedDisplayFilter)?.[0],
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: cycleId,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, cycleId, updateFilters]
|
||||
[workspaceSlug, projectId, cycleId, updateFilters, issueFilters]
|
||||
);
|
||||
|
||||
const handleDisplayProperties = useCallback(
|
||||
(property: Partial<IIssueDisplayProperties>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, cycleId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, cycleId).then(() =>
|
||||
captureIssuesDisplayFilterEvent({
|
||||
eventName: Object.values(property)?.[0] === true ? DP_APPLIED : DP_REMOVED,
|
||||
payload: {
|
||||
display_property: Object.keys(property).join(","),
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: cycleId,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, cycleId, updateFilters]
|
||||
[workspaceSlug, projectId, cycleId, updateFilters, issueFilters]
|
||||
);
|
||||
|
||||
// derived values
|
||||
@ -233,6 +286,17 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
states={projectStates}
|
||||
onSearchCapture={() =>
|
||||
captureIssuesFilterEvent({
|
||||
eventName: FILTER_SEARCHED,
|
||||
payload: {
|
||||
path: router.asPath,
|
||||
current_filters: issueFilters?.filters,
|
||||
layout: issueFilters?.displayFilters?.layout,
|
||||
element_id: cycleId,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
|
@ -3,7 +3,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useLabel, useMember, useUser, useIssues } from "hooks/store";
|
||||
import { useLabel, useMember, useUser, useIssues, useEventTracker } from "hooks/store";
|
||||
// components
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "components/issues";
|
||||
import { CreateUpdateWorkspaceViewModal } from "components/workspace";
|
||||
@ -18,6 +18,16 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
||||
// constants
|
||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import {
|
||||
DP_APPLIED,
|
||||
DP_REMOVED,
|
||||
elementFromPath,
|
||||
FILTER_APPLIED,
|
||||
FILTER_REMOVED,
|
||||
FILTER_SEARCHED,
|
||||
LAYOUT_CHANGED,
|
||||
LP_UPDATED,
|
||||
} from "constants/event-tracker";
|
||||
|
||||
const GLOBAL_VIEW_LAYOUTS = [
|
||||
{ key: "list", title: "List", link: "/workspace-views", icon: List },
|
||||
@ -46,6 +56,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
||||
const {
|
||||
workspace: { workspaceMemberIds },
|
||||
} = useMember();
|
||||
const { captureIssuesFilterEvent, captureEvent, captureIssuesDisplayFilterEvent } = useEventTracker();
|
||||
|
||||
const issueFilters = globalViewId ? filters[globalViewId.toString()] : undefined;
|
||||
|
||||
@ -53,14 +64,18 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
if (!workspaceSlug || !globalViewId) return;
|
||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||
let isFilterRemoved = false;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
else isFilterRemoved = true;
|
||||
});
|
||||
} else {
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) {
|
||||
isFilterRemoved = true;
|
||||
newValues.splice(newValues.indexOf(value), 1);
|
||||
} else newValues.push(value);
|
||||
}
|
||||
|
||||
updateFilters(
|
||||
@ -69,7 +84,18 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
||||
EIssueFilterType.FILTERS,
|
||||
{ [key]: newValues },
|
||||
globalViewId.toString()
|
||||
);
|
||||
).then(() => {
|
||||
captureIssuesFilterEvent({
|
||||
eventName: isFilterRemoved ? FILTER_REMOVED : FILTER_APPLIED,
|
||||
payload: {
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: globalViewId,
|
||||
filter_property: value,
|
||||
filter_type: key,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
[workspaceSlug, issueFilters, updateFilters, globalViewId]
|
||||
);
|
||||
@ -83,9 +109,20 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
||||
EIssueFilterType.DISPLAY_FILTERS,
|
||||
updatedDisplayFilter,
|
||||
globalViewId.toString()
|
||||
).then(() =>
|
||||
captureIssuesDisplayFilterEvent({
|
||||
eventName: LP_UPDATED,
|
||||
payload: {
|
||||
property_type: Object.keys(updatedDisplayFilter).join(","),
|
||||
property: Object.values(updatedDisplayFilter)?.[0],
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: globalViewId,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, updateFilters, globalViewId]
|
||||
[workspaceSlug, updateFilters, globalViewId, issueFilters]
|
||||
);
|
||||
|
||||
const handleDisplayProperties = useCallback(
|
||||
@ -97,9 +134,19 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
||||
EIssueFilterType.DISPLAY_PROPERTIES,
|
||||
property,
|
||||
globalViewId.toString()
|
||||
).then(() =>
|
||||
captureIssuesDisplayFilterEvent({
|
||||
eventName: Object.values(property)?.[0] === true ? DP_APPLIED : DP_REMOVED,
|
||||
payload: {
|
||||
display_property: Object.keys(property).join(","),
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: globalViewId,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, updateFilters, globalViewId]
|
||||
[workspaceSlug, updateFilters, globalViewId, issueFilters]
|
||||
);
|
||||
|
||||
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||
@ -160,6 +207,17 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
labels={workspaceLabels ?? undefined}
|
||||
memberIds={workspaceMemberIds ?? undefined}
|
||||
onSearchCapture={() =>
|
||||
captureIssuesFilterEvent({
|
||||
eventName: FILTER_SEARCHED,
|
||||
payload: {
|
||||
path: router.asPath,
|
||||
current_filters: issueFilters?.filters,
|
||||
layout: issueFilters?.displayFilters?.layout,
|
||||
element_id: globalViewId,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
|
@ -31,6 +31,16 @@ import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||
// constants
|
||||
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
import {
|
||||
DP_APPLIED,
|
||||
DP_REMOVED,
|
||||
elementFromPath,
|
||||
FILTER_APPLIED,
|
||||
FILTER_REMOVED,
|
||||
FILTER_SEARCHED,
|
||||
LAYOUT_CHANGED,
|
||||
LP_UPDATED,
|
||||
} from "constants/event-tracker";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { cn } from "helpers/common.helper";
|
||||
import { ModuleMobileHeader } from "components/modules/module-mobile-header";
|
||||
@ -77,7 +87,8 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
const {
|
||||
commandPalette: { toggleCreateIssueModal },
|
||||
} = useApplication();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { setTrackElement, captureEvent, captureIssuesFilterEvent, captureIssuesDisplayFilterEvent } =
|
||||
useEventTracker();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
@ -100,7 +111,13 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
const handleLayoutChange = useCallback(
|
||||
(layout: TIssueLayouts) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, moduleId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, moduleId).then(() =>
|
||||
captureEvent(LAYOUT_CHANGED, {
|
||||
layout: layout,
|
||||
element: elementFromPath(router.asPath),
|
||||
element_id: moduleId,
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||
);
|
||||
@ -109,17 +126,31 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||
|
||||
let isFilterRemoved = false;
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
else isFilterRemoved = true;
|
||||
});
|
||||
} else {
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) {
|
||||
isFilterRemoved = true;
|
||||
newValues.splice(newValues.indexOf(value), 1);
|
||||
} else newValues.push(value);
|
||||
}
|
||||
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, moduleId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, moduleId).then(() => {
|
||||
captureIssuesFilterEvent({
|
||||
eventName: isFilterRemoved ? FILTER_REMOVED : FILTER_APPLIED,
|
||||
payload: {
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: moduleId,
|
||||
filter_property: value,
|
||||
filter_type: key,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
[workspaceSlug, projectId, moduleId, issueFilters, updateFilters]
|
||||
);
|
||||
@ -127,17 +158,39 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
const handleDisplayFilters = useCallback(
|
||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, moduleId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, moduleId).then(
|
||||
() =>
|
||||
captureIssuesDisplayFilterEvent({
|
||||
eventName: LP_UPDATED,
|
||||
payload: {
|
||||
property_type: Object.keys(updatedDisplayFilter).join(","),
|
||||
property: Object.values(updatedDisplayFilter)?.[0],
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: moduleId,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||
[workspaceSlug, projectId, moduleId, updateFilters, issueFilters]
|
||||
);
|
||||
|
||||
const handleDisplayProperties = useCallback(
|
||||
(property: Partial<IIssueDisplayProperties>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, moduleId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, moduleId).then(() =>
|
||||
captureIssuesDisplayFilterEvent({
|
||||
eventName: Object.values(property)?.[0] === true ? DP_APPLIED : DP_REMOVED,
|
||||
payload: {
|
||||
display_property: Object.keys(property).join(","),
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: moduleId,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||
[workspaceSlug, projectId, moduleId, updateFilters, issueFilters]
|
||||
);
|
||||
|
||||
// derived values
|
||||
@ -237,6 +290,17 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
states={projectStates}
|
||||
onSearchCapture={() =>
|
||||
captureIssuesFilterEvent({
|
||||
eventName: FILTER_SEARCHED,
|
||||
payload: {
|
||||
path: router.asPath,
|
||||
current_filters: issueFilters?.filters,
|
||||
layout: issueFilters?.displayFilters?.layout,
|
||||
element_id: moduleId,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
|
@ -23,6 +23,16 @@ import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||
// constants
|
||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
import {
|
||||
DP_APPLIED,
|
||||
DP_REMOVED,
|
||||
elementFromPath,
|
||||
FILTER_APPLIED,
|
||||
FILTER_REMOVED,
|
||||
FILTER_SEARCHED,
|
||||
LAYOUT_CHANGED,
|
||||
LP_UPDATED,
|
||||
} from "constants/event-tracker";
|
||||
// helper
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
@ -45,7 +55,8 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||
const {
|
||||
commandPalette: { toggleCreateIssueModal },
|
||||
} = useApplication();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { captureEvent, setTrackElement, captureIssuesFilterEvent, captureIssuesDisplayFilterEvent } =
|
||||
useEventTracker();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
@ -59,17 +70,31 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||
|
||||
let isFilterRemoved = false;
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
else isFilterRemoved = true;
|
||||
});
|
||||
} else {
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) {
|
||||
newValues.splice(newValues.indexOf(value), 1);
|
||||
isFilterRemoved = true;
|
||||
} else newValues.push(value);
|
||||
}
|
||||
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues });
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }).then(() =>
|
||||
captureIssuesFilterEvent({
|
||||
eventName: isFilterRemoved ? FILTER_REMOVED : FILTER_APPLIED,
|
||||
payload: {
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: projectId,
|
||||
filter_property: value,
|
||||
filter_type: key,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, issueFilters, updateFilters]
|
||||
);
|
||||
@ -77,7 +102,13 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||
const handleLayoutChange = useCallback(
|
||||
(layout: TIssueLayouts) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }).then(() =>
|
||||
captureEvent(LAYOUT_CHANGED, {
|
||||
layout: layout,
|
||||
element: elementFromPath(router.asPath),
|
||||
element_id: projectId,
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, updateFilters]
|
||||
);
|
||||
@ -85,17 +116,38 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||
const handleDisplayFilters = useCallback(
|
||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter).then(() =>
|
||||
captureIssuesDisplayFilterEvent({
|
||||
eventName: LP_UPDATED,
|
||||
payload: {
|
||||
property_type: Object.keys(updatedDisplayFilter).join(","),
|
||||
property: Object.values(updatedDisplayFilter)?.[0],
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: projectId,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, updateFilters]
|
||||
[workspaceSlug, projectId, updateFilters, issueFilters]
|
||||
);
|
||||
|
||||
const handleDisplayProperties = useCallback(
|
||||
(property: Partial<IIssueDisplayProperties>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property).then(() => {
|
||||
captureIssuesDisplayFilterEvent({
|
||||
eventName: Object.values(property)?.[0] === true ? DP_APPLIED : DP_REMOVED,
|
||||
payload: {
|
||||
display_property: Object.keys(property).join(","),
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: projectId,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
[workspaceSlug, projectId, updateFilters]
|
||||
[workspaceSlug, projectId, updateFilters, issueFilters]
|
||||
);
|
||||
|
||||
const deployUrl = process.env.NEXT_PUBLIC_DEPLOY_URL;
|
||||
@ -183,6 +235,17 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
states={projectStates}
|
||||
onSearchCapture={() =>
|
||||
captureIssuesFilterEvent({
|
||||
eventName: FILTER_SEARCHED,
|
||||
payload: {
|
||||
path: router.asPath,
|
||||
current_filters: issueFilters?.filters,
|
||||
layout: issueFilters?.displayFilters?.layout,
|
||||
element_id: projectId,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
|
@ -29,6 +29,16 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
||||
// constants
|
||||
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import {
|
||||
DP_APPLIED,
|
||||
DP_REMOVED,
|
||||
elementFromPath,
|
||||
FILTER_APPLIED,
|
||||
FILTER_REMOVED,
|
||||
FILTER_SEARCHED,
|
||||
LAYOUT_CHANGED,
|
||||
LP_UPDATED,
|
||||
} from "constants/event-tracker";
|
||||
|
||||
export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
// router
|
||||
@ -42,7 +52,8 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { setTrackElement, captureEvent, captureIssuesFilterEvent, captureIssuesDisplayFilterEvent } =
|
||||
useEventTracker();
|
||||
const {
|
||||
commandPalette: { toggleCreateIssueModal },
|
||||
} = useApplication();
|
||||
@ -62,7 +73,13 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
const handleLayoutChange = useCallback(
|
||||
(layout: TIssueLayouts) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, viewId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, viewId).then(() =>
|
||||
captureEvent(LAYOUT_CHANGED, {
|
||||
layout: layout,
|
||||
element: elementFromPath(router.asPath),
|
||||
element_id: viewId,
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, viewId, updateFilters]
|
||||
);
|
||||
@ -71,17 +88,31 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||
|
||||
let isFilterRemoved = false;
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
else isFilterRemoved = true;
|
||||
});
|
||||
} else {
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) {
|
||||
isFilterRemoved = true;
|
||||
newValues.splice(newValues.indexOf(value), 1);
|
||||
} else newValues.push(value);
|
||||
}
|
||||
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, viewId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, viewId).then(() => {
|
||||
captureIssuesFilterEvent({
|
||||
eventName: isFilterRemoved ? FILTER_REMOVED : FILTER_APPLIED,
|
||||
payload: {
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: viewId,
|
||||
filter_property: value,
|
||||
filter_type: key,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
[workspaceSlug, projectId, viewId, issueFilters, updateFilters]
|
||||
);
|
||||
@ -89,17 +120,38 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
const handleDisplayFilters = useCallback(
|
||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, viewId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, viewId).then(() =>
|
||||
captureIssuesDisplayFilterEvent({
|
||||
eventName: LP_UPDATED,
|
||||
payload: {
|
||||
property_type: Object.keys(updatedDisplayFilter).join(","),
|
||||
property: Object.values(updatedDisplayFilter)?.[0],
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: viewId,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, viewId, updateFilters]
|
||||
[workspaceSlug, projectId, viewId, updateFilters, issueFilters]
|
||||
);
|
||||
|
||||
const handleDisplayProperties = useCallback(
|
||||
(property: Partial<IIssueDisplayProperties>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, viewId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, viewId).then(() =>
|
||||
captureIssuesDisplayFilterEvent({
|
||||
eventName: Object.values(property)?.[0] === true ? DP_APPLIED : DP_REMOVED,
|
||||
payload: {
|
||||
display_property: Object.keys(property).join(","),
|
||||
path: router.asPath,
|
||||
filters: issueFilters,
|
||||
element_id: viewId,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, viewId, updateFilters]
|
||||
[workspaceSlug, projectId, viewId, updateFilters, issueFilters]
|
||||
);
|
||||
|
||||
const viewDetails = viewId ? getViewById(viewId.toString()) : null;
|
||||
@ -198,6 +250,17 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
states={projectStates}
|
||||
onSearchCapture={() =>
|
||||
captureIssuesFilterEvent({
|
||||
eventName: FILTER_SEARCHED,
|
||||
payload: {
|
||||
path: router.asPath,
|
||||
current_filters: issueFilters?.filters,
|
||||
layout: issueFilters?.displayFilters?.layout,
|
||||
element_id: projectId,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Search, X } from "lucide-react";
|
||||
// components
|
||||
@ -14,6 +14,8 @@ import {
|
||||
FilterStateGroup,
|
||||
FilterTargetDate,
|
||||
} from "components/issues";
|
||||
// hooks
|
||||
import useDebounce from "hooks/use-debounce";
|
||||
// types
|
||||
import { IIssueFilterOptions, IIssueLabel, IState } from "@plane/types";
|
||||
// constants
|
||||
@ -26,14 +28,21 @@ type Props = {
|
||||
labels?: IIssueLabel[] | undefined;
|
||||
memberIds?: string[] | undefined;
|
||||
states?: IState[] | undefined;
|
||||
onSearchCapture?: () => void;
|
||||
};
|
||||
|
||||
export const FilterSelection: React.FC<Props> = observer((props) => {
|
||||
const { filters, handleFiltersUpdate, layoutDisplayFiltersOptions, labels, memberIds, states } = props;
|
||||
const { filters, handleFiltersUpdate, layoutDisplayFiltersOptions, labels, memberIds, states, onSearchCapture } =
|
||||
props;
|
||||
// states
|
||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
||||
|
||||
const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions?.filters.includes(filter);
|
||||
const debouncedValue = useDebounce(filtersSearchQuery, 1500);
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedValue && onSearchCapture) onSearchCapture();
|
||||
}, [debouncedValue]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||
|
@ -44,7 +44,7 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
|
||||
} = useUser();
|
||||
const { fetchAllGlobalViews } = useGlobalView();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { setTrackElement, captureIssuesListOpenedEvent } = useEventTracker();
|
||||
|
||||
const isDefaultView = ["all-issues", "assigned", "created", "subscribed"].includes(groupedIssueIds.dataViewId);
|
||||
const currentView = isDefaultView ? groupedIssueIds.dataViewId : "custom-view";
|
||||
@ -100,6 +100,10 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
|
||||
if (workspaceSlug && globalViewId) {
|
||||
await fetchAllGlobalViews(workspaceSlug.toString());
|
||||
await fetchFilters(workspaceSlug.toString(), globalViewId.toString());
|
||||
captureIssuesListOpenedEvent({
|
||||
path: router.asPath,
|
||||
element_id: globalViewId,
|
||||
});
|
||||
await fetchIssues(workspaceSlug.toString(), globalViewId.toString(), issueIds ? "mutation" : "init-loader");
|
||||
routerFilterParams();
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ISSUE_ORDER_BY_OPTIONS } from "./issue";
|
||||
|
||||
export type IssueEventProps = {
|
||||
eventName: string;
|
||||
payload: any;
|
||||
@ -124,23 +126,40 @@ export const getProjectStateEventPayload = (payload: any) => {
|
||||
export const getIssuesListOpenedPayload = (payload: any) => ({
|
||||
element: elementFromPath(payload.path),
|
||||
element_id: payload.element_id,
|
||||
type: payload.project_id ? "Project" : "Workspace",
|
||||
layout: payload?.displayFilters?.layout,
|
||||
filters: payload?.filters,
|
||||
display_properties: payload?.displayProperties,
|
||||
});
|
||||
|
||||
export const getIssuesFilterEventPayload = (payload: any) => ({
|
||||
filter_type: payload?.filter_type,
|
||||
filter_property: payload?.filter_property,
|
||||
layout: payload?.filters?.displayFilters?.layout,
|
||||
current_filters: payload?.filters?.filters,
|
||||
element: elementFromPath(payload.path),
|
||||
element_id: payload.element_id,
|
||||
type: payload.project_id ? "Project" : "Workspace",
|
||||
layout: payload?.displayFilters?.layout,
|
||||
filters: payload?.filters,
|
||||
display_properties: payload?.displayProperties,
|
||||
element_id: payload.element_id,
|
||||
});
|
||||
|
||||
const elementFromPath = (path?: string) => {
|
||||
if (path?.includes("workspace-views")) return "Workspace view";
|
||||
export const getIssuesDisplayFilterPayload = (payload: any) => {
|
||||
const property =
|
||||
payload.property_type == "order_by"
|
||||
? ISSUE_ORDER_BY_OPTIONS?.filter((option) => option.key === payload.property)?.[0]
|
||||
.title.toLocaleLowerCase()
|
||||
.replaceAll(" ", "_")
|
||||
: payload.property;
|
||||
return {
|
||||
layout: payload?.filters?.displayFilters?.layout,
|
||||
current_display_properties: payload?.filters?.displayProperties,
|
||||
element: elementFromPath(payload.path),
|
||||
element_id: payload.element_id,
|
||||
display_property: payload.display_property,
|
||||
property: property,
|
||||
property_type: payload.property_type,
|
||||
};
|
||||
};
|
||||
|
||||
export const elementFromPath = (path?: string) => {
|
||||
if (path?.includes("workspace-views")) return "Global view";
|
||||
if (path?.includes("cycles")) return "Cycle";
|
||||
if (path?.includes("modules")) return "Module";
|
||||
if (path?.includes("views")) return "Project view";
|
||||
@ -190,6 +209,12 @@ export const ISSUE_OPENED = "Issue opened";
|
||||
export const FILTER_APPLIED = "Filter applied";
|
||||
export const FILTER_REMOVED = "Filter removed";
|
||||
export const FILTER_SEARCHED = "Filter searched";
|
||||
// Issues Display Property Events
|
||||
export const DP_APPLIED = "Display property applied";
|
||||
export const DP_REMOVED = "Display property removed";
|
||||
// Issues Layout Property Event
|
||||
export const LP_UPDATED = "Layout property updated";
|
||||
export const LAYOUT_CHANGED = "Layout changed";
|
||||
// Project State Events
|
||||
export const STATE_CREATED = "State created";
|
||||
export const STATE_UPDATED = "State updated";
|
||||
|
@ -19,6 +19,9 @@ import {
|
||||
getPageEventPayload,
|
||||
ISSUES_LIST_OPENED,
|
||||
getIssuesListOpenedPayload,
|
||||
getIssuesFilterEventPayload,
|
||||
getIssuesDisplayFilterPayload,
|
||||
LP_UPDATED,
|
||||
} from "constants/event-tracker";
|
||||
|
||||
export interface IEventTrackerStore {
|
||||
@ -40,6 +43,8 @@ export interface IEventTrackerStore {
|
||||
captureIssueEvent: (props: IssueEventProps) => void;
|
||||
captureProjectStateEvent: (props: EventProps) => void;
|
||||
captureIssuesListOpenedEvent: (payload: any) => void;
|
||||
captureIssuesFilterEvent: (props: EventProps) => void;
|
||||
captureIssuesDisplayFilterEvent: (props: EventProps) => void;
|
||||
}
|
||||
|
||||
export class EventTrackerStore implements IEventTrackerStore {
|
||||
@ -245,15 +250,46 @@ export class EventTrackerStore implements IEventTrackerStore {
|
||||
|
||||
/**
|
||||
* @description: Captures the event whenever the issues list is opened.
|
||||
* @param {string} path
|
||||
* @param {any} filters
|
||||
* @param {any} payload
|
||||
*/
|
||||
captureIssuesListOpenedEvent = (payload: any) => {
|
||||
const eventPayload = {
|
||||
...getIssuesListOpenedPayload(payload),
|
||||
...this.getRequiredProperties,
|
||||
type: this.getRequiredProperties.project_id ? "Project" : "Workspace",
|
||||
};
|
||||
posthog?.capture(ISSUES_LIST_OPENED, eventPayload);
|
||||
this.setTrackElement(undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description: Captures the event whenever the issues filters are changed.
|
||||
* @param {IssueEventProps} props
|
||||
*/
|
||||
captureIssuesFilterEvent = (props: EventProps) => {
|
||||
const { eventName, payload } = props;
|
||||
const eventPayload = {
|
||||
...getIssuesFilterEventPayload(payload),
|
||||
...this.getRequiredProperties,
|
||||
type: this.getRequiredProperties.project_id ? "Project" : "Workspace",
|
||||
};
|
||||
posthog?.capture(eventName, eventPayload);
|
||||
this.setTrackElement(undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description: Captures the event whenever the issues display-filters are changed.
|
||||
* @param {IssueEventProps} props
|
||||
*/
|
||||
captureIssuesDisplayFilterEvent = (props: EventProps) => {
|
||||
const { eventName, payload } = props;
|
||||
const eventPayload = {
|
||||
...getIssuesDisplayFilterPayload(payload),
|
||||
...this.getRequiredProperties,
|
||||
type: this.getRequiredProperties.project_id ? "Project" : "Workspace",
|
||||
current_display_filter: eventName === LP_UPDATED ? payload?.filters?.displayFilters : undefined,
|
||||
};
|
||||
posthog?.capture(eventName, eventPayload);
|
||||
this.setTrackElement(undefined);
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user