mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-17 03:21:51 -05:00
fix: data migration and cleanup for userId attribute (#2400)
Co-authored-by: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com> Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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