@crowdin/app-project-module 0.91.1 → 0.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/modules/integration/handlers/integration-data.js +10 -7
- package/out/modules/integration/handlers/invite-users.js +8 -257
- package/out/modules/integration/handlers/main.js +22 -37
- package/out/modules/integration/handlers/settings-save.js +15 -0
- package/out/modules/integration/handlers/users.d.ts +3 -6
- package/out/modules/integration/handlers/users.js +8 -71
- package/out/modules/integration/types.d.ts +21 -0
- package/out/modules/integration/util/defaults.js +11 -0
- package/out/modules/integration/util/snapshot.js +5 -1
- package/out/modules/manifest.js +1 -3
- package/out/types.d.ts +1 -1
- package/out/views/main.handlebars +176 -59
- package/out/views/partials/head.handlebars +18 -14
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@ const types_1 = require("../types");
|
|
|
16
16
|
const storage_1 = require("../../../storage");
|
|
17
17
|
function handle(integration) {
|
|
18
18
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
19
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
19
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
20
20
|
const { parent_id: parentId, search, page } = req.query;
|
|
21
21
|
req.logInfo('Received request to get integration data');
|
|
22
22
|
let message;
|
|
@@ -34,15 +34,18 @@ function handle(integration) {
|
|
|
34
34
|
else {
|
|
35
35
|
files = result || [];
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
if ((appSettings === null || appSettings === void 0 ? void 0 : appSettings.skipIntegrationNodesToggle) === true ||
|
|
38
|
+
((appSettings === null || appSettings === void 0 ? void 0 : appSettings.skipIntegrationNodesToggle) === undefined && ((_a = integration.skipIntegrationNodesToggle) === null || _a === void 0 ? void 0 : _a.value))) {
|
|
39
|
+
files = (0, files_1.skipFilesByRegex)(files, integration.skipIntegrationNodes);
|
|
40
|
+
}
|
|
38
41
|
if (integration.filterByPathIntegrationFiles) {
|
|
39
|
-
const includePatterns = (
|
|
40
|
-
const excludePatterns = (
|
|
42
|
+
const includePatterns = (_b = appSettings === null || appSettings === void 0 ? void 0 : appSettings.includeByFilePath) === null || _b === void 0 ? void 0 : _b.split('\n').filter(Boolean);
|
|
43
|
+
const excludePatterns = (_c = appSettings === null || appSettings === void 0 ? void 0 : appSettings.excludeByFilePath) === null || _c === void 0 ? void 0 : _c.split('\n').filter(Boolean);
|
|
41
44
|
files = (0, files_1.filterFilesByPath)(files, includePatterns, excludePatterns);
|
|
42
45
|
}
|
|
43
|
-
if (((
|
|
44
|
-
((
|
|
45
|
-
((
|
|
46
|
+
if (((_e = (_d = integration.filtering) === null || _d === void 0 ? void 0 : _d.integrationFileStatus) === null || _e === void 0 ? void 0 : _e.isNew) ||
|
|
47
|
+
((_g = (_f = integration.filtering) === null || _f === void 0 ? void 0 : _f.integrationFileStatus) === null || _g === void 0 ? void 0 : _g.isUpdated) ||
|
|
48
|
+
((_j = (_h = integration.filtering) === null || _h === void 0 ? void 0 : _h.integrationFileStatus) === null || _j === void 0 ? void 0 : _j.notSynced)) {
|
|
46
49
|
const syncedData = yield (0, storage_1.getStorage)().getSyncedData(clientId, crowdinId, types_1.Provider.INTEGRATION);
|
|
47
50
|
const syncedFiles = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.files) ? JSON.parse(syncedData.files) : [];
|
|
48
51
|
const lastSyncTimestamp = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.updatedAt) ? Number(syncedData.updatedAt) : null;
|
|
@@ -10,270 +10,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const util_1 = require("../../../util");
|
|
13
|
-
const types_1 = require("../../../types");
|
|
14
13
|
const storage_1 = require("../../../storage");
|
|
15
|
-
const users_1 = require("./users");
|
|
16
|
-
const CONSTANTS = {
|
|
17
|
-
MAX_EMAIL_LENGTH: 76,
|
|
18
|
-
MANAGER_ROLES: ['owner', 'manager'],
|
|
19
|
-
PROJECT_INTEGRATIONS_MODULE_TYPE: 'project-integrations',
|
|
20
|
-
};
|
|
21
14
|
function handle() {
|
|
22
15
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
26
|
-
return res.status(400).send('Invalid request');
|
|
27
|
-
}
|
|
28
|
-
const client = req.crowdinApiClient;
|
|
29
|
-
const projectId = req.crowdinContext.jwtPayload.context.project_id;
|
|
30
|
-
const userId = req.crowdinContext.jwtPayload.context.user_id;
|
|
31
|
-
const { projectMembers, organizationMembers } = yield (0, users_1.getUsers)({ client, projectId });
|
|
32
|
-
const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(req.crowdinContext.clientId);
|
|
33
|
-
let applicationInstallation = null;
|
|
34
|
-
try {
|
|
35
|
-
applicationInstallation = yield client.applicationsApi.getApplicationInstallation(req.crowdinContext.appIdentifier);
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
if (error.code !== 403) {
|
|
39
|
-
console.error('Failed to get application installation', error);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const isAdmin = organizationMembers.some((member) => member.id === +userId && member.isAdmin) ||
|
|
43
|
-
projectMembers.some((member) => member.id === +userId && 'role' in member && member.role === 'owner');
|
|
44
|
-
const hasManagerAccess = isAdmin ||
|
|
45
|
-
projectMembers.some((member) => member.id === +userId &&
|
|
46
|
-
('role' in member ? CONSTANTS.MANAGER_ROLES.includes(member.role) : member.isManager));
|
|
47
|
-
if (!hasManagerAccess) {
|
|
48
|
-
return res.status(403).send({ error: 'Access denied' });
|
|
49
|
-
}
|
|
50
|
-
const inviteRestricted = !isAdmin &&
|
|
51
|
-
'invite_restrict_enabled' in req.crowdinContext.jwtPayload.context &&
|
|
52
|
-
!!req.crowdinContext.jwtPayload.context.invite_restrict_enabled;
|
|
53
|
-
const usersWhoWillBeInvitedToOrganization = filterOrganizationUsers(usersToInvite);
|
|
54
|
-
const usersWhoWillBeInvitedToProject = filterProjectUsers({
|
|
55
|
-
inviteRestricted,
|
|
56
|
-
usersToInvite,
|
|
57
|
-
projectMembers,
|
|
58
|
-
organizationMembers,
|
|
59
|
-
});
|
|
60
|
-
const usersWhoNotIssetInApplicationInstallation = filterNotIssetApplicationUsers({
|
|
61
|
-
inviteRestricted,
|
|
62
|
-
usersToInvite,
|
|
63
|
-
applicationInstallation,
|
|
64
|
-
projectMembers,
|
|
65
|
-
organizationMembers,
|
|
66
|
-
});
|
|
67
|
-
const usersWhoNotIssetInIntegration = filterNotIssetIntegrationUsers({
|
|
68
|
-
inviteRestricted,
|
|
69
|
-
usersToInvite,
|
|
70
|
-
integrationCredentials,
|
|
71
|
-
projectMembers,
|
|
72
|
-
organizationMembers,
|
|
73
|
-
});
|
|
74
|
-
if (onlyCheck) {
|
|
16
|
+
const clientId = req.crowdinContext.clientId;
|
|
17
|
+
const users = req.body.users;
|
|
18
|
+
if (!users) {
|
|
75
19
|
return res.send({
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
inviteRestricted,
|
|
79
|
-
editApplicationAvailable: applicationInstallation !== null,
|
|
80
|
-
usersWhoWillBeInvitedToOrganization,
|
|
81
|
-
usersWhoWillBeInvitedToProject,
|
|
82
|
-
usersWhoNotIssetInApplicationInstallation,
|
|
83
|
-
usersWhoNotIssetInIntegration,
|
|
84
|
-
},
|
|
20
|
+
success: false,
|
|
21
|
+
message: 'No users provided',
|
|
85
22
|
});
|
|
86
23
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
isAdmin,
|
|
91
|
-
inviteRestricted,
|
|
92
|
-
usersToInvite,
|
|
93
|
-
applicationInstallation,
|
|
94
|
-
usersWhoWillBeInvitedToOrganization,
|
|
95
|
-
usersWhoWillBeInvitedToProject,
|
|
96
|
-
usersWhoNotIssetInApplicationInstallation,
|
|
24
|
+
yield (0, storage_1.getStorage)().updateIntegrationManagers(clientId, JSON.stringify(users.map((user) => `${user.id}`)));
|
|
25
|
+
return res.send({
|
|
26
|
+
success: true,
|
|
97
27
|
});
|
|
98
|
-
return res.send(response);
|
|
99
28
|
}));
|
|
100
29
|
}
|
|
101
30
|
exports.default = handle;
|
|
102
|
-
function filterOrganizationUsers(usersToInvite) {
|
|
103
|
-
return usersToInvite.filter((identifier) => (0, util_1.validateEmail)(identifier)).map((name) => ({ name: `${name}` }));
|
|
104
|
-
}
|
|
105
|
-
function filterProjectUsers({ inviteRestricted, usersToInvite, projectMembers, organizationMembers, }) {
|
|
106
|
-
return usersToInvite
|
|
107
|
-
.map((identifier) => {
|
|
108
|
-
if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
|
|
109
|
-
return { name: `${identifier}` };
|
|
110
|
-
}
|
|
111
|
-
const user = projectMembers.find((member) => member.id === +identifier);
|
|
112
|
-
if (!user) {
|
|
113
|
-
const organizationUser = organizationMembers.find((member) => member.id === +identifier);
|
|
114
|
-
return organizationUser && !organizationUser.isAdmin
|
|
115
|
-
? { id: organizationUser.id, name: (0, users_1.getUserFullName)(organizationUser) }
|
|
116
|
-
: null;
|
|
117
|
-
}
|
|
118
|
-
return ('role' in user ? !CONSTANTS.MANAGER_ROLES.includes(user.role) : !user.isManager)
|
|
119
|
-
? { id: user.id, name: (0, users_1.getUserFullName)(user) }
|
|
120
|
-
: null;
|
|
121
|
-
})
|
|
122
|
-
.filter(Boolean);
|
|
123
|
-
}
|
|
124
|
-
function filterNotIssetApplicationUsers({ inviteRestricted, usersToInvite, applicationInstallation, projectMembers, organizationMembers, }) {
|
|
125
|
-
let userIdentifiers = [];
|
|
126
|
-
if (!applicationInstallation) {
|
|
127
|
-
return [];
|
|
128
|
-
}
|
|
129
|
-
const projectIntegrationModules = applicationInstallation.data.modules.filter((module) => module.type === CONSTANTS.PROJECT_INTEGRATIONS_MODULE_TYPE);
|
|
130
|
-
if (!projectIntegrationModules.length) {
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
if (projectIntegrationModules.some((module) => module.permissions.user.value === types_1.UserPermissions.ALL_MEMBERS)) {
|
|
134
|
-
return [];
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
userIdentifiers = usersToInvite.filter((userId) => projectIntegrationModules.every((module) => !module.permissions.user.ids.includes(+userId)));
|
|
138
|
-
}
|
|
139
|
-
return userIdentifiers
|
|
140
|
-
.map((identifier) => {
|
|
141
|
-
if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
|
|
142
|
-
return { name: `${identifier}` };
|
|
143
|
-
}
|
|
144
|
-
let user;
|
|
145
|
-
user = projectMembers.find((member) => member.id === +identifier);
|
|
146
|
-
if (!user) {
|
|
147
|
-
user = organizationMembers.find((member) => member.id === +identifier);
|
|
148
|
-
}
|
|
149
|
-
return user ? { id: user.id, name: (0, users_1.getUserFullName)(user) } : null;
|
|
150
|
-
})
|
|
151
|
-
.filter(Boolean);
|
|
152
|
-
}
|
|
153
|
-
function filterNotIssetIntegrationUsers({ inviteRestricted, usersToInvite, integrationCredentials, projectMembers, organizationMembers, }) {
|
|
154
|
-
let integrationManagers = [];
|
|
155
|
-
if (integrationCredentials === null || integrationCredentials === void 0 ? void 0 : integrationCredentials.managers) {
|
|
156
|
-
integrationManagers = JSON.parse(integrationCredentials.managers);
|
|
157
|
-
}
|
|
158
|
-
return usersToInvite
|
|
159
|
-
.map((identifier) => {
|
|
160
|
-
if (integrationManagers.includes(`${identifier}`)) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
|
|
164
|
-
return { name: `${identifier}` };
|
|
165
|
-
}
|
|
166
|
-
let user;
|
|
167
|
-
user = projectMembers.find((member) => member.id === +identifier);
|
|
168
|
-
if (!user) {
|
|
169
|
-
user = organizationMembers.find((member) => member.id === +identifier);
|
|
170
|
-
}
|
|
171
|
-
return user ? { id: user.id, name: (0, users_1.getUserFullName)(user) } : null;
|
|
172
|
-
})
|
|
173
|
-
.filter(Boolean);
|
|
174
|
-
}
|
|
175
|
-
function inviteUsers({ req, projectId, isAdmin, inviteRestricted, usersToInvite, applicationInstallation, usersWhoWillBeInvitedToOrganization, usersWhoWillBeInvitedToProject, usersWhoNotIssetInApplicationInstallation, }) {
|
|
176
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
177
|
-
const client = req.crowdinApiClient;
|
|
178
|
-
if (inviteRestricted) {
|
|
179
|
-
usersWhoWillBeInvitedToOrganization = [];
|
|
180
|
-
}
|
|
181
|
-
const alreadyAddedUserIds = yield inviteUsersToProject({
|
|
182
|
-
client,
|
|
183
|
-
projectId,
|
|
184
|
-
usersWhoWillBeInvitedToOrganization,
|
|
185
|
-
usersWhoWillBeInvitedToProject,
|
|
186
|
-
});
|
|
187
|
-
if (isAdmin) {
|
|
188
|
-
yield addUsersToApplicationInstallation({
|
|
189
|
-
client,
|
|
190
|
-
ownerId: req.integrationCredentials.ownerId,
|
|
191
|
-
applicationInstallation,
|
|
192
|
-
alreadyAddedUserIds,
|
|
193
|
-
usersWhoNotIssetInApplicationInstallation,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
yield addUsersToIntegration({
|
|
197
|
-
clientId: req.crowdinContext.clientId,
|
|
198
|
-
alreadyAddedUserIds,
|
|
199
|
-
usersToInvite,
|
|
200
|
-
});
|
|
201
|
-
return { success: true };
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
function inviteUsersToProject({ client, projectId, usersWhoWillBeInvitedToOrganization, usersWhoWillBeInvitedToProject, }) {
|
|
205
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
206
|
-
let addedIds = [];
|
|
207
|
-
const emailInvites = usersWhoWillBeInvitedToOrganization.map((user) => user.name);
|
|
208
|
-
const userIdInvites = usersWhoWillBeInvitedToProject.map((user) => user.id).filter(Boolean);
|
|
209
|
-
try {
|
|
210
|
-
if (emailInvites.length) {
|
|
211
|
-
const inviteResponse = (yield client.usersApi.addProjectMember(projectId, {
|
|
212
|
-
managerAccess: true,
|
|
213
|
-
emails: emailInvites,
|
|
214
|
-
})); // TODO: fix typings in the @crowdin/crowdin-api-client
|
|
215
|
-
addedIds = inviteResponse.data.added.map((member) => member.id);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
catch (error) {
|
|
219
|
-
console.error('Failed to invite users', error);
|
|
220
|
-
}
|
|
221
|
-
try {
|
|
222
|
-
if (userIdInvites.length) {
|
|
223
|
-
yield client.usersApi.addProjectMember(projectId, {
|
|
224
|
-
managerAccess: true,
|
|
225
|
-
userIds: userIdInvites,
|
|
226
|
-
}); // TODO: fix typings in the @crowdin/crowdin-api-client
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
console.error('Failed to grant project manager access', error);
|
|
231
|
-
}
|
|
232
|
-
return addedIds;
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
function addUsersToApplicationInstallation({ client, ownerId, applicationInstallation, alreadyAddedUserIds, usersWhoNotIssetInApplicationInstallation, }) {
|
|
236
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
237
|
-
if (!applicationInstallation || !usersWhoNotIssetInApplicationInstallation.length) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
const applicationInvites = [
|
|
241
|
-
ownerId,
|
|
242
|
-
...alreadyAddedUserIds,
|
|
243
|
-
...usersWhoNotIssetInApplicationInstallation.map((user) => user.id).filter(Boolean),
|
|
244
|
-
];
|
|
245
|
-
const permissions = applicationInstallation.data.modules.filter((module) => module.type === CONSTANTS.PROJECT_INTEGRATIONS_MODULE_TYPE);
|
|
246
|
-
if (!permissions.length) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
try {
|
|
250
|
-
for (const module of permissions) {
|
|
251
|
-
if (module.permissions.user.value === types_1.UserPermissions.ALL_MEMBERS) {
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
const userIds = [...new Set([...module.permissions.user.ids, ...applicationInvites])];
|
|
255
|
-
client.applicationsApi.editApplicationInstallation(applicationInstallation.data.identifier, [
|
|
256
|
-
{
|
|
257
|
-
op: 'replace',
|
|
258
|
-
path: `/modules/${module.key}/permissions`,
|
|
259
|
-
value: {
|
|
260
|
-
user: {
|
|
261
|
-
value: types_1.UserPermissions.RESTRICTED,
|
|
262
|
-
ids: userIds,
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
]);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
catch (error) {
|
|
270
|
-
console.error('Failed to update application permissions', error);
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
function addUsersToIntegration({ clientId, alreadyAddedUserIds, usersToInvite, }) {
|
|
275
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
276
|
-
const integrationInvites = [...alreadyAddedUserIds, ...usersToInvite.filter((identifier) => +identifier)];
|
|
277
|
-
yield (0, storage_1.getStorage)().updateIntegrationManagers(clientId, JSON.stringify(integrationInvites.map((id) => `${id}`)));
|
|
278
|
-
});
|
|
279
|
-
}
|
|
@@ -1,27 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
2
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
3
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
4
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -36,7 +13,7 @@ const util_1 = require("../../../util");
|
|
|
36
13
|
const logger_1 = require("../../../util/logger");
|
|
37
14
|
const subscription_1 = require("../../../util/subscription");
|
|
38
15
|
const defaults_1 = require("../util/defaults");
|
|
39
|
-
const
|
|
16
|
+
const users_1 = require("./users");
|
|
40
17
|
function handle(config, integration) {
|
|
41
18
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
42
19
|
var _a, _b, _c, _d;
|
|
@@ -64,21 +41,29 @@ function handle(config, integration) {
|
|
|
64
41
|
options.oauthMode = integration.oauthLogin.mode;
|
|
65
42
|
}
|
|
66
43
|
}
|
|
67
|
-
else
|
|
68
|
-
|
|
44
|
+
else {
|
|
45
|
+
if (integration.getConfiguration) {
|
|
46
|
+
options.config = JSON.stringify(req.integrationSettings || {});
|
|
47
|
+
options.reloadOnConfigSave = !!integration.reloadOnConfigSave;
|
|
48
|
+
options.integrationPagination = integration.integrationPagination;
|
|
49
|
+
if ((_b = req.query) === null || _b === void 0 ? void 0 : _b.parentUrl) {
|
|
50
|
+
const parentUrl = new URL(req.query.parentUrl);
|
|
51
|
+
parentUrl.searchParams.set('zen-mode', 'true');
|
|
52
|
+
options.zenModeUrl = parentUrl.toString();
|
|
53
|
+
}
|
|
54
|
+
const configurationFields = yield integration.getConfiguration(req.crowdinContext.jwtPayload.context.project_id, req.crowdinApiClient, req.integrationCredentials);
|
|
55
|
+
options.configurationFields = (0, defaults_1.groupFieldsByCategory)(configurationFields);
|
|
56
|
+
logger(`Adding configuration fields ${JSON.stringify(options.configurationFields, null, 2)}`);
|
|
57
|
+
}
|
|
69
58
|
options.hasOrganization = !!req.crowdinContext.jwtPayload.domain;
|
|
59
|
+
const userId = req.crowdinContext.jwtPayload.context.user_id;
|
|
70
60
|
options.isOwner = req.integrationCredentials.ownerId === userId;
|
|
71
|
-
options.
|
|
72
|
-
options.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
options.zenModeUrl = parentUrl.toString();
|
|
78
|
-
}
|
|
79
|
-
const configurationFields = yield integration.getConfiguration(req.crowdinContext.jwtPayload.context.project_id, req.crowdinApiClient, req.integrationCredentials);
|
|
80
|
-
options.configurationFields = (0, defaults_1.groupFieldsByCategory)(configurationFields);
|
|
81
|
-
logger(`Adding configuration fields ${JSON.stringify(options.configurationFields, null, 2)}`);
|
|
61
|
+
options.userId = userId;
|
|
62
|
+
options.isManager = yield (0, users_1.isManager)({
|
|
63
|
+
client: req.crowdinApiClient,
|
|
64
|
+
projectId: req.crowdinContext.jwtPayload.context.project_id,
|
|
65
|
+
memberId: userId,
|
|
66
|
+
});
|
|
82
67
|
}
|
|
83
68
|
options.infoModal = integration.infoModal;
|
|
84
69
|
options.syncNewElements = integration.syncNewElements;
|
|
@@ -25,6 +25,21 @@ function handle(config, integration) {
|
|
|
25
25
|
},
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
+
if (integration.validateSettings) {
|
|
29
|
+
const validationResult = yield integration.validateSettings({
|
|
30
|
+
client: req.crowdinApiClient,
|
|
31
|
+
credentials: req.integrationCredentials,
|
|
32
|
+
appSettings,
|
|
33
|
+
});
|
|
34
|
+
if (validationResult && Object.keys(validationResult).length > 0) {
|
|
35
|
+
return res.status(400).json({
|
|
36
|
+
error: {
|
|
37
|
+
type: 'validation_error',
|
|
38
|
+
details: validationResult,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
28
43
|
const clientId = req.crowdinContext.clientId;
|
|
29
44
|
req.logInfo(`Saving settings ${JSON.stringify(appSettings, null, 2)}`);
|
|
30
45
|
const integrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(clientId);
|
|
@@ -3,11 +3,8 @@ import Crowdin, { UsersModel } from '@crowdin/crowdin-api-client';
|
|
|
3
3
|
import { Response } from 'express';
|
|
4
4
|
export type ProjectMember = UsersModel.ProjectMember | UsersModel.EnterpriseProjectMember;
|
|
5
5
|
export default function handle(): (req: import("../../../types").CrowdinClientRequest | import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function isManager({ client, projectId, memberId, }: {
|
|
7
7
|
client: Crowdin;
|
|
8
8
|
projectId: number;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
organizationMembers: UsersModel.User[];
|
|
12
|
-
}>;
|
|
13
|
-
export declare function getUserFullName(user: UsersModel.User | ProjectMember): string;
|
|
9
|
+
memberId: number;
|
|
10
|
+
}): Promise<{}>;
|
|
@@ -1,27 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
2
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
3
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
4
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -32,32 +9,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
32
9
|
});
|
|
33
10
|
};
|
|
34
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
-
exports.
|
|
12
|
+
exports.isManager = void 0;
|
|
36
13
|
const util_1 = require("../../../util");
|
|
37
14
|
const storage_1 = require("../../../storage");
|
|
38
|
-
const
|
|
15
|
+
const MANAGER_ROLES = ['owner', 'manager'];
|
|
39
16
|
function handle() {
|
|
40
17
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
41
|
-
const client = req.crowdinApiClient;
|
|
42
|
-
const ownerId = req.integrationCredentials.ownerId;
|
|
43
|
-
const { projectId } = crowdinAppFunctions.parseCrowdinId(req.crowdinContext.clientId);
|
|
44
|
-
const { projectMembers, organizationMembers } = yield getUsers({ client, projectId });
|
|
45
|
-
const uniqueUsers = new Map();
|
|
46
|
-
[...projectMembers, ...organizationMembers].forEach((manager) => {
|
|
47
|
-
if (manager.id !== ownerId && !uniqueUsers.has(manager.id)) {
|
|
48
|
-
uniqueUsers.set(manager.id, manager);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
const sortedUsers = Array.from(uniqueUsers.values())
|
|
52
|
-
.sort((a, b) => {
|
|
53
|
-
const aValue = a.firstName || a.username || '';
|
|
54
|
-
const bValue = b.firstName || b.username || '';
|
|
55
|
-
return aValue.localeCompare(bValue);
|
|
56
|
-
})
|
|
57
|
-
.map((user) => ({
|
|
58
|
-
id: user.id.toString(),
|
|
59
|
-
name: getUserFullName(user),
|
|
60
|
-
}));
|
|
61
18
|
let integrationManagers = [];
|
|
62
19
|
const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(req.crowdinContext.clientId);
|
|
63
20
|
if (integrationCredentials === null || integrationCredentials === void 0 ? void 0 : integrationCredentials.managers) {
|
|
@@ -65,38 +22,18 @@ function handle() {
|
|
|
65
22
|
}
|
|
66
23
|
res.json({
|
|
67
24
|
data: {
|
|
68
|
-
users: sortedUsers,
|
|
69
25
|
managers: integrationManagers,
|
|
70
26
|
},
|
|
71
27
|
});
|
|
72
28
|
}));
|
|
73
29
|
}
|
|
74
30
|
exports.default = handle;
|
|
75
|
-
function
|
|
31
|
+
function isManager({ client, projectId, memberId, }) {
|
|
76
32
|
return __awaiter(this, void 0, void 0, function* () {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const projectMembers = (yield client.usersApi.withFetchAll().listProjectMembers(projectId)).data;
|
|
82
|
-
users.projectMembers = projectMembers.map((members) => members.data);
|
|
83
|
-
if (client.organization) {
|
|
84
|
-
try {
|
|
85
|
-
const admins = (yield client.usersApi.withFetchAll().listUsers()).data;
|
|
86
|
-
users.organizationMembers = admins.map((admin) => admin.data);
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
console.error('Failed to get organization users', error);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return users;
|
|
33
|
+
const member = (yield client.usersApi.getProjectMemberPermissions(projectId, memberId)).data;
|
|
34
|
+
const isAdmin = 'isAdmin' in member ? member.isAdmin : 'role' in member && member.role === 'owner';
|
|
35
|
+
const isManager = ('role' in member && MANAGER_ROLES.includes(member.role)) || ('isManager' in member && member.isManager);
|
|
36
|
+
return isAdmin || isManager;
|
|
93
37
|
});
|
|
94
38
|
}
|
|
95
|
-
exports.
|
|
96
|
-
function getUserFullName(user) {
|
|
97
|
-
const ownerFullName = 'fullName' in user ? user.fullName : ((user.firstName || '') + ' ' + (user.lastName || '')).trim();
|
|
98
|
-
return !!ownerFullName && (user === null || user === void 0 ? void 0 : user.username) !== ownerFullName
|
|
99
|
-
? `${ownerFullName} (${user === null || user === void 0 ? void 0 : user.username})`
|
|
100
|
-
: user === null || user === void 0 ? void 0 : user.username;
|
|
101
|
-
}
|
|
102
|
-
exports.getUserFullName = getUserFullName;
|
|
39
|
+
exports.isManager = isManager;
|
|
@@ -19,6 +19,16 @@ export interface IntegrationLogic extends ModuleKey {
|
|
|
19
19
|
* flag that defines if the app should have a dedicated root folder in Crowdin files, default 'false'
|
|
20
20
|
*/
|
|
21
21
|
withRootFolder?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Validate integration settings before saving
|
|
24
|
+
*/
|
|
25
|
+
validateSettings?: ({ client, credentials, appSettings, }: {
|
|
26
|
+
client: Crowdin;
|
|
27
|
+
credentials: any;
|
|
28
|
+
appSettings: AppSettings;
|
|
29
|
+
}) => Promise<{
|
|
30
|
+
[key: string]: string;
|
|
31
|
+
} | null>;
|
|
22
32
|
/**
|
|
23
33
|
* function which will be used to check connection with integration service
|
|
24
34
|
*/
|
|
@@ -186,7 +196,18 @@ export interface IntegrationLogic extends ModuleKey {
|
|
|
186
196
|
icon: boolean;
|
|
187
197
|
close: boolean;
|
|
188
198
|
};
|
|
199
|
+
/**
|
|
200
|
+
* Skip integration nodes
|
|
201
|
+
*/
|
|
189
202
|
skipIntegrationNodes?: SkipIntegrationNodes;
|
|
203
|
+
/**
|
|
204
|
+
* Configuration for toggling skipIntegrationNodes functionality with custom title and description
|
|
205
|
+
*/
|
|
206
|
+
skipIntegrationNodesToggle?: {
|
|
207
|
+
title: string;
|
|
208
|
+
description: string;
|
|
209
|
+
value: boolean;
|
|
210
|
+
};
|
|
190
211
|
/**
|
|
191
212
|
* Async progress checking time interval to update job progress, im ms.
|
|
192
213
|
*
|
|
@@ -287,6 +287,17 @@ function applyIntegrationModuleDefaults(config, integration) {
|
|
|
287
287
|
helpText: 'Enter the path patterns for files or folders to exclude. Use wildcard selectors like `*` and `**` to match multiple files. Example: `/drafts/**`',
|
|
288
288
|
});
|
|
289
289
|
}
|
|
290
|
+
if (integration.skipIntegrationNodes && integration.skipIntegrationNodesToggle) {
|
|
291
|
+
defaultSettings.push({
|
|
292
|
+
key: 'skipIntegrationNodesToggle',
|
|
293
|
+
label: integration.skipIntegrationNodesToggle.title,
|
|
294
|
+
type: 'checkbox',
|
|
295
|
+
helpText: integration.skipIntegrationNodesToggle.description,
|
|
296
|
+
defaultValue: integration.skipIntegrationNodesToggle.value,
|
|
297
|
+
category: types_1.DefaultCategory.GENERAL,
|
|
298
|
+
position: 7,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
290
301
|
return [...defaultSettings, ...fields];
|
|
291
302
|
});
|
|
292
303
|
if (!integration.checkConnection) {
|
|
@@ -51,6 +51,7 @@ function getOneLevelFetchingFiles(integration, integrationCredentials, integrati
|
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
function getIntegrationSnapshot(integration, integrationCredentials, integrationSettings) {
|
|
54
|
+
var _a;
|
|
54
55
|
return __awaiter(this, void 0, void 0, function* () {
|
|
55
56
|
let files = [];
|
|
56
57
|
let integrationData = [];
|
|
@@ -59,7 +60,10 @@ function getIntegrationSnapshot(integration, integrationCredentials, integration
|
|
|
59
60
|
if (integration.integrationOneLevelFetching) {
|
|
60
61
|
files = yield getOneLevelFetchingFiles(integration, integrationCredentials, integrationSettings, files);
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
+
if ((integrationSettings === null || integrationSettings === void 0 ? void 0 : integrationSettings.skipIntegrationNodesToggle) === true ||
|
|
64
|
+
((integrationSettings === null || integrationSettings === void 0 ? void 0 : integrationSettings.skipIntegrationNodesToggle) === null && ((_a = integration.skipIntegrationNodesToggle) === null || _a === void 0 ? void 0 : _a.value))) {
|
|
65
|
+
files = (0, files_1.skipFilesByRegex)(files, integration.skipIntegrationNodes);
|
|
66
|
+
}
|
|
63
67
|
// trick for compatibility in requests and set files
|
|
64
68
|
files = files.map((file) => (Object.assign(Object.assign({}, file), { parentId: file.parent_id || file.parentId,
|
|
65
69
|
// eslint-disable-next-line @typescript-eslint/camelcase
|
package/out/modules/manifest.js
CHANGED
|
@@ -362,9 +362,7 @@ function handle(config) {
|
|
|
362
362
|
if (!(0, subscription_1.isAppFree)(config)) {
|
|
363
363
|
events['subscription_paid'] = '/subscription-paid';
|
|
364
364
|
}
|
|
365
|
-
const defaultScopes = config.projectIntegration
|
|
366
|
-
? [types_1.Scope.USERS, types_1.Scope.PROJECTS, types_1.Scope.APPLICATIONS]
|
|
367
|
-
: [types_1.Scope.PROJECTS];
|
|
365
|
+
const defaultScopes = config.projectIntegration ? [types_1.Scope.USERS, types_1.Scope.PROJECTS] : [types_1.Scope.PROJECTS];
|
|
368
366
|
return (_req, res) => {
|
|
369
367
|
const manifest = Object.assign(Object.assign(Object.assign(Object.assign({ identifier: config.identifier, name: config.name, logo: (0, util_1.getLogoUrl)(), baseUrl: config.baseUrl, authentication: {
|
|
370
368
|
type: config.authenticationType || types_1.AuthenticationType.APP,
|
package/out/types.d.ts
CHANGED
|
@@ -261,7 +261,7 @@ export interface ClientConfig extends ImagePath {
|
|
|
261
261
|
/**
|
|
262
262
|
* property that tells backend that AiProvider and AiPromptProvider modules can cooperate only with each one
|
|
263
263
|
*/
|
|
264
|
-
restrictAiToSameApp
|
|
264
|
+
restrictAiToSameApp?: boolean;
|
|
265
265
|
}
|
|
266
266
|
export interface Environments {
|
|
267
267
|
environments?: Environment | Environment[];
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
<div id="buttons">
|
|
37
37
|
<crowdin-button id="show-integration-btn" class="hidden" icon-before="arrow_back" onclick="showIntegration();">Integration</crowdin-button>
|
|
38
38
|
<crowdin-button id="show-error-logs-btn" icon-before="list" onclick="showErrorLogs();">Error logs</crowdin-button>
|
|
39
|
-
|
|
39
|
+
{{#if isManager}}
|
|
40
|
+
<crowdin-button icon-before="link" onclick="showPermissionsDialog()">Share</crowdin-button>
|
|
41
|
+
{{/if}}
|
|
40
42
|
{{#if infoModal}}
|
|
41
43
|
<crowdin-button icon-before="info" onclick="openModal(infoModal);">{{infoModal.title}}</crowdin-button>
|
|
42
44
|
{{/if}}
|
|
@@ -154,7 +156,7 @@
|
|
|
154
156
|
label="Users"
|
|
155
157
|
help-text="Search for members by name, username, email, or invite new ones using their email."
|
|
156
158
|
is-position-fixed
|
|
157
|
-
onchange="
|
|
159
|
+
onchange="checkUsers()"
|
|
158
160
|
>
|
|
159
161
|
</crowdin-select>
|
|
160
162
|
</div>
|
|
@@ -221,7 +223,7 @@
|
|
|
221
223
|
</div>
|
|
222
224
|
|
|
223
225
|
<div slot="footer">
|
|
224
|
-
<crowdin-button id="confirm-users-btn" outlined onclick="inviteUsers(
|
|
226
|
+
<crowdin-button id="confirm-users-btn" outlined onclick="inviteUsers()">Save</crowdin-button>
|
|
225
227
|
</div>
|
|
226
228
|
</crowdin-modal>
|
|
227
229
|
|
|
@@ -285,6 +287,7 @@
|
|
|
285
287
|
{{#if dependencySettings}}
|
|
286
288
|
data-dependency="{{dependencySettings}}"
|
|
287
289
|
{{/if}}
|
|
290
|
+
onchange="settingsFieldChange(this)"
|
|
288
291
|
>
|
|
289
292
|
</crowdin-checkbox>
|
|
290
293
|
{{/ifeq}}
|
|
@@ -310,6 +313,7 @@
|
|
|
310
313
|
data-dependency="{{dependencySettings}}"
|
|
311
314
|
{{/if}}
|
|
312
315
|
is-position-fixed
|
|
316
|
+
onchange="settingsFieldChange(this)"
|
|
313
317
|
>
|
|
314
318
|
{{#each options}}
|
|
315
319
|
<option
|
|
@@ -338,6 +342,7 @@
|
|
|
338
342
|
data-dependency="{{dependencySettings}}"
|
|
339
343
|
{{/if}}
|
|
340
344
|
value="{{#if defaultValue}}{{defaultValue}}{{/if}}"
|
|
345
|
+
onchange="settingsFieldChange(this)"
|
|
341
346
|
>
|
|
342
347
|
</crowdin-input>
|
|
343
348
|
{{/ifeq}}
|
|
@@ -355,7 +360,9 @@
|
|
|
355
360
|
{{#if dependencySettings}}
|
|
356
361
|
data-dependency="{{dependencySettings}}"
|
|
357
362
|
{{/if}}
|
|
358
|
-
value="{{#if defaultValue}}{{defaultValue}}{{/if}}"
|
|
363
|
+
value="{{#if defaultValue}}{{defaultValue}}{{/if}}"
|
|
364
|
+
onchange="settingsFieldChange(this)"
|
|
365
|
+
>
|
|
359
366
|
</crowdin-textarea>
|
|
360
367
|
{{/ifeq}}
|
|
361
368
|
{{#ifeq type "notice"}}
|
|
@@ -959,6 +966,18 @@
|
|
|
959
966
|
permissionsModal.setAttribute('body-overflow-unset', true)
|
|
960
967
|
}
|
|
961
968
|
|
|
969
|
+
function getUserFullName(user) {
|
|
970
|
+
if (user.full_name) {
|
|
971
|
+
return user.full_name;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const ownerFullName = user.name || ((user.first_name || '') + ' ' + (user.last_name || '')).trim();
|
|
975
|
+
|
|
976
|
+
return !!ownerFullName && user?.login !== ownerFullName
|
|
977
|
+
? `${ownerFullName} (${user?.login})`
|
|
978
|
+
: user?.login;
|
|
979
|
+
}
|
|
980
|
+
|
|
962
981
|
function showPermissionsDialog() {
|
|
963
982
|
hideConfirmUsersBlock();
|
|
964
983
|
openModal(permissions)
|
|
@@ -967,13 +986,26 @@
|
|
|
967
986
|
|
|
968
987
|
select.value = '[]';
|
|
969
988
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
989
|
+
Promise.all([
|
|
990
|
+
new Promise((resolve) => {
|
|
991
|
+
checkOrigin()
|
|
992
|
+
.then(restParams => fetch('api/users' + restParams))
|
|
993
|
+
.then(checkResponse)
|
|
994
|
+
.then((res) => {
|
|
995
|
+
resolve(JSON.stringify(res.data.managers));
|
|
996
|
+
})
|
|
997
|
+
}),
|
|
998
|
+
new Promise((resolve) => {
|
|
999
|
+
AP.getUsers(res => {
|
|
1000
|
+
const users = Object.values(res).filter(user => user.id !== Number('{{userId}}'));
|
|
1001
|
+
|
|
1002
|
+
resolve(users.map(user => `<option value="${user.id}">${sanitizeHTML(getUserFullName(user))}</option>`).join(''));
|
|
1003
|
+
})
|
|
1004
|
+
})
|
|
1005
|
+
])
|
|
1006
|
+
.then(([managers, users]) => {
|
|
1007
|
+
select.innerHTML = users;
|
|
1008
|
+
select.value = managers;
|
|
977
1009
|
})
|
|
978
1010
|
.catch(e => catchRejection(e, 'Can\'t fetch users'))
|
|
979
1011
|
.finally(() => unsetLoader('#permissions-modal'));
|
|
@@ -987,43 +1019,116 @@
|
|
|
987
1019
|
permissionsModal.removeAttribute('body-overflow-unset')
|
|
988
1020
|
}
|
|
989
1021
|
|
|
990
|
-
|
|
1022
|
+
function checkUsers() {
|
|
991
1023
|
setLoader('#permissions-modal');
|
|
992
1024
|
|
|
993
1025
|
const select = document.getElementById('users');
|
|
994
1026
|
|
|
995
|
-
if (
|
|
1027
|
+
if (select.value === '[]') {
|
|
996
1028
|
hideConfirmUsersBlock();
|
|
997
1029
|
unsetLoader('#permissions-modal');
|
|
998
1030
|
return;
|
|
999
1031
|
}
|
|
1000
1032
|
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1033
|
+
const users = JSON.parse(select.value);
|
|
1034
|
+
const user_ids = users.filter(id => parseInt(id));
|
|
1035
|
+
const emails = users.filter(id => !parseInt(id));
|
|
1036
|
+
|
|
1037
|
+
const params = { user_ids, emails };
|
|
1038
|
+
|
|
1039
|
+
Promise.all([
|
|
1040
|
+
new Promise((resolve) => {
|
|
1041
|
+
checkOrigin()
|
|
1042
|
+
.then(restParams => fetch('api/users' + restParams))
|
|
1043
|
+
.then(checkResponse)
|
|
1044
|
+
.then((usersResponse) => {
|
|
1045
|
+
resolve(usersResponse.data.managers);
|
|
1046
|
+
});
|
|
1047
|
+
}),
|
|
1048
|
+
new Promise((resolve) => {
|
|
1049
|
+
AP.validateUsers(params, (res) => {
|
|
1050
|
+
resolve(res.data);
|
|
1051
|
+
});
|
|
1052
|
+
}),
|
|
1053
|
+
new Promise((resolve) => {
|
|
1054
|
+
AP.getUsers(res => {
|
|
1055
|
+
resolve(res);
|
|
1056
|
+
});
|
|
1057
|
+
})
|
|
1058
|
+
])
|
|
1059
|
+
.then(([appManagers, checkUsersResponse, allUsers]) => {
|
|
1060
|
+
const newUserEmails = checkUsersResponse.newUserEmails.map(email => ({name: email}));
|
|
1061
|
+
|
|
1062
|
+
const newProjectManagers = [
|
|
1063
|
+
...newUserEmails,
|
|
1064
|
+
...checkUsersResponse.newProjectManagers.map(user => ({
|
|
1065
|
+
name: getUserFullName(user),
|
|
1066
|
+
})),
|
|
1067
|
+
];
|
|
1068
|
+
|
|
1069
|
+
const newApplicationAccessUsers = [
|
|
1070
|
+
...newUserEmails,
|
|
1071
|
+
...checkUsersResponse.newApplicationAccessUsers.map(user => ({
|
|
1072
|
+
name: getUserFullName(user),
|
|
1073
|
+
})),
|
|
1074
|
+
];
|
|
1075
|
+
|
|
1076
|
+
const newApplicationManagers = [
|
|
1077
|
+
...newUserEmails,
|
|
1078
|
+
...Object.values(allUsers)
|
|
1079
|
+
.filter(user => users.some(id => +id === user.id) && !appManagers.some(id => +id === user.id))
|
|
1080
|
+
.map(user => ({name: getUserFullName(user)})),
|
|
1081
|
+
];
|
|
1082
|
+
|
|
1083
|
+
prepareUsersConfirmBlock({
|
|
1084
|
+
...checkUsersResponse,
|
|
1085
|
+
newUserEmails,
|
|
1086
|
+
newProjectManagers,
|
|
1087
|
+
newApplicationAccessUsers,
|
|
1088
|
+
newApplicationManagers,
|
|
1089
|
+
});
|
|
1090
|
+
})
|
|
1091
|
+
.catch(e => catchRejection(e, e?.error || 'Can\'t invite users'))
|
|
1092
|
+
.finally(() => unsetLoader('#permissions-modal'));
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function inviteUsers() {
|
|
1096
|
+
setLoader('#permissions-modal');
|
|
1097
|
+
|
|
1098
|
+
const select = document.getElementById('users');
|
|
1099
|
+
const users = JSON.parse(select.value);
|
|
1100
|
+
const user_ids = users.filter(id => parseInt(id));
|
|
1101
|
+
const emails = users.filter(id => !parseInt(id));
|
|
1102
|
+
|
|
1103
|
+
const params = { user_ids, emails };
|
|
1104
|
+
|
|
1105
|
+
AP.inviteUsers(params, (res) => {
|
|
1106
|
+
if (!res.success) {
|
|
1107
|
+
catchRejection(res, res?.error?.message || 'Can\'t invite users')
|
|
1108
|
+
unsetLoader('#permissions-modal');
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
checkOrigin()
|
|
1113
|
+
.then(restParams => fetch('api/invite-users' + restParams, {
|
|
1114
|
+
method: 'POST',
|
|
1115
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1116
|
+
body: JSON.stringify(res.data)
|
|
1117
|
+
}))
|
|
1118
|
+
.then(checkResponse)
|
|
1119
|
+
.then((res) => {
|
|
1120
|
+
if (!res.success) {
|
|
1121
|
+
catchRejection(res, res?.message || 'Can\'t invite users');
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1005
1124
|
|
|
1006
|
-
checkOrigin()
|
|
1007
|
-
.then(restParams => fetch('api/invite-users' + restParams, {
|
|
1008
|
-
method: 'POST',
|
|
1009
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1010
|
-
body: JSON.stringify(params)
|
|
1011
|
-
}))
|
|
1012
|
-
.then(checkResponse)
|
|
1013
|
-
.then((response) => {
|
|
1014
|
-
if (!onlyCheck) {
|
|
1015
1125
|
showToast('Users successfully updated');
|
|
1016
1126
|
hideConfirmUsersBlock();
|
|
1017
1127
|
closeModal(permissions);
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
})
|
|
1023
|
-
.catch(e => {
|
|
1024
|
-
catchRejection(e, e?.error || 'Can\'t invite users')
|
|
1025
|
-
})
|
|
1026
|
-
.finally(() => unsetLoader('#permissions-modal'));
|
|
1128
|
+
})
|
|
1129
|
+
.catch((e) => catchRejection(e, e?.error || 'Can\'t invite users'))
|
|
1130
|
+
.finally(() => unsetLoader('#permissions-modal'));
|
|
1131
|
+
});
|
|
1027
1132
|
}
|
|
1028
1133
|
|
|
1029
1134
|
function prepareUsersConfirmBlock(usersData) {
|
|
@@ -1044,8 +1149,8 @@
|
|
|
1044
1149
|
processUsersWhoWillBeInvitedToOrganization(organizationInvite, usersData, organizationWillGrantElement, grantedElement, notAvailableElement);
|
|
1045
1150
|
}
|
|
1046
1151
|
|
|
1047
|
-
processUsersWhoWillBeInvited(projectInvite, usersData.
|
|
1048
|
-
processUsersWhoWillBeInvited(applicationCredentialsInvite, usersData.
|
|
1152
|
+
processUsersWhoWillBeInvited(projectInvite, usersData.newProjectManagers, willGrantedElement, grantedElement);
|
|
1153
|
+
processUsersWhoWillBeInvited(applicationCredentialsInvite, usersData.newApplicationManagers, willGrantedElement, grantedElement);
|
|
1049
1154
|
|
|
1050
1155
|
processUsersWhoWillBeInvitedApplicationSettings(usersData, willGrantedElement, grantedElement, notAvailableElement);
|
|
1051
1156
|
}
|
|
@@ -1058,7 +1163,7 @@
|
|
|
1058
1163
|
description.classList.remove('text-warning');
|
|
1059
1164
|
description.innerText = '';
|
|
1060
1165
|
|
|
1061
|
-
const users = usersData.
|
|
1166
|
+
const users = usersData.newUserEmails;
|
|
1062
1167
|
|
|
1063
1168
|
if (users.length) {
|
|
1064
1169
|
if (usersData.inviteRestricted) {
|
|
@@ -1114,25 +1219,13 @@
|
|
|
1114
1219
|
|
|
1115
1220
|
let affectedUsers = '<ul>';
|
|
1116
1221
|
|
|
1117
|
-
if (!usersData.
|
|
1118
|
-
descriptionMessage += ' The application doesn\'t have permission to update this setting.';
|
|
1119
|
-
descriptionMessage += usersData.isAdmin ? ' Please reinstall the app.' : ' Please ask the organization admin to reinstall the app.';
|
|
1120
|
-
|
|
1121
|
-
description.classList.add('text-warning');
|
|
1122
|
-
description.innerText = descriptionMessage;
|
|
1123
|
-
|
|
1124
|
-
tooltip.innerHTML = notAvailableElement;
|
|
1125
|
-
for (const user of usersData.usersWhoNotIssetInApplicationInstallation) {
|
|
1126
|
-
affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
|
|
1127
|
-
}
|
|
1128
|
-
userList.innerHTML = affectedUsers + '</ul>';
|
|
1129
|
-
} else if (!usersData.usersWhoNotIssetInApplicationInstallation.length) {
|
|
1222
|
+
if (!usersData.newApplicationAccessUsers.length) {
|
|
1130
1223
|
tooltip.innerHTML = grantedElement;
|
|
1131
1224
|
userList.innerHTML = grantedElement;
|
|
1132
1225
|
} else {
|
|
1133
1226
|
if (usersData.isAdmin) {
|
|
1134
1227
|
tooltip.innerHTML = willGrantedElement;
|
|
1135
|
-
for (const user of usersData.
|
|
1228
|
+
for (const user of usersData.newApplicationAccessUsers) {
|
|
1136
1229
|
affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
|
|
1137
1230
|
}
|
|
1138
1231
|
userList.innerHTML = affectedUsers + '</ul>';
|
|
@@ -1143,7 +1236,7 @@
|
|
|
1143
1236
|
description.innerText = descriptionMessage;
|
|
1144
1237
|
|
|
1145
1238
|
tooltip.innerHTML = notAvailableElement;
|
|
1146
|
-
for (const user of usersData.
|
|
1239
|
+
for (const user of usersData.newApplicationAccessUsers) {
|
|
1147
1240
|
affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
|
|
1148
1241
|
}
|
|
1149
1242
|
userList.innerHTML = affectedUsers + '</ul>';
|
|
@@ -1227,6 +1320,7 @@
|
|
|
1227
1320
|
});
|
|
1228
1321
|
});
|
|
1229
1322
|
|
|
1323
|
+
let isValidationError = false;
|
|
1230
1324
|
settingsSaveBtn.setAttribute('disabled', true);
|
|
1231
1325
|
checkOrigin()
|
|
1232
1326
|
.then(restParams => fetch('api/settings' + restParams, {
|
|
@@ -1239,15 +1333,33 @@
|
|
|
1239
1333
|
showToast('Settings successfully saved');
|
|
1240
1334
|
config = configReq;
|
|
1241
1335
|
})
|
|
1242
|
-
.catch(e =>
|
|
1336
|
+
.catch(e => {
|
|
1337
|
+
if (e?.error && e?.error?.type === 'validation_error') {
|
|
1338
|
+
Object.keys(e?.error?.details).forEach(key => {
|
|
1339
|
+
const el = document.getElementById(`${key}-settings`);
|
|
1340
|
+
if (el) {
|
|
1341
|
+
el.setAttribute('error', 'true');
|
|
1342
|
+
el.setAttribute('error-text', e?.error?.details[key]);
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
isValidationError = true;
|
|
1347
|
+
showToast(e?.error?.details?.message || 'Can\'t save settings');
|
|
1348
|
+
settingsSaveBtn.removeAttribute('disabled');
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
catchRejection(e, 'Can\'t save settings')
|
|
1352
|
+
})
|
|
1243
1353
|
.finally(() => {
|
|
1244
1354
|
unsetLoader('#settings-modal');
|
|
1245
1355
|
settingsSaveBtn.removeAttribute('disabled');
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1356
|
+
if (!isValidationError) {
|
|
1357
|
+
closeModal(settingsModal);
|
|
1358
|
+
{{#if reloadOnConfigSave}}
|
|
1359
|
+
getIntegrationData(true);
|
|
1360
|
+
getCrowdinData();
|
|
1361
|
+
{{/if}}
|
|
1362
|
+
}
|
|
1251
1363
|
});
|
|
1252
1364
|
}
|
|
1253
1365
|
{{else}}
|
|
@@ -1682,6 +1794,11 @@
|
|
|
1682
1794
|
modal.style.display = 'none';
|
|
1683
1795
|
modal.close()
|
|
1684
1796
|
}
|
|
1797
|
+
|
|
1798
|
+
function settingsFieldChange(el) {
|
|
1799
|
+
el.removeAttribute('error');
|
|
1800
|
+
el.removeAttribute('error-text');
|
|
1801
|
+
}
|
|
1685
1802
|
</script>
|
|
1686
1803
|
|
|
1687
1804
|
</html>
|
|
@@ -26,24 +26,28 @@
|
|
|
26
26
|
crossorigin="anonymous"
|
|
27
27
|
></script>
|
|
28
28
|
<script>
|
|
29
|
-
Sentry
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
if (typeof Sentry !== 'undefined') {
|
|
30
|
+
Sentry.init({
|
|
31
|
+
dsn: "{{sentryData.dsn}}",
|
|
32
|
+
environment: "frontend",
|
|
33
|
+
replaysSessionSampleRate: 0,
|
|
34
|
+
replaysOnErrorSampleRate: 1.0,
|
|
35
|
+
integrations: [new Sentry.Replay()],
|
|
36
|
+
});
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
Sentry.configureScope(function(scope) {
|
|
39
|
+
scope.setTag("identifier", "{{sentryData.appIdentifier}}");
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
AP.getContext(contextData => {
|
|
42
|
+
const { user_id, ...rest } = contextData;
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
user_id && scope.setUser({ id: user_id });
|
|
45
|
+
scope.setTags(rest);
|
|
46
|
+
});
|
|
45
47
|
});
|
|
46
|
-
}
|
|
48
|
+
} else {
|
|
49
|
+
console.warn('Sentry is not available. This might be due to ad/tracking blockers or network issues.');
|
|
50
|
+
}
|
|
47
51
|
</script>
|
|
48
52
|
{{/if}}
|
|
49
53
|
</head>
|
package/package.json
CHANGED