@oneuptime/common 12.0.3 → 12.0.4
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/Models/DatabaseModels/CephCluster.ts +14 -0
- package/Models/DatabaseModels/CodeRepository.ts +14 -6
- package/Models/DatabaseModels/DockerHost.ts +14 -0
- package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
- package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
- package/Models/DatabaseModels/IoTFleet.ts +14 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
- package/Models/DatabaseModels/NetworkDevice.ts +14 -0
- package/Models/DatabaseModels/NetworkInterface.ts +18 -0
- package/Models/DatabaseModels/Project.ts +16 -2
- package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
- package/Models/DatabaseModels/Service.ts +30 -0
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
- package/Models/DatabaseModels/User.ts +30 -0
- package/Server/API/AIAgentDataAPI.ts +31 -0
- package/Server/API/DashboardAPI.ts +419 -12
- package/Server/API/GitHubAPI.ts +119 -283
- package/Server/API/UserAPI.ts +263 -1
- package/Server/EnvironmentConfig.ts +7 -3
- package/Server/Infrastructure/GlobalCache.ts +37 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Services/AnalyticsDatabaseService.ts +72 -1
- package/Server/Services/CodeRepositoryService.ts +105 -2
- package/Server/Services/DatabaseService.ts +185 -2
- package/Server/Services/OpenTelemetryIngestService.ts +230 -26
- package/Server/Services/UserService.ts +184 -0
- package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
- package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
- package/Server/Utils/PasswordHash.ts +306 -0
- package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
- package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
- package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
- package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
- package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
- package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
- package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
- package/Tests/Server/API/Helpers.ts +6 -1
- package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +128 -0
- package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
- package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
- package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
- package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
- package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
- package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
- package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
- package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
- package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
- package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
- package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
- package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
- package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
- package/Tests/Types/Database/ColumnLength.test.ts +7 -1
- package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
- package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
- package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
- package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
- package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
- package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
- package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
- package/Types/Database/ColumnLength.ts +9 -1
- package/Types/Database/TableColumn.ts +11 -0
- package/Types/Database/UnsynchronizedIndex.ts +48 -0
- package/Types/HashedString.ts +139 -4
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
- package/UI/Components/Navbar/NavBar.tsx +12 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
- package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
- package/Utils/TeamMembersByProject.ts +237 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +16 -2
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +28 -1
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +32 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +304 -10
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/GitHubAPI.js +94 -167
- package/build/dist/Server/API/GitHubAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +187 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +7 -3
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +29 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
- package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +160 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +165 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
- package/build/dist/Server/Utils/PasswordHash.js +237 -0
- package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
- package/build/dist/Types/Database/ColumnLength.js +9 -1
- package/build/dist/Types/Database/ColumnLength.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
- package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
- package/build/dist/Types/HashedString.js +95 -5
- package/build/dist/Types/HashedString.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
- package/build/dist/Utils/TeamMembersByProject.js +145 -0
- package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
import "@testing-library/jest-dom";
|
|
3
|
+
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Users are the one resource whose custom field answers are not on the record
|
|
9
|
+
* being viewed: User is global, so the per-project values live on the
|
|
10
|
+
* ProjectUserProfile row for (project, user). Nothing can be rendered until
|
|
11
|
+
* that row has been found, which is why the user pages go through this
|
|
12
|
+
* wrapper instead of mounting CustomFieldsDetail directly.
|
|
13
|
+
*
|
|
14
|
+
* The same component backs two very different call sites - the dedicated
|
|
15
|
+
* Custom Fields page, which owes the reader an explanation when there is
|
|
16
|
+
* nothing to show, and the user overview, where it must simply not appear.
|
|
17
|
+
* Both halves are pinned here.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const getListMock: MockFunction = getJestMockFunction();
|
|
21
|
+
const getItemMock: MockFunction = getJestMockFunction();
|
|
22
|
+
const getCurrentProjectIdMock: MockFunction = getJestMockFunction();
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* The arrow wrappers are load bearing: jest.mock is hoisted above the compiled
|
|
26
|
+
* requires, so naming the mocks directly would capture them before their
|
|
27
|
+
* initializers have run.
|
|
28
|
+
*/
|
|
29
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
30
|
+
return {
|
|
31
|
+
__esModule: true,
|
|
32
|
+
default: {
|
|
33
|
+
getList: (...args: Array<any>) => {
|
|
34
|
+
return getListMock(...args);
|
|
35
|
+
},
|
|
36
|
+
getItem: (...args: Array<any>) => {
|
|
37
|
+
return getItemMock(...args);
|
|
38
|
+
},
|
|
39
|
+
updateById: () => {
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
jest.mock("../../../UI/Utils/Project", () => {
|
|
47
|
+
return {
|
|
48
|
+
__esModule: true,
|
|
49
|
+
default: {
|
|
50
|
+
getCurrentProjectId: (...args: Array<any>) => {
|
|
51
|
+
return getCurrentProjectIdMock(...args);
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
import UserCustomFields from "../../../../App/FeatureSet/Dashboard/src/Components/CustomFields/UserCustomFields";
|
|
58
|
+
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
59
|
+
import ProjectUserProfile from "../../../Models/DatabaseModels/ProjectUserProfile";
|
|
60
|
+
import TeamMemberCustomField from "../../../Models/DatabaseModels/TeamMemberCustomField";
|
|
61
|
+
import CustomFieldType from "../../../Types/CustomField/CustomFieldType";
|
|
62
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
63
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
64
|
+
|
|
65
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
66
|
+
"11111111-1111-4111-8111-111111111111",
|
|
67
|
+
);
|
|
68
|
+
const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
69
|
+
const PROFILE_ID: ObjectID = new ObjectID(
|
|
70
|
+
"33333333-3333-4333-8333-333333333333",
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
type ListResultShape = {
|
|
74
|
+
data: Array<BaseModel>;
|
|
75
|
+
count: number;
|
|
76
|
+
skip: number;
|
|
77
|
+
limit: number;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
type ToListResultFunction = (data: Array<BaseModel>) => ListResultShape;
|
|
81
|
+
|
|
82
|
+
const toListResult: ToListResultFunction = (
|
|
83
|
+
data: Array<BaseModel>,
|
|
84
|
+
): ListResultShape => {
|
|
85
|
+
return { data: data, count: data.length, skip: 0, limit: data.length };
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type BuildSchemaFunction = (
|
|
89
|
+
names: Array<string>,
|
|
90
|
+
) => Array<TeamMemberCustomField>;
|
|
91
|
+
|
|
92
|
+
const buildSchema: BuildSchemaFunction = (
|
|
93
|
+
names: Array<string>,
|
|
94
|
+
): Array<TeamMemberCustomField> => {
|
|
95
|
+
return names.map((name: string) => {
|
|
96
|
+
const schemaItem: TeamMemberCustomField = new TeamMemberCustomField();
|
|
97
|
+
schemaItem.name = name;
|
|
98
|
+
schemaItem.customFieldType = CustomFieldType.Text;
|
|
99
|
+
return schemaItem;
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
type BuildProfileFunction = () => ProjectUserProfile;
|
|
104
|
+
|
|
105
|
+
const buildProfile: BuildProfileFunction = (): ProjectUserProfile => {
|
|
106
|
+
const profile: ProjectUserProfile = new ProjectUserProfile();
|
|
107
|
+
profile.id = PROFILE_ID;
|
|
108
|
+
return profile;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/*
|
|
112
|
+
* getList is called for two different models: first the schema, then (only if
|
|
113
|
+
* the project has any) the profile row. Dispatch on modelType rather than call
|
|
114
|
+
* order so a test that expects the second call to be SKIPPED still reads
|
|
115
|
+
* naturally.
|
|
116
|
+
*/
|
|
117
|
+
interface ListPlan {
|
|
118
|
+
schema: Array<BaseModel>;
|
|
119
|
+
profiles: Array<BaseModel>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
type PlanListsFunction = (plan: ListPlan) => void;
|
|
123
|
+
|
|
124
|
+
const planLists: PlanListsFunction = (plan: ListPlan): void => {
|
|
125
|
+
getListMock.mockImplementation((args: any) => {
|
|
126
|
+
if (args.modelType === TeamMemberCustomField) {
|
|
127
|
+
return Promise.resolve(toListResult(plan.schema));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return Promise.resolve(toListResult(plan.profiles));
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
type ResolveProfileItemFunction = (customFields: JSONObject) => void;
|
|
135
|
+
|
|
136
|
+
const resolveProfileItemWith: ResolveProfileItemFunction = (
|
|
137
|
+
customFields: JSONObject,
|
|
138
|
+
): void => {
|
|
139
|
+
const profile: ProjectUserProfile = buildProfile();
|
|
140
|
+
profile.customFields = customFields;
|
|
141
|
+
getItemMock.mockResolvedValue(profile as never);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
afterEach(() => {
|
|
145
|
+
cleanup();
|
|
146
|
+
getListMock.mockReset();
|
|
147
|
+
getItemMock.mockReset();
|
|
148
|
+
getCurrentProjectIdMock.mockReset();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("UserCustomFields - the user overview (hideIfEmpty)", () => {
|
|
152
|
+
test("shows the member's values once a profile row exists", async () => {
|
|
153
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
154
|
+
planLists({
|
|
155
|
+
schema: buildSchema(["Employee ID", "Desk"]),
|
|
156
|
+
profiles: [buildProfile()],
|
|
157
|
+
});
|
|
158
|
+
resolveProfileItemWith({ "Employee ID": "E-4711", Desk: "3rd floor" });
|
|
159
|
+
|
|
160
|
+
render(<UserCustomFields userId={USER_ID} hideIfEmpty={true} />);
|
|
161
|
+
|
|
162
|
+
expect(await screen.findByText("E-4711")).toBeInTheDocument();
|
|
163
|
+
expect(screen.getByText("3rd floor")).toBeInTheDocument();
|
|
164
|
+
expect(screen.getByText("Employee ID")).toBeInTheDocument();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("renders nothing when the project defined no member fields", async () => {
|
|
168
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
169
|
+
planLists({ schema: [], profiles: [buildProfile()] });
|
|
170
|
+
|
|
171
|
+
const { container } = render(
|
|
172
|
+
<UserCustomFields userId={USER_ID} hideIfEmpty={true} />,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
await waitFor(() => {
|
|
176
|
+
expect(getListMock).toHaveBeenCalled();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(container).toBeEmptyDOMElement();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/*
|
|
183
|
+
* The profile row is created lazily, the first time someone saves their own
|
|
184
|
+
* custom fields. Until then there is genuinely nothing to point the card at.
|
|
185
|
+
*/
|
|
186
|
+
test("renders nothing when the member has no profile row yet", async () => {
|
|
187
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
188
|
+
planLists({ schema: buildSchema(["Employee ID"]), profiles: [] });
|
|
189
|
+
|
|
190
|
+
const { container } = render(
|
|
191
|
+
<UserCustomFields userId={USER_ID} hideIfEmpty={true} />,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
await waitFor(() => {
|
|
195
|
+
expect(getListMock.mock.calls.length).toBeGreaterThan(1);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(container).toBeEmptyDOMElement();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("renders nothing when the lookup fails", async () => {
|
|
202
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
203
|
+
getListMock.mockRejectedValue(new Error("Upgrade your plan"));
|
|
204
|
+
|
|
205
|
+
const { container } = render(
|
|
206
|
+
<UserCustomFields userId={USER_ID} hideIfEmpty={true} />,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
await waitFor(() => {
|
|
210
|
+
expect(getListMock).toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(screen.queryByText(/Upgrade your plan/i)).not.toBeInTheDocument();
|
|
214
|
+
expect(container).toBeEmptyDOMElement();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("renders nothing, and asks for nothing, without a current project", async () => {
|
|
218
|
+
getCurrentProjectIdMock.mockReturnValue(null);
|
|
219
|
+
|
|
220
|
+
const { container } = render(
|
|
221
|
+
<UserCustomFields userId={USER_ID} hideIfEmpty={true} />,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
await waitFor(() => {
|
|
225
|
+
expect(container).toBeEmptyDOMElement();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(getListMock).not.toHaveBeenCalled();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("does not look for a profile row when there are no fields to fill", async () => {
|
|
232
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
233
|
+
planLists({ schema: [], profiles: [buildProfile()] });
|
|
234
|
+
|
|
235
|
+
render(<UserCustomFields userId={USER_ID} hideIfEmpty={true} />);
|
|
236
|
+
|
|
237
|
+
await waitFor(() => {
|
|
238
|
+
expect(getListMock).toHaveBeenCalledTimes(1);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
expect((getListMock.mock.calls[0]![0] as JSONObject)["modelType"]).toBe(
|
|
242
|
+
TeamMemberCustomField,
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("looks the profile up by both project and user", async () => {
|
|
247
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
248
|
+
planLists({
|
|
249
|
+
schema: buildSchema(["Employee ID"]),
|
|
250
|
+
profiles: [buildProfile()],
|
|
251
|
+
});
|
|
252
|
+
resolveProfileItemWith({ "Employee ID": "E-4711" });
|
|
253
|
+
|
|
254
|
+
render(<UserCustomFields userId={USER_ID} hideIfEmpty={true} />);
|
|
255
|
+
|
|
256
|
+
await screen.findByText("E-4711");
|
|
257
|
+
|
|
258
|
+
const profileArgs: JSONObject = getListMock.mock.calls[1]![0] as JSONObject;
|
|
259
|
+
expect(profileArgs["modelType"]).toBe(ProjectUserProfile);
|
|
260
|
+
expect((profileArgs["query"] as JSONObject)["projectId"]).toBe(PROJECT_ID);
|
|
261
|
+
expect((profileArgs["query"] as JSONObject)["userId"]).toBe(USER_ID);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
/*
|
|
265
|
+
* The wrapper's own two lookups are not the only requests involved: the
|
|
266
|
+
* card it mounts then fetches the schema and the profile row again for
|
|
267
|
+
* itself. Hiding only the wrapper's half left the card's half free to paint
|
|
268
|
+
* "No custom fields have been added" - and then an error box - onto the
|
|
269
|
+
* overview of a user who plainly has custom fields.
|
|
270
|
+
*/
|
|
271
|
+
test("renders nothing when the card's own read of the profile fails", async () => {
|
|
272
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
273
|
+
planLists({
|
|
274
|
+
schema: buildSchema(["Employee ID"]),
|
|
275
|
+
profiles: [buildProfile()],
|
|
276
|
+
});
|
|
277
|
+
getItemMock.mockRejectedValue(new Error("Upgrade your plan"));
|
|
278
|
+
|
|
279
|
+
const { container } = render(
|
|
280
|
+
<UserCustomFields userId={USER_ID} hideIfEmpty={true} />,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
await waitFor(() => {
|
|
284
|
+
expect(getItemMock).toHaveBeenCalled();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
expect(screen.queryByText(/Upgrade your plan/i)).not.toBeInTheDocument();
|
|
288
|
+
expect(container).toBeEmptyDOMElement();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("renders nothing when the profile row disappears between the two reads", async () => {
|
|
292
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
293
|
+
planLists({
|
|
294
|
+
schema: buildSchema(["Employee ID"]),
|
|
295
|
+
profiles: [buildProfile()],
|
|
296
|
+
});
|
|
297
|
+
getItemMock.mockResolvedValue(null as never);
|
|
298
|
+
|
|
299
|
+
const { container } = render(
|
|
300
|
+
<UserCustomFields userId={USER_ID} hideIfEmpty={true} />,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
await waitFor(() => {
|
|
304
|
+
expect(getItemMock).toHaveBeenCalled();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
expect(screen.queryByText("Item not found")).not.toBeInTheDocument();
|
|
308
|
+
expect(container).toBeEmptyDOMElement();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
/*
|
|
312
|
+
* Someone else's answers, on someone else's profile page. Reading them is
|
|
313
|
+
* the point; editing them from here is not.
|
|
314
|
+
*/
|
|
315
|
+
test("is read only", async () => {
|
|
316
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
317
|
+
planLists({
|
|
318
|
+
schema: buildSchema(["Employee ID"]),
|
|
319
|
+
profiles: [buildProfile()],
|
|
320
|
+
});
|
|
321
|
+
resolveProfileItemWith({ "Employee ID": "E-4711" });
|
|
322
|
+
|
|
323
|
+
render(<UserCustomFields userId={USER_ID} hideIfEmpty={true} />);
|
|
324
|
+
|
|
325
|
+
expect(await screen.findByText("E-4711")).toBeInTheDocument();
|
|
326
|
+
expect(screen.queryByText("Edit Fields")).not.toBeInTheDocument();
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe("UserCustomFields - the dedicated page (no hideIfEmpty)", () => {
|
|
331
|
+
test("explains that the project defined no member fields", async () => {
|
|
332
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
333
|
+
planLists({ schema: [], profiles: [buildProfile()] });
|
|
334
|
+
|
|
335
|
+
render(<UserCustomFields userId={USER_ID} />);
|
|
336
|
+
|
|
337
|
+
expect(
|
|
338
|
+
await screen.findByText("No custom fields defined"),
|
|
339
|
+
).toBeInTheDocument();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("explains that this member has not filled anything in", async () => {
|
|
343
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
344
|
+
planLists({ schema: buildSchema(["Employee ID"]), profiles: [] });
|
|
345
|
+
|
|
346
|
+
render(<UserCustomFields userId={USER_ID} />);
|
|
347
|
+
|
|
348
|
+
expect(
|
|
349
|
+
await screen.findByText("No custom field values"),
|
|
350
|
+
).toBeInTheDocument();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("surfaces a failed lookup rather than hiding it", async () => {
|
|
354
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
355
|
+
getListMock.mockRejectedValue(new Error("Upgrade your plan"));
|
|
356
|
+
|
|
357
|
+
render(<UserCustomFields userId={USER_ID} />);
|
|
358
|
+
|
|
359
|
+
expect(await screen.findByText(/Upgrade your plan/i)).toBeInTheDocument();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
/*
|
|
363
|
+
* The other half of the pair above: here the card's own read is the one
|
|
364
|
+
* that fails, and this page is the one place that has to say so.
|
|
365
|
+
*/
|
|
366
|
+
test("surfaces a failure of the card's own read too", async () => {
|
|
367
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
368
|
+
planLists({
|
|
369
|
+
schema: buildSchema(["Employee ID"]),
|
|
370
|
+
profiles: [buildProfile()],
|
|
371
|
+
});
|
|
372
|
+
getItemMock.mockRejectedValue(new Error("Upgrade your plan"));
|
|
373
|
+
|
|
374
|
+
render(<UserCustomFields userId={USER_ID} />);
|
|
375
|
+
|
|
376
|
+
expect(await screen.findByText(/Upgrade your plan/i)).toBeInTheDocument();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("still shows the values when there is something to show", async () => {
|
|
380
|
+
getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
|
|
381
|
+
planLists({
|
|
382
|
+
schema: buildSchema(["Employee ID"]),
|
|
383
|
+
profiles: [buildProfile()],
|
|
384
|
+
});
|
|
385
|
+
resolveProfileItemWith({ "Employee ID": "E-4711" });
|
|
386
|
+
|
|
387
|
+
render(<UserCustomFields userId={USER_ID} />);
|
|
388
|
+
|
|
389
|
+
expect(await screen.findByText("E-4711")).toBeInTheDocument();
|
|
390
|
+
expect(screen.getByText("Custom Fields")).toBeInTheDocument();
|
|
391
|
+
});
|
|
392
|
+
});
|