Files
plane/web/ee/store/dashboards/widget.ts
JayashTripathy 641949f1d7 [WEB-3758] Chore: improved visibility of values in group_by and x-axis field in dashboard configuration (#2873)
* chore: added created by in the dashboard filter

* [WEB-3736] chore: added Created By property in widget configuration x-axis

* [WEB-3758] chore: improved visibility of values in group_by and x-axis field in dashboard configuration

* chore: fixed eslint issues and added conditional check while calling setValue

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Jayash Tripathy <jayashtripathy@Jayashs-MacBook-Pro.local>
2025-04-07 15:58:05 +05:30

254 lines
8.8 KiB
TypeScript

import set from "lodash/set";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
// plane imports
import {
E_FEATURE_FLAGS,
EWidgetChartModels,
EWidgetChartTypes,
EWidgetXAxisDateGrouping,
EWidgetXAxisProperty,
EWidgetYAxisMetric,
WIDGET_CHART_MODELS_LIST,
} from "@plane/constants";
// plane types
import { TDashboardWidget, TDashboardWidgetConfig, TDashboardWidgetData } from "@plane/types";
// plane web store
import { RootStore } from "@/plane-web/store/root.store";
import { TDashboardWidgetHelpers } from "./dashboard-widgets.store";
export interface IDashboardWidgetInstance extends TDashboardWidget {
isFetchingData: boolean;
data: TDashboardWidgetData | null;
// helpers
asJSON: TDashboardWidget;
mutateProperties: (data: Partial<TDashboardWidget>) => void;
isConfigurationMissing: keyof TDashboardWidget | null;
isWidgetAvailableInCurrentPlan: boolean;
// permissions
canCurrentUserDeleteWidget: boolean;
canCurrentUserEditWidget: boolean;
// actions
updateWidget: (data: Partial<TDashboardWidget>) => Promise<TDashboardWidget>;
fetchWidgetData: () => Promise<TDashboardWidgetData | undefined>;
}
export class DashboardWidgetInstance implements IDashboardWidgetInstance {
// observables
isFetchingData: boolean = false;
data: TDashboardWidgetData | null = null;
// widget properties
chart_model: EWidgetChartModels | undefined;
chart_type: EWidgetChartTypes | undefined;
config: TDashboardWidgetConfig | undefined;
created_at: Date | undefined;
created_by: string | undefined;
group_by: EWidgetXAxisProperty | null | undefined;
height: number | undefined;
id: string | undefined;
name: string | undefined;
updated_at: Date | undefined;
updated_by: string | undefined;
width: number | undefined;
x_axis_coord: number | undefined;
x_axis_date_grouping: EWidgetXAxisDateGrouping | null | undefined;
x_axis_property: EWidgetXAxisProperty | null | undefined;
y_axis_coord: number | undefined;
y_axis_metric: EWidgetYAxisMetric | null | undefined;
// helpers
private helpers: TDashboardWidgetHelpers;
// root store
private rootStore: RootStore;
constructor(store: RootStore, widget: TDashboardWidget, helpers: TDashboardWidgetHelpers) {
// initialize widget properties
this.chart_model = widget.chart_model;
this.chart_type = widget.chart_type;
this.config = widget.config;
this.created_at = widget.created_at;
this.created_by = widget.created_by;
this.group_by = widget.group_by;
this.height = widget.height === undefined ? 1 : widget.height;
this.id = widget.id;
this.name = widget.name;
this.updated_at = widget.updated_at;
this.updated_by = widget.updated_by;
this.width = widget.width === undefined ? 1 : widget.width;
this.x_axis_coord = widget.x_axis_coord === undefined ? 0 : widget.x_axis_coord;
this.x_axis_date_grouping = widget.x_axis_date_grouping;
this.x_axis_property = widget.x_axis_property;
this.y_axis_coord = widget.y_axis_coord === undefined ? 0 : widget.y_axis_coord;
this.y_axis_metric = widget.y_axis_metric;
// initialize helpers
this.helpers = helpers;
// initialize root store
this.rootStore = store;
makeObservable(this, {
// observables
isFetchingData: observable.ref,
data: observable,
chart_model: observable.ref,
chart_type: observable.ref,
config: observable,
created_at: observable.ref,
created_by: observable.ref,
group_by: observable.ref,
height: observable.ref,
id: observable.ref,
name: observable.ref,
updated_at: observable.ref,
updated_by: observable.ref,
width: observable.ref,
x_axis_coord: observable.ref,
x_axis_date_grouping: observable.ref,
x_axis_property: observable.ref,
y_axis_coord: observable.ref,
y_axis_metric: observable.ref,
// computed
asJSON: computed,
isConfigurationMissing: computed,
isWidgetAvailableInCurrentPlan: computed,
canCurrentUserDeleteWidget: computed,
canCurrentUserEditWidget: computed,
// actions
mutateProperties: action,
updateWidget: action,
fetchWidgetData: action,
});
}
// helpers
get asJSON() {
return {
chart_model: this.chart_model,
chart_type: this.chart_type,
config: this.config,
created_at: this.created_at,
created_by: this.created_by,
group_by: this.group_by,
height: this.height,
id: this.id,
name: this.name,
updated_at: this.updated_at,
updated_by: this.updated_by,
width: this.width,
x_axis_coord: this.x_axis_coord,
x_axis_date_grouping: this.x_axis_date_grouping,
x_axis_property: this.x_axis_property,
y_axis_coord: this.y_axis_coord,
y_axis_metric: this.y_axis_metric,
};
}
get isConfigurationMissing() {
const chartType = this.chart_type;
const chartModel = this.chart_model;
if (chartType === EWidgetChartTypes.BAR_CHART) {
if (!this.x_axis_property) return "x_axis_property";
if (!this.y_axis_metric) return "y_axis_metric";
if ((chartModel === EWidgetChartModels.STACKED || chartModel === EWidgetChartModels.GROUPED) && !this.group_by)
return "group_by";
}
if (chartType === EWidgetChartTypes.LINE_CHART) {
if (!this.x_axis_property) return "x_axis_property";
if (!this.y_axis_metric) return "y_axis_metric";
if (chartModel === EWidgetChartModels.MULTI_LINE && !this.group_by) return "group_by";
}
if (chartType === EWidgetChartTypes.AREA_CHART) {
if (!this.x_axis_property) return "x_axis_property";
if (!this.y_axis_metric) return "y_axis_metric";
if (chartModel === EWidgetChartModels.STACKED && !this.group_by) return "group_by";
}
if (chartType === EWidgetChartTypes.DONUT_CHART) {
if (chartModel === EWidgetChartModels.PROGRESS) {
if (!this.y_axis_metric) return "y_axis_metric";
} else {
if (!this.x_axis_property) return "x_axis_property";
if (!this.y_axis_metric) return "y_axis_metric";
}
}
if (chartType === EWidgetChartTypes.PIE_CHART) {
if (!this.x_axis_property) return "x_axis_property";
if (!this.y_axis_metric) return "y_axis_metric";
}
if (chartType === EWidgetChartTypes.NUMBER) {
if (!this.y_axis_metric) return "y_axis_metric";
}
return null;
}
get isWidgetAvailableInCurrentPlan() {
const chartType = this.chart_type;
const chartModel = this.chart_model;
if (!chartType || !chartModel) return false;
const currentModelFlags = WIDGET_CHART_MODELS_LIST[chartType].find((model) => model.value === chartModel)?.flags;
const getFeatureFlag = (flag: E_FEATURE_FLAGS) =>
this.rootStore.featureFlags.getFeatureFlagForCurrentWorkspace(flag, false);
return !!currentModelFlags?.some((flag) => getFeatureFlag(flag));
}
mutateProperties: IDashboardWidgetInstance["mutateProperties"] = (data) => {
runInAction(() => {
Object.keys(data).map((key) => {
const dataKey = key as keyof TDashboardWidget;
set(this, [dataKey], data[dataKey]);
});
});
};
// permissions
get canCurrentUserDeleteWidget() {
return this.helpers.permissions.canCurrentUserDeleteWidget;
}
get canCurrentUserEditWidget() {
return this.helpers.permissions.canCurrentUserEditWidget;
}
// actions
updateWidget: IDashboardWidgetInstance["updateWidget"] = async (data) => {
const workspaceSlug = this.rootStore.workspaceRoot.currentWorkspace?.slug;
if (!workspaceSlug || !this.id) throw new Error("Required fields not found");
const originalWidget = { ...this.asJSON };
try {
// optimistically update
this.mutateProperties(data);
const res = await this.helpers.actions.updateWidget(this.id, data);
return res;
} catch (error) {
// revert changes
this.mutateProperties(originalWidget);
// update loader
console.error("Error in updating dashboard widget:", error);
throw error;
}
};
fetchWidgetData: IDashboardWidgetInstance["fetchWidgetData"] = async () => {
const workspaceSlug = this.rootStore.workspaceRoot.currentWorkspace?.slug;
const configurationMissing = this.isConfigurationMissing;
if (configurationMissing) {
return;
}
if (!workspaceSlug || !this.id) throw new Error("Required fields not found");
try {
runInAction(() => {
this.isFetchingData = true;
});
// make api call
const res = await this.helpers.actions.fetchWidgetData(this.id);
runInAction(() => {
this.data = res;
});
return res;
} catch (error) {
console.error("Error in fetching dashboard widget data:", error);
throw error;
} finally {
runInAction(() => {
this.isFetchingData = false;
});
}
};
}