@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.
Files changed (78) hide show
  1. package/iam-policy/OrgMembersPanel.d.ts +7 -0
  2. package/iam-policy/OrgMembersPanel.d.ts.map +1 -1
  3. package/iam-policy/OrgMembersPanel.js +28 -12
  4. package/iam-policy/OrgMembersPanel.js.map +1 -1
  5. package/iam-policy/RoleSelector.d.ts +7 -1
  6. package/iam-policy/RoleSelector.d.ts.map +1 -1
  7. package/iam-policy/RoleSelector.js +2 -2
  8. package/iam-policy/RoleSelector.js.map +1 -1
  9. package/iam-policy/useOwnerAssignment.d.ts +23 -0
  10. package/iam-policy/useOwnerAssignment.d.ts.map +1 -0
  11. package/iam-policy/useOwnerAssignment.js +23 -0
  12. package/iam-policy/useOwnerAssignment.js.map +1 -0
  13. package/iam-policy/useRoleSelector.d.ts +6 -1
  14. package/iam-policy/useRoleSelector.d.ts.map +1 -1
  15. package/iam-policy/useRoleSelector.js +11 -3
  16. package/iam-policy/useRoleSelector.js.map +1 -1
  17. package/invitation/InvitationManager.d.ts.map +1 -1
  18. package/invitation/InvitationManager.js +5 -1
  19. package/invitation/InvitationManager.js.map +1 -1
  20. package/oauth-app/OAuthAppDetailPanel.d.ts +5 -1
  21. package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
  22. package/oauth-app/OAuthAppDetailPanel.js +10 -5
  23. package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
  24. package/oauth-app/OAuthAppListPanel.d.ts +11 -3
  25. package/oauth-app/OAuthAppListPanel.d.ts.map +1 -1
  26. package/oauth-app/OAuthAppListPanel.js +4 -4
  27. package/oauth-app/OAuthAppListPanel.js.map +1 -1
  28. package/oauth-app/copy.d.ts +15 -0
  29. package/oauth-app/copy.d.ts.map +1 -0
  30. package/oauth-app/copy.js +15 -0
  31. package/oauth-app/copy.js.map +1 -0
  32. package/oauth-app/useDeleteOAuthApp.d.ts +4 -3
  33. package/oauth-app/useDeleteOAuthApp.d.ts.map +1 -1
  34. package/oauth-app/useDeleteOAuthApp.js +4 -3
  35. package/oauth-app/useDeleteOAuthApp.js.map +1 -1
  36. package/oauth-app/useOAuthAppList.d.ts +6 -4
  37. package/oauth-app/useOAuthAppList.d.ts.map +1 -1
  38. package/oauth-app/useOAuthAppList.js +6 -4
  39. package/oauth-app/useOAuthAppList.js.map +1 -1
  40. package/package.json +5 -5
  41. package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
  42. package/platform-client/PlatformClientDetailPanel.js +9 -4
  43. package/platform-client/PlatformClientDetailPanel.js.map +1 -1
  44. package/platform-client/PlatformClientListPanel.d.ts.map +1 -1
  45. package/platform-client/PlatformClientListPanel.js +2 -1
  46. package/platform-client/PlatformClientListPanel.js.map +1 -1
  47. package/platform-client/expiry.d.ts +4 -0
  48. package/platform-client/expiry.d.ts.map +1 -0
  49. package/platform-client/expiry.js +18 -0
  50. package/platform-client/expiry.js.map +1 -0
  51. package/settings/MembersSection.d.ts.map +1 -1
  52. package/settings/MembersSection.js +1 -1
  53. package/settings/MembersSection.js.map +1 -1
  54. package/settings/OAuthAppsSection.d.ts +9 -1
  55. package/settings/OAuthAppsSection.d.ts.map +1 -1
  56. package/settings/OAuthAppsSection.js +17 -3
  57. package/settings/OAuthAppsSection.js.map +1 -1
  58. package/src/iam-policy/OrgMembersPanel.tsx +38 -8
  59. package/src/iam-policy/RoleSelector.tsx +8 -0
  60. package/src/iam-policy/__tests__/OrgMembersPanel.test.tsx +139 -0
  61. package/src/iam-policy/__tests__/useOwnerAssignment.test.tsx +55 -0
  62. package/src/iam-policy/useOwnerAssignment.ts +40 -0
  63. package/src/iam-policy/useRoleSelector.ts +14 -3
  64. package/src/invitation/InvitationManager.tsx +5 -0
  65. package/src/oauth-app/OAuthAppDetailPanel.tsx +39 -24
  66. package/src/oauth-app/OAuthAppListPanel.tsx +12 -3
  67. package/src/oauth-app/__tests__/OAuthAppDetailPanel.test.tsx +57 -9
  68. package/src/oauth-app/copy.ts +16 -0
  69. package/src/oauth-app/useDeleteOAuthApp.ts +4 -3
  70. package/src/oauth-app/useOAuthAppList.ts +6 -4
  71. package/src/platform-client/PlatformClientDetailPanel.tsx +28 -5
  72. package/src/platform-client/PlatformClientListPanel.tsx +2 -1
  73. package/src/platform-client/__tests__/PlatformClientDetailPanel.test.tsx +45 -3
  74. package/src/platform-client/__tests__/expiry.test.ts +37 -0
  75. package/src/platform-client/expiry.ts +22 -0
  76. package/src/settings/MembersSection.tsx +2 -1
  77. package/src/settings/OAuthAppsSection.tsx +21 -3
  78. 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
- /** Settings section for organization OAuth app credentials. */
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
+ });