@stigmer/react 3.30.0 → 3.32.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/iam-policy/OrgMembersPanel.d.ts +7 -0
- package/iam-policy/OrgMembersPanel.d.ts.map +1 -1
- package/iam-policy/OrgMembersPanel.js +28 -12
- package/iam-policy/OrgMembersPanel.js.map +1 -1
- package/iam-policy/RoleSelector.d.ts +7 -1
- package/iam-policy/RoleSelector.d.ts.map +1 -1
- package/iam-policy/RoleSelector.js +2 -2
- package/iam-policy/RoleSelector.js.map +1 -1
- package/iam-policy/useOwnerAssignment.d.ts +23 -0
- package/iam-policy/useOwnerAssignment.d.ts.map +1 -0
- package/iam-policy/useOwnerAssignment.js +23 -0
- package/iam-policy/useOwnerAssignment.js.map +1 -0
- package/iam-policy/useRoleSelector.d.ts +6 -1
- package/iam-policy/useRoleSelector.d.ts.map +1 -1
- package/iam-policy/useRoleSelector.js +11 -3
- package/iam-policy/useRoleSelector.js.map +1 -1
- package/invitation/InvitationManager.d.ts.map +1 -1
- package/invitation/InvitationManager.js +5 -1
- package/invitation/InvitationManager.js.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.d.ts +5 -1
- package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.js +10 -5
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
- package/oauth-app/OAuthAppListPanel.d.ts +11 -3
- package/oauth-app/OAuthAppListPanel.d.ts.map +1 -1
- package/oauth-app/OAuthAppListPanel.js +4 -4
- package/oauth-app/OAuthAppListPanel.js.map +1 -1
- package/oauth-app/copy.d.ts +15 -0
- package/oauth-app/copy.d.ts.map +1 -0
- package/oauth-app/copy.js +15 -0
- package/oauth-app/copy.js.map +1 -0
- package/oauth-app/useDeleteOAuthApp.d.ts +4 -3
- package/oauth-app/useDeleteOAuthApp.d.ts.map +1 -1
- package/oauth-app/useDeleteOAuthApp.js +4 -3
- package/oauth-app/useDeleteOAuthApp.js.map +1 -1
- package/oauth-app/useOAuthAppList.d.ts +6 -4
- package/oauth-app/useOAuthAppList.d.ts.map +1 -1
- package/oauth-app/useOAuthAppList.js +6 -4
- package/oauth-app/useOAuthAppList.js.map +1 -1
- package/package.json +5 -5
- package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
- package/platform-client/PlatformClientDetailPanel.js +9 -4
- package/platform-client/PlatformClientDetailPanel.js.map +1 -1
- package/platform-client/PlatformClientListPanel.d.ts.map +1 -1
- package/platform-client/PlatformClientListPanel.js +2 -1
- package/platform-client/PlatformClientListPanel.js.map +1 -1
- package/platform-client/expiry.d.ts +4 -0
- package/platform-client/expiry.d.ts.map +1 -0
- package/platform-client/expiry.js +18 -0
- package/platform-client/expiry.js.map +1 -0
- package/settings/MembersSection.d.ts.map +1 -1
- package/settings/MembersSection.js +1 -1
- package/settings/MembersSection.js.map +1 -1
- package/settings/OAuthAppsSection.d.ts +9 -1
- package/settings/OAuthAppsSection.d.ts.map +1 -1
- package/settings/OAuthAppsSection.js +17 -3
- package/settings/OAuthAppsSection.js.map +1 -1
- package/src/iam-policy/OrgMembersPanel.tsx +38 -8
- package/src/iam-policy/RoleSelector.tsx +8 -0
- package/src/iam-policy/__tests__/OrgMembersPanel.test.tsx +139 -0
- package/src/iam-policy/__tests__/useOwnerAssignment.test.tsx +55 -0
- package/src/iam-policy/useOwnerAssignment.ts +40 -0
- package/src/iam-policy/useRoleSelector.ts +14 -3
- package/src/invitation/InvitationManager.tsx +5 -0
- package/src/oauth-app/OAuthAppDetailPanel.tsx +39 -24
- package/src/oauth-app/OAuthAppListPanel.tsx +12 -3
- package/src/oauth-app/__tests__/OAuthAppDetailPanel.test.tsx +57 -9
- package/src/oauth-app/copy.ts +16 -0
- package/src/oauth-app/useDeleteOAuthApp.ts +4 -3
- package/src/oauth-app/useOAuthAppList.ts +6 -4
- package/src/platform-client/PlatformClientDetailPanel.tsx +28 -5
- package/src/platform-client/PlatformClientListPanel.tsx +2 -1
- package/src/platform-client/__tests__/PlatformClientDetailPanel.test.tsx +45 -3
- package/src/platform-client/__tests__/expiry.test.ts +37 -0
- package/src/platform-client/expiry.ts +22 -0
- package/src/settings/MembersSection.tsx +2 -1
- package/src/settings/OAuthAppsSection.tsx +21 -3
- package/src/settings/__tests__/OAuthAppsSection.test.tsx +133 -0
|
@@ -5,20 +5,37 @@ import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb
|
|
|
5
5
|
import { OAuthAppListPanel } from "../oauth-app/OAuthAppListPanel.js";
|
|
6
6
|
import { CreateOAuthAppForm } from "../oauth-app/CreateOAuthAppForm.js";
|
|
7
7
|
import { OAuthAppDetailPanel } from "../oauth-app/OAuthAppDetailPanel.js";
|
|
8
|
+
import { OAUTH_APPS_MANAGED_BY_ADMINS } from "../oauth-app/copy.js";
|
|
8
9
|
import { useResourceAvailable, ApiResourceKind } from "../deployment-mode.js";
|
|
10
|
+
import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
|
|
9
11
|
import { CloudFeatureNotice } from "../internal/CloudFeatureNotice.js";
|
|
10
|
-
import { useActiveOrgSlug } from "../organization/OrgProvider.js";
|
|
12
|
+
import { useActiveOrgId, useActiveOrgSlug } from "../organization/OrgProvider.js";
|
|
11
13
|
|
|
12
14
|
type FlowState =
|
|
13
15
|
| { phase: "idle" }
|
|
14
16
|
| { phase: "creating" }
|
|
15
17
|
| { phase: "editing"; oauthApp: OAuthApp };
|
|
16
18
|
|
|
17
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Settings section for organization OAuth app credentials.
|
|
21
|
+
*
|
|
22
|
+
* "New OAuth app" is offered once the server confirms the caller holds
|
|
23
|
+
* `can_create_oauth_app` on the organization. The list shows only the apps
|
|
24
|
+
* the caller may view (each app's creator and the organization's admins),
|
|
25
|
+
* so a caller who may not create OAuth apps is told that the
|
|
26
|
+
* organization's admins manage them rather than that none is configured.
|
|
27
|
+
*/
|
|
18
28
|
export function OAuthAppsSection() {
|
|
19
29
|
const headingId = useId();
|
|
20
30
|
const org = useActiveOrgSlug();
|
|
31
|
+
const orgId = useActiveOrgId();
|
|
21
32
|
const oauthAppsAvailable = useResourceAvailable(ApiResourceKind.oauth_app);
|
|
33
|
+
const createCheck = useCheckPermission(
|
|
34
|
+
oauthAppsAvailable && orgId ? { kind: "organization", id: orgId } : null,
|
|
35
|
+
"can_create_oauth_app",
|
|
36
|
+
);
|
|
37
|
+
const canCreate = !createCheck.isLoading && createCheck.allowed;
|
|
38
|
+
const deniedCreate = !createCheck.isLoading && !createCheck.allowed;
|
|
22
39
|
|
|
23
40
|
const [flow, setFlow] = useState<FlowState>({ phase: "idle" });
|
|
24
41
|
const listRefetchRef = useRef<(() => void) | null>(null);
|
|
@@ -52,7 +69,7 @@ export function OAuthAppsSection() {
|
|
|
52
69
|
OAuth Apps
|
|
53
70
|
</h2>
|
|
54
71
|
|
|
55
|
-
{oauthAppsAvailable && org && flow.phase === "idle" && (
|
|
72
|
+
{oauthAppsAvailable && org && canCreate && flow.phase === "idle" && (
|
|
56
73
|
<button
|
|
57
74
|
type="button"
|
|
58
75
|
onClick={() => setFlow({ phase: "creating" })}
|
|
@@ -94,6 +111,7 @@ export function OAuthAppsSection() {
|
|
|
94
111
|
org={org}
|
|
95
112
|
onEdit={(app) => setFlow({ phase: "editing", oauthApp: app })}
|
|
96
113
|
onRefetchRef={handleRefetchRef}
|
|
114
|
+
emptyState={deniedCreate ? OAUTH_APPS_MANAGED_BY_ADMINS : undefined}
|
|
97
115
|
/>
|
|
98
116
|
)}
|
|
99
117
|
</section>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins what OAuthAppsSection offers a caller by the server's verdicts.
|
|
3
|
+
* OAuth apps are administrative: an app carries its organization's vendor
|
|
4
|
+
* credentials, the model shows it to its creator and the organization's
|
|
5
|
+
* admins only, and the list holds only the apps the caller may view
|
|
6
|
+
* (stigmer/stigmer#1257), so the section must neither offer what the server
|
|
7
|
+
* refuses nor call an empty list "nothing configured" to someone who simply
|
|
8
|
+
* may not see it.
|
|
9
|
+
*
|
|
10
|
+
* - the list asks for the active organization's apps by its slug;
|
|
11
|
+
* - "New OAuth app" is offered only with `can_create_oauth_app` on the
|
|
12
|
+
* organization, checked on the organization's id;
|
|
13
|
+
* - an empty list reads "No OAuth apps configured yet." to a caller who
|
|
14
|
+
* may create one, and names the organization's admins to a caller the
|
|
15
|
+
* server has refused.
|
|
16
|
+
*
|
|
17
|
+
* The data hook and the permission check are stubbed; the list panel is
|
|
18
|
+
* real. The detail panel's Edit and Delete gates are pinned beside it
|
|
19
|
+
* (oauth-app/__tests__/OAuthAppDetailPanel.test.tsx).
|
|
20
|
+
*/
|
|
21
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
22
|
+
import { cleanup, render, screen } from "@testing-library/react";
|
|
23
|
+
import { create } from "@bufbuild/protobuf";
|
|
24
|
+
import {
|
|
25
|
+
OAuthAppSchema,
|
|
26
|
+
type OAuthApp,
|
|
27
|
+
} from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
|
|
28
|
+
import type { DeploymentMode } from "@stigmer/sdk";
|
|
29
|
+
import { DeploymentModeContext } from "../../deployment-mode";
|
|
30
|
+
|
|
31
|
+
const stubs = vi.hoisted(() => ({
|
|
32
|
+
listedOrg: undefined as string | null | undefined,
|
|
33
|
+
apps: [] as OAuthApp[],
|
|
34
|
+
allowed: new Set<string>(),
|
|
35
|
+
checked: [] as Array<[string, string, string]>,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
vi.mock("../../organization/OrgProvider.js", () => ({
|
|
39
|
+
useActiveOrgSlug: () => "acme",
|
|
40
|
+
useActiveOrgId: () => "org_acme",
|
|
41
|
+
}));
|
|
42
|
+
vi.mock("../../oauth-app/useOAuthAppList.js", () => ({
|
|
43
|
+
useOAuthAppList: (org: string | null) => {
|
|
44
|
+
stubs.listedOrg = org;
|
|
45
|
+
return {
|
|
46
|
+
oauthApps: stubs.apps,
|
|
47
|
+
isLoading: false,
|
|
48
|
+
isRefetching: false,
|
|
49
|
+
error: null,
|
|
50
|
+
refetch: () => {},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
vi.mock("../../iam-policy/useCheckPermission.js", () => ({
|
|
55
|
+
useCheckPermission: (
|
|
56
|
+
resource: { kind: string; id: string } | null,
|
|
57
|
+
relation: string,
|
|
58
|
+
) => {
|
|
59
|
+
if (resource !== null) {
|
|
60
|
+
stubs.checked.push([resource.kind, resource.id, relation]);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
allowed: resource === null ? true : stubs.allowed.has(relation),
|
|
64
|
+
isLoading: false,
|
|
65
|
+
error: null,
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
import { OAuthAppsSection } from "../OAuthAppsSection";
|
|
71
|
+
|
|
72
|
+
const SLACK = create(OAuthAppSchema, {
|
|
73
|
+
metadata: {
|
|
74
|
+
id: "oapp_slack",
|
|
75
|
+
name: "Slack OAuth",
|
|
76
|
+
slug: "slack-oauth",
|
|
77
|
+
org: "acme",
|
|
78
|
+
},
|
|
79
|
+
spec: { provider: "slack", clientId: "slack-client-1" },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
function renderSection(mode: DeploymentMode = "cloud") {
|
|
83
|
+
return render(
|
|
84
|
+
<DeploymentModeContext.Provider value={mode}>
|
|
85
|
+
<OAuthAppsSection />
|
|
86
|
+
</DeploymentModeContext.Provider>,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
afterEach(() => {
|
|
91
|
+
cleanup();
|
|
92
|
+
stubs.listedOrg = undefined;
|
|
93
|
+
stubs.apps = [];
|
|
94
|
+
stubs.allowed = new Set();
|
|
95
|
+
stubs.checked = [];
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("OAuthAppsSection", () => {
|
|
99
|
+
it("lists the active organization's apps the caller may view", () => {
|
|
100
|
+
stubs.apps = [SLACK];
|
|
101
|
+
renderSection();
|
|
102
|
+
expect(stubs.listedOrg).toBe("acme");
|
|
103
|
+
expect(screen.getByText("slack-client-1")).toBeTruthy();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("offers New OAuth app only with can_create_oauth_app on the organization", () => {
|
|
107
|
+
renderSection();
|
|
108
|
+
expect(screen.queryByRole("button", { name: /New OAuth app/ })).toBeNull();
|
|
109
|
+
expect(stubs.checked).toContainEqual([
|
|
110
|
+
"organization",
|
|
111
|
+
"org_acme",
|
|
112
|
+
"can_create_oauth_app",
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
cleanup();
|
|
116
|
+
stubs.allowed = new Set(["can_create_oauth_app"]);
|
|
117
|
+
renderSection();
|
|
118
|
+
expect(screen.getByRole("button", { name: /New OAuth app/ })).toBeTruthy();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("names the organization's admins, not an empty configuration, to a caller who may not create OAuth apps", () => {
|
|
122
|
+
renderSection();
|
|
123
|
+
expect(
|
|
124
|
+
screen.getByText("OAuth apps are managed by your organization's admins."),
|
|
125
|
+
).toBeTruthy();
|
|
126
|
+
expect(screen.queryByText("No OAuth apps configured yet.")).toBeNull();
|
|
127
|
+
|
|
128
|
+
cleanup();
|
|
129
|
+
stubs.allowed = new Set(["can_create_oauth_app"]);
|
|
130
|
+
renderSection();
|
|
131
|
+
expect(screen.getByText("No OAuth apps configured yet.")).toBeTruthy();
|
|
132
|
+
});
|
|
133
|
+
});
|