Files
formbricks-formbricks/packages/lib/segment/utils.ts
Dhruwang Jariwala ab80bc1bf2 feat: language switch (#2692)
Co-authored-by: Johannes <johannes@formbricks.com>
Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
2024-06-12 14:10:22 +00:00

607 lines
15 KiB
TypeScript

import { createId } from "@paralleldrive/cuid2";
import {
TActionMetric,
TAllOperators,
TAttributeOperator,
TBaseFilter,
TBaseFilters,
TDeviceOperator,
TSegment,
TSegmentActionFilter,
TSegmentAttributeFilter,
TSegmentConnector,
TSegmentDeviceFilter,
TSegmentFilter,
TSegmentOperator,
TSegmentPersonFilter,
TSegmentSegmentFilter,
} from "@formbricks/types/segment";
// type guard to check if a resource is a filter
export const isResourceFilter = (resource: TSegmentFilter | TBaseFilters): resource is TSegmentFilter => {
return (resource as TSegmentFilter).root !== undefined;
};
export const convertOperatorToText = (operator: TAllOperators) => {
switch (operator) {
case "equals":
return "=";
case "notEquals":
return "!=";
case "lessThan":
return "<";
case "lessEqual":
return "<=";
case "greaterThan":
return ">";
case "greaterEqual":
return ">=";
case "isSet":
return "is set";
case "isNotSet":
return "is not set";
case "contains":
return "contains ";
case "doesNotContain":
return "does not contain";
case "startsWith":
return "starts with";
case "endsWith":
return "ends with";
case "userIsIn":
return "User is in";
case "userIsNotIn":
return "User is not in";
default:
return operator;
}
};
export const convertOperatorToTitle = (operator: TAllOperators) => {
switch (operator) {
case "equals":
return "Equals";
case "notEquals":
return "Not equals to";
case "lessThan":
return "Less than";
case "lessEqual":
return "Less than or equal to";
case "greaterThan":
return "Greater than";
case "greaterEqual":
return "Greater than or equal to";
case "isSet":
return "Is set";
case "isNotSet":
return "Is not set";
case "contains":
return "Contains";
case "doesNotContain":
return "Does not contain";
case "startsWith":
return "Starts with";
case "endsWith":
return "Ends with";
case "userIsIn":
return "User is in";
case "userIsNotIn":
return "User is not in";
default:
return operator;
}
};
export const convertMetricToText = (metric: TActionMetric) => {
switch (metric) {
case "lastQuarterCount":
return "Last quarter (Count)";
case "lastMonthCount":
return "Last month (Count)";
case "lastWeekCount":
return "Last week (Count)";
case "occuranceCount":
return "Occurance (Count)";
case "lastOccurranceDaysAgo":
return "Last occurrance (Days ago)";
case "firstOccurranceDaysAgo":
return "First occurrance (Days ago)";
default:
return metric;
}
};
export const addFilterBelow = (group: TBaseFilters, resourceId: string, filter: TBaseFilter) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
if (resource.id === resourceId) {
group.splice(i + 1, 0, filter);
break;
}
} else {
if (group[i].id === resourceId) {
group.splice(i + 1, 0, filter);
break;
} else {
addFilterBelow(resource, resourceId, filter);
}
}
}
};
export const createGroupFromResource = (group: TBaseFilters, resourceId: string) => {
for (let i = 0; i < group.length; i++) {
const filters = group[i];
if (isResourceFilter(filters.resource)) {
if (filters.resource.id === resourceId) {
const newGroupToAdd: TBaseFilter = {
id: createId(),
connector: filters.connector,
resource: [
{
...filters,
connector: null,
},
],
};
group.splice(i, 1, newGroupToAdd);
break;
}
} else {
if (group[i].id === resourceId) {
// make an outer group, wrap the current group in it and add a filter below it
// const newFilter: TBaseFilter = {
// id: createId(),
// connector: "and",
// resource: {
// id: createId(),
// root: { type: "attribute", attributeClassName: "" },
// qualifier: { operator: "endsWith" },
// value: "",
// },
// };
const outerGroup: TBaseFilter = {
connector: filters.connector,
id: createId(),
resource: [{ ...filters, connector: null }],
};
group.splice(i, 1, outerGroup);
break;
} else {
createGroupFromResource(filters.resource, resourceId);
}
}
}
};
export const moveResourceUp = (group: TBaseFilters, i: number) => {
if (i === 0) {
return;
}
const previousTemp = group[i - 1];
group[i - 1] = group[i];
group[i] = previousTemp;
if (i - 1 === 0) {
const newConnector = group[i - 1].connector;
group[i - 1].connector = null;
group[i].connector = newConnector;
}
};
export const moveResourceDown = (group: TBaseFilters, i: number) => {
if (i === group.length - 1) {
return;
}
const temp = group[i + 1];
group[i + 1] = group[i];
group[i] = temp;
// after the swap, determine if the connector should be null or not
if (i === 0) {
const nextConnector = group[i].connector;
group[i].connector = null;
group[i + 1].connector = nextConnector;
}
};
export const moveResource = (group: TBaseFilters, resourceId: string, direction: "up" | "down") => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
if (resource.id === resourceId) {
if (direction === "up") {
moveResourceUp(group, i);
break;
} else {
moveResourceDown(group, i);
break;
}
}
} else {
if (group[i].id === resourceId) {
if (direction === "up") {
moveResourceUp(group, i);
break;
} else {
moveResourceDown(group, i);
break;
}
}
moveResource(resource, resourceId, direction);
}
}
};
export const deleteResource = (group: TBaseFilters, resourceId: string) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource) && resource.id === resourceId) {
group.splice(i, 1);
if (group.length) {
group[0].connector = null;
}
break;
} else if (!isResourceFilter(resource) && group[i].id === resourceId) {
group.splice(i, 1);
if (group.length) {
group[0].connector = null;
}
break;
} else if (!isResourceFilter(resource)) {
deleteResource(resource, resourceId);
}
}
// check and delete all empty groups
deleteEmptyGroups(group);
};
export const deleteEmptyGroups = (group: TBaseFilters) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (!isResourceFilter(resource) && resource.length === 0) {
group.splice(i, 1);
} else if (!isResourceFilter(resource)) {
deleteEmptyGroups(resource);
}
}
};
export const addFilterInGroup = (group: TBaseFilters, groupId: string, filter: TBaseFilter) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
continue;
} else {
if (group[i].id === groupId) {
const { resource } = group[i];
if (!isResourceFilter(resource)) {
if (resource.length === 0) {
resource.push({
...filter,
connector: null,
});
} else {
resource.push(filter);
}
}
break;
} else {
addFilterInGroup(resource, groupId, filter);
}
}
}
};
export const toggleGroupConnector = (
group: TBaseFilters,
groupId: string,
newConnectorValue: TSegmentConnector
) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (!isResourceFilter(resource)) {
if (group[i].id === groupId) {
group[i].connector = newConnectorValue;
break;
} else {
toggleGroupConnector(resource, groupId, newConnectorValue);
}
}
}
};
export const toggleFilterConnector = (
group: TBaseFilters,
filterId: string,
newConnectorValue: TSegmentConnector
) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
if (resource.id === filterId) {
group[i].connector = newConnectorValue;
}
} else {
toggleFilterConnector(resource, filterId, newConnectorValue);
}
}
};
export const updateOperatorInFilter = (
group: TBaseFilters,
filterId: string,
newOperator: TAttributeOperator | TSegmentOperator | TDeviceOperator
) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
if (resource.id === filterId) {
resource.qualifier.operator = newOperator;
break;
}
} else {
updateOperatorInFilter(resource, filterId, newOperator);
}
}
};
export const updateAttributeClassNameInFilter = (
group: TBaseFilters,
filterId: string,
newAttributeClassName: string
) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
if (resource.id === filterId) {
(resource as TSegmentAttributeFilter).root.attributeClassName = newAttributeClassName;
break;
}
} else {
updateAttributeClassNameInFilter(resource, filterId, newAttributeClassName);
}
}
};
export const updatePersonIdentifierInFilter = (
group: TBaseFilters,
filterId: string,
newPersonIdentifier: string
) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
if (resource.id === filterId) {
(resource as TSegmentPersonFilter).root.personIdentifier = newPersonIdentifier;
}
} else {
updatePersonIdentifierInFilter(resource, filterId, newPersonIdentifier);
}
}
};
export const updateActionClassIdInFilter = (
group: TBaseFilters,
filterId: string,
newActionClassId: string
) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
if (resource.id === filterId) {
(resource as TSegmentActionFilter).root.actionClassId = newActionClassId;
break;
}
} else {
updateActionClassIdInFilter(resource, filterId, newActionClassId);
}
}
};
export const updateMetricInFilter = (group: TBaseFilters, filterId: string, newMetric: TActionMetric) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
if (resource.id === filterId) {
(resource as TSegmentActionFilter).qualifier.metric = newMetric;
break;
}
} else {
updateMetricInFilter(resource, filterId, newMetric);
}
}
};
export const updateSegmentIdInFilter = (group: TBaseFilters, filterId: string, newSegmentId: string) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
if (resource.id === filterId) {
(resource as TSegmentSegmentFilter).root.segmentId = newSegmentId;
resource.value = newSegmentId;
break;
}
} else {
updateSegmentIdInFilter(resource, filterId, newSegmentId);
}
}
};
export const updateFilterValue = (group: TBaseFilters, filterId: string, newValue: string | number) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
if (resource.id === filterId) {
resource.value = newValue;
break;
}
} else {
updateFilterValue(resource, filterId, newValue);
}
}
};
export const updateDeviceTypeInFilter = (
group: TBaseFilters,
filterId: string,
newDeviceType: "phone" | "desktop"
) => {
for (let i = 0; i < group.length; i++) {
const { resource } = group[i];
if (isResourceFilter(resource)) {
if (resource.id === filterId) {
(resource as TSegmentDeviceFilter).root.deviceType = newDeviceType;
resource.value = newDeviceType;
break;
}
} else {
updateDeviceTypeInFilter(resource, filterId, newDeviceType);
}
}
};
export const formatSegmentDateFields = (segment: TSegment): TSegment => {
if (typeof segment.createdAt === "string") {
segment.createdAt = new Date(segment.createdAt);
}
if (typeof segment.updatedAt === "string") {
segment.updatedAt = new Date(segment.updatedAt);
}
return segment;
};
export const searchForAttributeClassNameInSegment = (
filters: TBaseFilters,
attributeClassName: string
): boolean => {
for (let filter of filters) {
const { resource } = filter;
if (isResourceFilter(resource)) {
const { root } = resource;
const { type } = root;
if (type === "attribute") {
const { attributeClassName: className } = root;
if (className === attributeClassName) {
return true;
}
}
} else {
const found = searchForAttributeClassNameInSegment(resource, attributeClassName);
if (found) {
return true;
}
}
}
return false;
};
// check if a segment has a filter with "type" other than "attribute" or "person"
// if it does, this is an advanced segment
export const isAdvancedSegment = (filters: TBaseFilters): boolean => {
for (let filter of filters) {
const { resource } = filter;
if (isResourceFilter(resource)) {
const { root } = resource;
const { type } = root;
if (type !== "attribute" && type !== "person") {
return true;
}
} else {
// the resource is a group, so we don't need to recurse, we know that this is an advanced segment
return true;
}
}
return false;
};
type TAttributeFilter = {
attributeClassName: string;
operator: TAttributeOperator;
value: string;
};
export const transformSegmentFiltersToAttributeFilters = (
filters: TBaseFilters
): TAttributeFilter[] | null => {
const attributeFilters: TAttributeFilter[] = [];
for (let filter of filters) {
const { resource } = filter;
if (isResourceFilter(resource)) {
const { root, qualifier, value } = resource;
const { type } = root;
if (type === "attribute") {
const { attributeClassName } = root;
const { operator } = qualifier;
attributeFilters.push({
attributeClassName,
operator: operator as TAttributeOperator,
value: value.toString(),
});
}
if (type === "person") {
const { operator } = qualifier;
attributeFilters.push({
attributeClassName: "userId",
operator: operator as TAttributeOperator,
value: value.toString(),
});
}
} else {
// the resource is a group, so we don't need to recurse, we know that this is an advanced segment
return null;
}
}
return attributeFilters;
};