[WEB-4125] feat: add feature flag for dashboard widgets filters (#4083)

This commit is contained in:
Jayash Tripathy
2025-09-03 13:36:59 +05:30
committed by GitHub
parent 560e6542ff
commit cdec24495e
5 changed files with 45 additions and 33 deletions

View File

@@ -21,7 +21,10 @@ from plane.ee.utils.chart_validations import validate_chart_config
from plane.ee.utils.widget_graph_plot import build_widget_chart
from plane.ee.views.base import BaseAPIView
from plane.payment.flags.flag import FeatureFlag
from plane.payment.flags.flag_decorator import check_feature_flag
from plane.payment.flags.flag_decorator import (
check_feature_flag,
check_workspace_feature_flag,
)
from plane.utils.filters.complex_filter import ComplexFilterBackend
@@ -275,12 +278,15 @@ class WidgetListEndpoint(BaseAPIView):
project_id__in=dashboard_project_ids,
)
# get the widget filter
if dashboard_widget.filters:
# use the complex filter backend using dashboard widget filters
issues = ComplexFilterBackend().filter_queryset(
request, issues, self, dashboard_widget.filters
)
if check_workspace_feature_flag(
FeatureFlag.DASHBOARD_WIDGET_FILTERS, slug, user_id=str(request.user.id)
):
# get the widget filter
if dashboard_widget.filters:
# use the complex filter backend using dashboard widget filters
issues = ComplexFilterBackend().filter_queryset(
request, issues, self, dashboard_widget.filters
)
issues = (
issues.filter(

View File

@@ -90,6 +90,8 @@ class FeatureFlag(Enum):
PROJECT_AUTOMATIONS = "PROJECT_AUTOMATIONS"
# Recurring Work Items
RECURRING_WORKITEMS = "RECURRING_WORKITEMS"
# Dashboard widgets filters
DASHBOARD_WIDGET_FILTERS = "DASHBOARD_WIDGET_FILTERS"
class AdminFeatureFlag(Enum):

View File

@@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useMemo } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { AtSign, CircleUserRound, Files, Layers, SignalHigh, Tag, Users } from "lucide-react";
@@ -19,7 +19,7 @@ import {
TIssuePriorities,
TIssueType,
} from "@plane/types";
import { Avatar, CycleGroupIcon, DiceIcon, Loader, PriorityIcon } from "@plane/ui";
import { Avatar, CycleGroupIcon, DiceIcon, PriorityIcon } from "@plane/ui";
import {
cn,
getAssigneeFilterConfig,
@@ -40,6 +40,7 @@ import { useCycle } from "@/hooks/store/use-cycle";
import { useLabel } from "@/hooks/store/use-label";
import { useMember } from "@/hooks/store/use-member";
import { useModule } from "@/hooks/store/use-module";
import { useProject } from "@/hooks/store/use-project";
import { IssueTypeLogo } from "@/plane-web/components/issue-types/common/issue-type-logo";
import { AddFilterButton } from "@/plane-web/components/rich-filters/add-filters-button";
@@ -47,7 +48,6 @@ import { FilterItem } from "@/plane-web/components/rich-filters/filter-item";
import { useIssueTypes } from "@/plane-web/hooks/store";
import { IFilterInstance } from "@/plane-web/store/rich-filters/filter";
import { withFilters } from "./with-filters";
import { useProject } from "@/hooks/store/use-project";
type Props = {
filters: IFilterInstance<TDashboardWidgetFilterKeys, TExternalDashboardWidgetFilterExpression> | null;
@@ -55,22 +55,20 @@ type Props = {
handleSubmit: (data: Partial<TDashboardWidget>) => Promise<void>;
};
const WidgetConfigSidebarFilters: React.FC<Props> = observer((props) => {
const WidgetConfigSidebarFiltersRoot: React.FC<Props> = observer((props) => {
const { filters, projectIds } = props;
/**
* store hooks
*/
const { getProjectLabels, fetchProjectLabels } = useLabel();
const { getProjectLabels } = useLabel();
const { workspaceSlug } = useParams();
const { getUserDetails } = useMember();
const { getProjectById } = useProject();
const {
project: { getProjectMemberIds, fetchProjectMembers },
} = useMember();
const { getProjectCycleDetails, fetchAllCycles } = useCycle();
const { getProjectModuleDetails, fetchModules } = useModule();
const { getProjectIssueTypes, fetchAll } = useIssueTypes();
const { getProjectCycleDetails } = useCycle();
const { getProjectModuleDetails } = useModule();
const { getProjectIssueTypes } = useIssueTypes();
/**
* derived values
@@ -135,17 +133,12 @@ const WidgetConfigSidebarFilters: React.FC<Props> = observer((props) => {
}, [
workspaceSlug,
projectIds,
fetchProjectLabels,
getProjectLabels,
fetchProjectMembers,
getProjectMemberIds,
getUserDetails,
fetchAllCycles,
getProjectById,
getProjectCycleDetails,
fetchModules,
getProjectModuleDetails,
fetchAll,
getProjectIssueTypes,
getUserDetails,
]);
/**
@@ -306,4 +299,4 @@ const WidgetConfigSidebarFilters: React.FC<Props> = observer((props) => {
);
});
export const EnhancedWidgetConfigSidebarFilters = withFilters(WidgetConfigSidebarFilters);
export const WidgetConfigSidebarFilters = withFilters(WidgetConfigSidebarFiltersRoot);

View File

@@ -1,5 +1,6 @@
import { useCallback, useEffect } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { FormProvider, useForm } from "react-hook-form";
// plane imports
@@ -10,11 +11,12 @@ import { setToast, TOAST_TYPE } from "@plane/ui";
import { cn } from "@plane/utils";
// plane web hooks
import { useDashboards } from "@/plane-web/hooks/store";
import { useDashboards, useFlag } from "@/plane-web/hooks/store";
// local components
import { WidgetConfigSidebarAxisConfig } from "./axis-config";
import { WidgetConfigSidebarBasicConfig } from "./basic-config";
import { EnhancedWidgetConfigSidebarFilters } from "./filters";
import { WidgetConfigSidebarFilters } from "./filters";
import { WidgetConfigSidebarHeader } from "./header";
import { WidgetConfigSidebarStyleConfig } from "./style-config/root";
@@ -25,6 +27,8 @@ type Props = {
export const DashboardsWidgetConfigSidebarRoot: React.FC<Props> = observer((props) => {
const { className, dashboardId } = props;
const { workspaceSlug } = useParams();
// store hooks
const { getDashboardById } = useDashboards();
// derived values
@@ -33,6 +37,8 @@ export const DashboardsWidgetConfigSidebarRoot: React.FC<Props> = observer((prop
const { isViewModeEnabled, widgetsStore, project_ids } = dashboardDetails ?? {};
// translation
const { t } = useTranslation();
// feature flag
const isDashboardWidgetsFiltersEnabled = useFlag(workspaceSlug?.toString(), "DASHBOARD_WIDGET_FILTERS");
const { isEditingWidget: widgetIdToEdit, getWidgetById, toggleEditWidget, toggleDeleteWidget } = widgetsStore ?? {};
const isEditingWidget = !!widgetIdToEdit;
@@ -49,6 +55,7 @@ export const DashboardsWidgetConfigSidebarRoot: React.FC<Props> = observer((prop
filters,
} = widget ?? {};
const shouldShowSidebar = !isViewModeEnabled && !!widgetIdToEdit;
// form info
const methods = useForm<TDashboardWidget>();
const { handleSubmit, reset, setValue } = methods;
@@ -134,6 +141,7 @@ export const DashboardsWidgetConfigSidebarRoot: React.FC<Props> = observer((prop
handleFormSubmit({ name: autoGeneratedInitialName });
}
}, [isConfigurationMissing, widget?.name, x_axis_property, y_axis_metric, group_by, t, setValue, handleFormSubmit]);
return (
<form
onSubmit={handleSubmit(handleFormSubmit)}
@@ -149,11 +157,13 @@ export const DashboardsWidgetConfigSidebarRoot: React.FC<Props> = observer((prop
<div className="flex-shrink-0 h-px bg-custom-background-80" />
<WidgetConfigSidebarStyleConfig handleSubmit={handleFormSubmit} />
<div className="flex-shrink-0 h-px bg-custom-background-80" />
<EnhancedWidgetConfigSidebarFilters
handleSubmit={handleFormSubmit}
projectIds={project_ids}
initialFilters={filters ?? undefined}
/>
{isDashboardWidgetsFiltersEnabled && (
<WidgetConfigSidebarFilters
handleSubmit={handleFormSubmit}
projectIds={project_ids}
initialFilters={filters ?? undefined}
/>
)}
</FormProvider>
</form>
);

View File

@@ -86,6 +86,7 @@ export enum E_FEATURE_FLAGS {
// dashboards
DASHBOARDS = "DASHBOARDS",
DASHBOARDS_ADVANCED = "DASHBOARDS_ADVANCED",
DASHBOARD_WIDGET_FILTERS = "DASHBOARD_WIDGET_FILTERS",
// applications
APPLICATIONS = "APPLICATIONS",
// pages and editor