mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
Merge branch 'main' into shubham/saturn-integration-v2
This commit is contained in:
@@ -147,7 +147,7 @@ These variables can be provided at the runtime i.e. in your docker-compose file.
|
||||
| S3_ACCESS_KEY | Access key for S3. | optional | (resolved by the AWS SDK) |
|
||||
| S3_SECRET_KEY | Secret key for S3. | optional | (resolved by the AWS SDK) |
|
||||
| S3_REGION | Region for S3. | optional | (resolved by the AWS SDK) |
|
||||
| S3_BUCKET | Bucket name for S3. | optional (required if S3 is enabled) | |
|
||||
| S3_BUCKET_NAME | Bucket name for S3. | optional (required if S3 is enabled) | |
|
||||
| S3_ENDPOINT | Endpoint for S3. | optional | (resolved by the AWS SDK) |
|
||||
| PRIVACY_URL | URL for privacy policy. | optional | |
|
||||
| TERMS_URL | URL for terms of service. | optional | |
|
||||
|
||||
@@ -38,9 +38,7 @@ export default async function AttributesSection({ personId }: { personId: string
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-slate-500">User Id</dt>
|
||||
<dd className="ph-no-capture mt-1 text-sm text-slate-900">
|
||||
{person.attributes.userId ? (
|
||||
<span>{person.attributes.userId}</span>
|
||||
) : person.userId ? (
|
||||
{person.userId ? (
|
||||
<span>{person.userId}</span>
|
||||
) : (
|
||||
<span className="text-slate-300">Not provided</span>
|
||||
@@ -53,7 +51,7 @@ export default async function AttributesSection({ personId }: { personId: string
|
||||
</div>
|
||||
|
||||
{Object.entries(person.attributes)
|
||||
.filter(([key, _]) => key !== "email" && key !== "userId" && key !== "language")
|
||||
.filter(([key, _]) => key !== "email" && key !== "language")
|
||||
.map(([key, value]) => (
|
||||
<div key={key}>
|
||||
<dt className="text-sm font-medium text-slate-500">{capitalizeFirstLetter(key.toString())}</dt>
|
||||
@@ -62,10 +60,6 @@ export default async function AttributesSection({ personId }: { personId: string
|
||||
))}
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
{/* <dt className="text-sm font-medium text-slate-500">Sessions</dt> */}
|
||||
{/* <dd className="mt-1 text-sm text-slate-900">{numberOfSessions}</dd> */}
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-slate-500">Responses</dt>
|
||||
<dd className="mt-1 text-sm text-slate-900">{numberOfResponses}</dd>
|
||||
|
||||
@@ -4,7 +4,6 @@ import Link from "next/link";
|
||||
import { ITEMS_PER_PAGE } from "@formbricks/lib/constants";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getPeople, getPeopleCount } from "@formbricks/lib/person/service";
|
||||
import { truncateMiddle } from "@formbricks/lib/strings";
|
||||
import { TPerson } from "@formbricks/types/people";
|
||||
import { PersonAvatar } from "@formbricks/ui/Avatars";
|
||||
import EmptySpaceFiller from "@formbricks/ui/EmptySpaceFiller";
|
||||
@@ -83,9 +82,7 @@ export default async function PeoplePage({
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 my-auto hidden whitespace-nowrap text-center text-sm text-slate-500 sm:block">
|
||||
<div className="ph-no-capture text-slate-900">
|
||||
{truncateMiddle(getAttributeValue(person, "userId"), 24) || person.userId}
|
||||
</div>
|
||||
<div className="ph-no-capture text-slate-900">{person.userId}</div>
|
||||
</div>
|
||||
<div className="col-span-2 my-auto hidden whitespace-nowrap text-center text-sm text-slate-500 sm:block">
|
||||
<div className="ph-no-capture text-slate-900">{getAttributeValue(person, "email")}</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ export const CTASummary = ({ questionSummary }: CTASummaryProps) => {
|
||||
<div className="space-y-5 rounded-b-lg bg-white px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
<div className="text flex justify-between px-2 pb-2">
|
||||
<div className="mr-8 flex space-x-1">
|
||||
<p className="font-semibold text-slate-700">Clickthrough Rate (CTR)</p>
|
||||
<p className="font-semibold text-slate-700">Click-through rate (CTR)</p>
|
||||
<div>
|
||||
<p className="rounded-lg bg-slate-100 px-2 text-slate-700">
|
||||
{convertFloatToNDecimal(questionSummary.ctr.percentage, 1)}%
|
||||
@@ -23,7 +23,7 @@ export const CTASummary = ({ questionSummary }: CTASummaryProps) => {
|
||||
</div>
|
||||
</div>
|
||||
<p className="flex w-32 items-end justify-end text-slate-600">
|
||||
{questionSummary.ctr.count} {questionSummary.ctr.count === 1 ? "response" : "responses"}
|
||||
{questionSummary.ctr.count} {questionSummary.ctr.count === 1 ? "click" : "clicks"}
|
||||
</p>
|
||||
</div>
|
||||
<ProgressBar barColor="bg-brand" progress={questionSummary.ctr.percentage / 100} />
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
|
||||
import { getPersonIdentifier } from "@formbricks/lib/person/util";
|
||||
import { TSurveyQuestionSummaryMultipleChoice } from "@formbricks/types/surveys";
|
||||
import { PersonAvatar } from "@formbricks/ui/Avatars";
|
||||
import { Button } from "@formbricks/ui/Button";
|
||||
import { ProgressBar } from "@formbricks/ui/ProgressBar";
|
||||
|
||||
import { convertFloatToNDecimal } from "../lib/util";
|
||||
@@ -19,15 +21,28 @@ export const MultipleChoiceSummary = ({
|
||||
environmentId,
|
||||
surveyType,
|
||||
}: MultipleChoiceSummaryProps) => {
|
||||
const [visibleOtherResponses, setVisibleOtherResponses] = useState(10);
|
||||
|
||||
// sort by count and transform to array
|
||||
const results = Object.values(questionSummary.choices).sort((a, b) => {
|
||||
if (a.others) return 1; // Always put a after b if a has 'others'
|
||||
if (b.others) return -1; // Always put b after a if b has 'others'
|
||||
|
||||
// Sort by count
|
||||
return b.count - a.count;
|
||||
return b.count - a.count; // Sort by count
|
||||
});
|
||||
|
||||
const handleLoadMore = () => {
|
||||
const lastChoice = results[results.length - 1];
|
||||
const hasOthers = lastChoice.others && lastChoice.others.length > 0;
|
||||
|
||||
if (!hasOthers) return; // If there are no 'others' to show, don't increase the visible options
|
||||
|
||||
// Increase the number of visible responses by 10, not exceeding the total number of responses
|
||||
setVisibleOtherResponses((prevVisibleOptions) =>
|
||||
Math.min(prevVisibleOptions + 10, lastChoice.others?.length || 0)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm">
|
||||
<QuestionSummaryHeader questionSummary={questionSummary} />
|
||||
@@ -58,6 +73,7 @@ export const MultipleChoiceSummary = ({
|
||||
</div>
|
||||
{result.others
|
||||
.filter((otherValue) => otherValue.value !== "")
|
||||
.slice(0, visibleOtherResponses)
|
||||
.map((otherValue, idx) => (
|
||||
<div key={idx}>
|
||||
{surveyType === "link" && (
|
||||
@@ -87,6 +103,13 @@ export const MultipleChoiceSummary = ({
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{visibleOtherResponses < result.others.length && (
|
||||
<div className="flex justify-center py-4">
|
||||
<Button onClick={handleLoadMore} variant="secondary" size="sm">
|
||||
Load more
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -124,7 +124,7 @@ test.describe("JS Package Test", async () => {
|
||||
await page.waitForTimeout(1000);
|
||||
await expect(page.getByRole("button", { name: "Responses50%" })).toBeVisible();
|
||||
await expect(page.getByText("1 Responses", { exact: true }).first()).toBeVisible();
|
||||
await expect(page.getByText("Clickthrough Rate (CTR)100%")).toBeVisible();
|
||||
await expect(page.getByText("Click-through rate (CTR)100%")).toBeVisible();
|
||||
await expect(page.getByText("Somewhat disappointed100%")).toBeVisible();
|
||||
await expect(page.getByText("Founder100%")).toBeVisible();
|
||||
await expect(page.getByText("People who believe that PMF").first()).toBeVisible();
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
// get all the persons that have an attribute class with the name "userId"
|
||||
const personsWithUserIdAttribute = await tx.person.findMany({
|
||||
where: {
|
||||
attributes: {
|
||||
some: {
|
||||
attributeClass: {
|
||||
name: "userId",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
attributes: {
|
||||
include: { attributeClass: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (let person of personsWithUserIdAttribute) {
|
||||
// If the person already has a userId, skip it
|
||||
if (person.userId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const userIdAttributeValue = person.attributes.find((attribute) => {
|
||||
if (attribute.attributeClass.name === "userId") {
|
||||
return attribute;
|
||||
}
|
||||
});
|
||||
|
||||
if (!userIdAttributeValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await tx.person.update({
|
||||
where: {
|
||||
id: person.id,
|
||||
},
|
||||
data: {
|
||||
userId: userIdAttributeValue.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Delete all attributeClasses with the name "userId"
|
||||
await tx.attributeClass.deleteMany({
|
||||
where: {
|
||||
name: "userId",
|
||||
},
|
||||
});
|
||||
},
|
||||
{
|
||||
timeout: 60000 * 3, // 3 minutes
|
||||
}
|
||||
);
|
||||
}
|
||||
main()
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => await prisma.$disconnect());
|
||||
@@ -1,293 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter =
|
||||
(this && this.__awaiter) ||
|
||||
function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) {
|
||||
return value instanceof P
|
||||
? value
|
||||
: new P(function (resolve) {
|
||||
resolve(value);
|
||||
});
|
||||
}
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) {
|
||||
try {
|
||||
step(generator.next(value));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
function rejected(value) {
|
||||
try {
|
||||
step(generator["throw"](value));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
function step(result) {
|
||||
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
|
||||
}
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __generator =
|
||||
(this && this.__generator) ||
|
||||
function (thisArg, body) {
|
||||
var _ = {
|
||||
label: 0,
|
||||
sent: function () {
|
||||
if (t[0] & 1) throw t[1];
|
||||
return t[1];
|
||||
},
|
||||
trys: [],
|
||||
ops: [],
|
||||
},
|
||||
f,
|
||||
y,
|
||||
t,
|
||||
g;
|
||||
return (
|
||||
(g = { next: verb(0), throw: verb(1), return: verb(2) }),
|
||||
typeof Symbol === "function" &&
|
||||
(g[Symbol.iterator] = function () {
|
||||
return this;
|
||||
}),
|
||||
g
|
||||
);
|
||||
function verb(n) {
|
||||
return function (v) {
|
||||
return step([n, v]);
|
||||
};
|
||||
}
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while ((g && ((g = 0), op[0] && (_ = 0)), _))
|
||||
try {
|
||||
if (
|
||||
((f = 1),
|
||||
y &&
|
||||
(t =
|
||||
op[0] & 2
|
||||
? y["return"]
|
||||
: op[0]
|
||||
? y["throw"] || ((t = y["return"]) && t.call(y), 0)
|
||||
: y.next) &&
|
||||
!(t = t.call(y, op[1])).done)
|
||||
)
|
||||
return t;
|
||||
if (((y = 0), t)) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0:
|
||||
case 1:
|
||||
t = op;
|
||||
break;
|
||||
case 4:
|
||||
_.label++;
|
||||
return { value: op[1], done: false };
|
||||
case 5:
|
||||
_.label++;
|
||||
y = op[1];
|
||||
op = [0];
|
||||
continue;
|
||||
case 7:
|
||||
op = _.ops.pop();
|
||||
_.trys.pop();
|
||||
continue;
|
||||
default:
|
||||
if (!((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && (op[0] === 6 || op[0] === 2)) {
|
||||
_ = 0;
|
||||
continue;
|
||||
}
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
|
||||
_.label = op[1];
|
||||
break;
|
||||
}
|
||||
if (op[0] === 6 && _.label < t[1]) {
|
||||
_.label = t[1];
|
||||
t = op;
|
||||
break;
|
||||
}
|
||||
if (t && _.label < t[2]) {
|
||||
_.label = t[2];
|
||||
_.ops.push(op);
|
||||
break;
|
||||
}
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop();
|
||||
continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) {
|
||||
op = [6, e];
|
||||
y = 0;
|
||||
} finally {
|
||||
f = t = 0;
|
||||
}
|
||||
if (op[0] & 5) throw op[1];
|
||||
return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var cuid2_1 = require("@paralleldrive/cuid2");
|
||||
var client_1 = require("@prisma/client");
|
||||
var prisma = new client_1.PrismaClient();
|
||||
function main() {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var _this = this;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
return [
|
||||
4 /*yield*/,
|
||||
prisma.$transaction(function (tx) {
|
||||
return __awaiter(_this, void 0, void 0, function () {
|
||||
var allSurveysWithAttributeFilters;
|
||||
var _this = this;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
return [
|
||||
4 /*yield*/,
|
||||
prisma.survey.findMany({
|
||||
where: {
|
||||
attributeFilters: {
|
||||
some: {},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
attributeFilters: { include: { attributeClass: true } },
|
||||
},
|
||||
}),
|
||||
];
|
||||
case 1:
|
||||
allSurveysWithAttributeFilters = _a.sent();
|
||||
if (
|
||||
!(allSurveysWithAttributeFilters === null || allSurveysWithAttributeFilters === void 0
|
||||
? void 0
|
||||
: allSurveysWithAttributeFilters.length)
|
||||
) {
|
||||
// stop the migration if there are no surveys with attribute filters
|
||||
return [2 /*return*/];
|
||||
}
|
||||
allSurveysWithAttributeFilters.forEach(function (survey) {
|
||||
return __awaiter(_this, void 0, void 0, function () {
|
||||
var attributeFilters, filters;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
attributeFilters = survey.attributeFilters;
|
||||
// if there are no attribute filters, we can skip this survey
|
||||
if (
|
||||
!(attributeFilters === null || attributeFilters === void 0
|
||||
? void 0
|
||||
: attributeFilters.length)
|
||||
) {
|
||||
return [2 /*return*/];
|
||||
}
|
||||
filters = attributeFilters.map(function (filter, idx) {
|
||||
var attributeClass = filter.attributeClass;
|
||||
var resource;
|
||||
// if the attribute class is userId, we need to create a user segment with the person filter
|
||||
if (
|
||||
attributeClass.name === "userId" &&
|
||||
attributeClass.type === "automatic"
|
||||
) {
|
||||
resource = {
|
||||
id: (0, cuid2_1.createId)(),
|
||||
root: {
|
||||
type: "person",
|
||||
personIdentifier: "userId",
|
||||
},
|
||||
qualifier: {
|
||||
operator: filter.condition,
|
||||
},
|
||||
value: filter.value,
|
||||
};
|
||||
} else {
|
||||
resource = {
|
||||
id: (0, cuid2_1.createId)(),
|
||||
root: {
|
||||
type: "attribute",
|
||||
attributeClassName: attributeClass.name,
|
||||
},
|
||||
qualifier: {
|
||||
operator: filter.condition,
|
||||
},
|
||||
value: filter.value,
|
||||
};
|
||||
}
|
||||
var attributeSegment = {
|
||||
id: filter.id,
|
||||
connector: idx === 0 ? null : "and",
|
||||
resource: resource,
|
||||
};
|
||||
return attributeSegment;
|
||||
});
|
||||
return [
|
||||
4 /*yield*/,
|
||||
tx.segment.create({
|
||||
data: {
|
||||
title: "".concat(survey.id),
|
||||
description: "",
|
||||
isPrivate: true,
|
||||
filters: filters,
|
||||
surveys: {
|
||||
connect: {
|
||||
id: survey.id,
|
||||
},
|
||||
},
|
||||
environment: {
|
||||
connect: {
|
||||
id: survey.environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
case 1:
|
||||
_a.sent();
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
// delete all attribute filters
|
||||
return [4 /*yield*/, tx.surveyAttributeFilter.deleteMany({})];
|
||||
case 2:
|
||||
// delete all attribute filters
|
||||
_a.sent();
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
}),
|
||||
];
|
||||
case 1:
|
||||
_a.sent();
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
main()
|
||||
.catch(function (e) {
|
||||
return __awaiter(void 0, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
return [2 /*return*/];
|
||||
});
|
||||
});
|
||||
})
|
||||
.finally(function () {
|
||||
return __awaiter(void 0, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
return [4 /*yield*/, prisma.$disconnect()];
|
||||
case 1:
|
||||
return [2 /*return*/, _a.sent()];
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -23,12 +23,13 @@
|
||||
"lint": "eslint ./src --fix",
|
||||
"post-install": "pnpm generate",
|
||||
"predev": "pnpm generate",
|
||||
"data-migration:v1.6": "ts-node ./migrations/20240207041922_advanced_targeting/data-migration.ts",
|
||||
"data-migration:styling": "ts-node ./migrations/20240320090315_add_form_styling/data-migration.ts",
|
||||
"data-migration:v1.6": "ts-node ./data-migrations/20240207041922_advanced_targeting/data-migration.ts",
|
||||
"data-migration:styling": "ts-node ./data-migrations/20240320090315_add_form_styling/data-migration.ts",
|
||||
"data-migration:v1.7": "pnpm data-migration:mls && pnpm data-migration:styling",
|
||||
"data-migration:mls": "ts-node ./migrations/20240318050527_add_languages_and_survey_languages/data-migration.ts",
|
||||
"data-migration:mls-fix": "ts-node ./migrations/20240318050527_add_languages_and_survey_languages/data-migration-fix.ts",
|
||||
"data-migration:mls-range-fix": "ts-node ./migrations/20240318050527_add_languages_and_survey_languages/data-migration-range-fix.ts"
|
||||
"data-migration:mls": "ts-node ./data-migrations/20240318050527_add_languages_and_survey_languages/data-migration.ts",
|
||||
"data-migration:mls-fix": "ts-node ./data-migrations/20240318050527_add_languages_and_survey_languages/data-migration-fix.ts",
|
||||
"data-migration:mls-range-fix": "ts-node ./data-migrations/20240318050527_add_languages_and_survey_languages/data-migration-range-fix.ts",
|
||||
"data-migration:userId": "ts-node ./data-migrations/20240408123456_userid_migration/data-migration.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.12.1",
|
||||
|
||||
@@ -35,7 +35,7 @@ export const triggerSurvey = async (survey: TSurvey, action?: string): Promise<v
|
||||
if (survey.displayPercentage) {
|
||||
const shouldDisplaySurvey = shouldDisplayBasedOnPercentage(survey.displayPercentage);
|
||||
if (!shouldDisplaySurvey) {
|
||||
logger.debug("Survey display skipped based on displayPercentage.");
|
||||
logger.debug(`Survey display of "${survey.name}" skipped based on displayPercentage.`);
|
||||
return; // skip displaying the survey
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ const renderWidget = async (survey: TSurvey, action?: string) => {
|
||||
setIsSurveyRunning(true);
|
||||
|
||||
if (survey.delay) {
|
||||
logger.debug(`Delaying survey by ${survey.delay} seconds.`);
|
||||
logger.debug(`Delaying survey "${survey.name}" by ${survey.delay} seconds.`);
|
||||
}
|
||||
|
||||
const product = config.get().state.product;
|
||||
@@ -63,7 +63,7 @@ const renderWidget = async (survey: TSurvey, action?: string) => {
|
||||
const displayLanguage = getLanguageCode(survey, attributes);
|
||||
//if survey is not available in selected language, survey wont be shown
|
||||
if (!displayLanguage) {
|
||||
logger.debug("Survey not available in specified language.");
|
||||
logger.debug(`Survey "${survey.name}" is not available in specified language.`);
|
||||
setIsSurveyRunning(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -325,50 +325,7 @@ export const getPersonByUserId = async (environmentId: string, userId: string):
|
||||
return transformPrismaPerson(personWithUserId);
|
||||
}
|
||||
|
||||
// Check if a person with the userId attribute exists
|
||||
let personWithUserIdAttribute = await prisma.person.findFirst({
|
||||
where: {
|
||||
environmentId,
|
||||
attributes: {
|
||||
some: {
|
||||
attributeClass: {
|
||||
name: "userId",
|
||||
},
|
||||
value: userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: selectPerson,
|
||||
});
|
||||
|
||||
const userIdAttributeClassId = personWithUserIdAttribute?.attributes.find(
|
||||
(attr) => attr.attributeClass.name === "userId" && attr.value === userId
|
||||
)?.attributeClass.id;
|
||||
|
||||
if (!personWithUserIdAttribute) {
|
||||
return null;
|
||||
}
|
||||
|
||||
personWithUserIdAttribute = await prisma.person.update({
|
||||
where: {
|
||||
id: personWithUserIdAttribute.id,
|
||||
},
|
||||
data: {
|
||||
userId,
|
||||
attributes: {
|
||||
deleteMany: { attributeClassId: userIdAttributeClassId },
|
||||
},
|
||||
},
|
||||
select: selectPerson,
|
||||
});
|
||||
|
||||
personCache.revalidate({
|
||||
id: personWithUserIdAttribute.id,
|
||||
environmentId,
|
||||
userId,
|
||||
});
|
||||
|
||||
return transformPrismaPerson(personWithUserIdAttribute);
|
||||
return null;
|
||||
},
|
||||
[`getPersonByUserId-${environmentId}-${userId}`],
|
||||
{
|
||||
@@ -380,7 +337,7 @@ export const getPersonByUserId = async (environmentId: string, userId: string):
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated This function is deprecated and only used in legacy endpoints. Use updatePerson instead.
|
||||
* @deprecated This function is deprecated and only used in legacy endpoints. Use `updatePerson` instead.
|
||||
*/
|
||||
export const updatePersonAttribute = async (
|
||||
personId: string,
|
||||
|
||||
@@ -14,17 +14,6 @@ export const truncate = (str: string, length: number) => {
|
||||
return str;
|
||||
};
|
||||
|
||||
// write a function that takes a string and truncates the middle of it so that the beginning and ending are always visible
|
||||
export const truncateMiddle = (str: string, length: number) => {
|
||||
if (!str) return "";
|
||||
if (str.length > length) {
|
||||
const start = str.substring(0, length / 2);
|
||||
const end = str.substring(str.length - length / 2, str.length);
|
||||
return start + " ... " + end;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
// write a function that takes a string and removes all characters that could cause issues with the url and truncates it to the specified length
|
||||
export const sanitizeString = (str: string, delimiter: string = "_", length: number = 255) => {
|
||||
return str.replace(/[^0-9a-zA-Z\-._]+/g, delimiter).substring(0, length);
|
||||
|
||||
Reference in New Issue
Block a user