Fix console app graphQL queries

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-02-13 14:11:11 +04:00
parent 4c67217e35
commit ec3d8fad40
22 changed files with 229 additions and 209 deletions

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<beba83081139a0721dd1ca6e169c56f2>> * @generated SignedSource<<dcdbded62e85b824993721946b5e5e2e>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -19,7 +19,7 @@ export type MembershipsDropdownMenuQuery$data = {
readonly id: string; readonly id: string;
readonly membership: { readonly membership: {
readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdownMenuItemFragment">; readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdownMenuItemFragment">;
} | null | undefined; };
readonly organization: { readonly organization: {
readonly name: string; readonly name: string;
readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdownMenuItem_organizationFragment">; readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdownMenuItem_organizationFragment">;
@@ -134,6 +134,8 @@ return {
"action": "THROW" "action": "THROW"
}, },
{ {
"kind": "RequiredField",
"field": {
"alias": null, "alias": null,
"args": null, "args": null,
"concreteType": "Membership", "concreteType": "Membership",
@@ -148,6 +150,8 @@ return {
} }
], ],
"storageKey": null "storageKey": null
},
"action": "THROW"
} }
], ],
"storageKey": null "storageKey": null
@@ -289,6 +293,6 @@ return {
}; };
})(); })();
(node as any).hash = "14dbd79243ffb6104ff7315b78aa7990"; (node as any).hash = "11d7ec7c24e0e539fd42e428d71998e1";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<5916f4df5b8113a300774131759cb218>> * @generated SignedSource<<b6eef6dfea77fdf5e4e151eef11f4484>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -285,6 +285,19 @@ return {
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"iam:membership:update\")" "storageKey": "permission(action:\"iam:membership:update\")"
},
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership-profile:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership-profile:delete\")"
} }
], ],
"storageKey": null "storageKey": null
@@ -328,19 +341,6 @@ return {
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"iam:membership-profile:update\")" "storageKey": "permission(action:\"iam:membership-profile:update\")"
}, },
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership-profile:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership-profile:delete\")"
},
(v11/*: any*/) (v11/*: any*/)
], ],
"storageKey": null "storageKey": null
@@ -430,12 +430,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "3ed525921615d7910d784222c189566a", "cacheID": "25aa17e466fdec627ba3d24d5230301b",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PeopleListFragment_RefetchQuery", "name": "PeopleListFragment_RefetchQuery",
"operationKind": "query", "operationKind": "query",
"text": "query PeopleListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: ProfileOrder = {direction: ASC, field: FULL_NAME}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PeopleListFragment_16fISc\n id\n }\n}\n\nfragment PeopleListFragment_16fISc on Organization {\n profiles(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n fullName\n kind\n position\n membership {\n id\n role\n source\n state\n canUpdate: permission(action: \"iam:membership:update\")\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n}\n" "text": "query PeopleListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: ProfileOrder = {direction: ASC, field: FULL_NAME}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PeopleListFragment_16fISc\n id\n }\n}\n\nfragment PeopleListFragment_16fISc on Organization {\n profiles(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n fullName\n kind\n position\n membership {\n id\n role\n source\n state\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<b56389d57c3d4cb31dd873feaeff2bb3>> * @generated SignedSource<<6e9b0a4fbf6ce65f11f2367f2ec5b6d4>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -15,7 +15,6 @@ export type MembershipState = "ACTIVE" | "INACTIVE";
export type ProfileKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT"; export type ProfileKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT";
import { FragmentRefs } from "relay-runtime"; import { FragmentRefs } from "relay-runtime";
export type PeopleListItemFragment$data = { export type PeopleListItemFragment$data = {
readonly canDelete: boolean;
readonly canUpdate: boolean; readonly canUpdate: boolean;
readonly createdAt: string; readonly createdAt: string;
readonly fullName: string; readonly fullName: string;
@@ -25,6 +24,7 @@ export type PeopleListItemFragment$data = {
}; };
readonly kind: ProfileKind; readonly kind: ProfileKind;
readonly membership: { readonly membership: {
readonly canDelete: boolean;
readonly canUpdate: boolean; readonly canUpdate: boolean;
readonly id: string; readonly id: string;
readonly role: MembershipRole; readonly role: MembershipRole;
@@ -119,6 +119,19 @@ return {
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"iam:membership:update\")" "storageKey": "permission(action:\"iam:membership:update\")"
},
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership-profile:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership-profile:delete\")"
} }
], ],
"storageKey": null "storageKey": null
@@ -166,19 +179,6 @@ return {
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"iam:membership-profile:update\")" "storageKey": "permission(action:\"iam:membership-profile:update\")"
},
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership-profile:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership-profile:delete\")"
} }
], ],
"type": "Profile", "type": "Profile",
@@ -186,6 +186,6 @@ return {
}; };
})(); })();
(node as any).hash = "5f45ee7c58d50b0a6569c3bd22ffc83f"; (node as any).hash = "89702fbdf02294212269adb22efb65fe";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<53070e63046901877444d5de41cc8624>> * @generated SignedSource<<e628337d33e88ca5cfe6e45a55eebe32>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -396,6 +396,19 @@ return {
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"iam:membership:update\")" "storageKey": "permission(action:\"iam:membership:update\")"
},
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership-profile:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership-profile:delete\")"
} }
], ],
"storageKey": null "storageKey": null
@@ -427,19 +440,6 @@ return {
"name": "permission", "name": "permission",
"storageKey": "permission(action:\"iam:membership-profile:update\")" "storageKey": "permission(action:\"iam:membership-profile:update\")"
}, },
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership-profile:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership-profile:delete\")"
},
(v2/*: any*/) (v2/*: any*/)
], ],
"storageKey": null "storageKey": null
@@ -552,12 +552,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "d13993ce884669894f65925a714aeaa6", "cacheID": "4303ec6c2fa72181d747bb9053446aaa",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PeoplePageQuery", "name": "PeoplePageQuery",
"operationKind": "query", "operationKind": "query",
"text": "query PeoplePageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n canInviteUser: permission(action: \"iam:invitation:create\")\n profiles(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n }\n ...PeopleListFragment_8lnpd\n invitations(first: 20, orderBy: {direction: DESC, field: CREATED_AT}) {\n totalCount\n __typename\n }\n ...InvitationListFragment_1PypFi\n }\n id\n }\n}\n\nfragment InvitationListFragment_1PypFi on Organization {\n invitations(first: 20, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...InvitationListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment InvitationListItemFragment on Invitation {\n id\n fullName\n email\n role\n status\n createdAt\n acceptedAt\n canDelete: permission(action: \"iam:invitation:delete\")\n}\n\nfragment PeopleListFragment_8lnpd on Organization {\n profiles(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n fullName\n kind\n position\n membership {\n id\n role\n source\n state\n canUpdate: permission(action: \"iam:membership:update\")\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n}\n" "text": "query PeoplePageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n canInviteUser: permission(action: \"iam:invitation:create\")\n profiles(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n }\n ...PeopleListFragment_8lnpd\n invitations(first: 20, orderBy: {direction: DESC, field: CREATED_AT}) {\n totalCount\n __typename\n }\n ...InvitationListFragment_1PypFi\n }\n id\n }\n}\n\nfragment InvitationListFragment_1PypFi on Organization {\n invitations(first: 20, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...InvitationListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment InvitationListItemFragment on Invitation {\n id\n fullName\n email\n role\n status\n createdAt\n acceptedAt\n canDelete: permission(action: \"iam:invitation:delete\")\n}\n\nfragment PeopleListFragment_8lnpd on Organization {\n profiles(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n fullName\n kind\n position\n membership {\n id\n role\n source\n state\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<405fdde1b279cf7336ca395457041b93>> * @generated SignedSource<<2e75f19fcdaf2b78a187c33458c9cfe0>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -23,7 +23,7 @@ export type PersonPageQuery$data = {
}; };
readonly membership: { readonly membership: {
readonly id: string; readonly id: string;
} | null | undefined; };
readonly " $fragmentSpreads": FragmentRefs<"PersonFormFragment">; readonly " $fragmentSpreads": FragmentRefs<"PersonFormFragment">;
} | { } | {
// This will never be '%other', but we need some // This will never be '%other', but we need some
@@ -126,7 +126,11 @@ return {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
(v3/*: any*/), (v3/*: any*/),
(v5/*: any*/), {
"kind": "RequiredField",
"field": (v5/*: any*/),
"action": "THROW"
},
{ {
"kind": "RequiredField", "kind": "RequiredField",
"field": { "field": {
@@ -265,6 +269,6 @@ return {
}; };
})(); })();
(node as any).hash = "24863216f5484c1fae7ff6ad002a7f8c"; (node as any).hash = "5dce91fda9224f6f3928d4549485d3bd";
export default node; export default node;

View File

@@ -18,19 +18,22 @@ import { MembershipCard } from "./_components/MembershipCard";
export const membershipsPageQuery = graphql` export const membershipsPageQuery = graphql`
query MembershipsPageQuery { query MembershipsPageQuery {
viewer @required(action: THROW) { viewer @required(action: THROW) {
memberships( profiles(
first: 1000 first: 1000
orderBy: { direction: ASC, field: ORGANIZATION_NAME } orderBy: { direction: ASC, field: ORGANIZATION_NAME }
) )
@connection(key: "MembershipsPage_memberships") @connection(key: "MembershipsPage_profiles")
@required(action: THROW) { @required(action: THROW) {
__id __id
edges @required(action: THROW) { edges @required(action: THROW) {
node { node {
id id
membership @required(action: THROW) {
...MembershipCardFragment ...MembershipCardFragment
}
organization @required(action: THROW) { organization @required(action: THROW) {
name name
...MembershipCard_organizationFragment
} }
} }
} }
@@ -64,7 +67,7 @@ export function MembershipsPage(props: {
const { queryRef } = props; const { queryRef } = props;
const { const {
viewer: { viewer: {
memberships: { __id: membershipConnectionId, edges: initialMemberships }, profiles: { edges: initialProfiles },
pendingInvitations: { pendingInvitations: {
__id: pendingInvitationsConnectionId, __id: pendingInvitationsConnectionId,
edges: invitations, edges: invitations,
@@ -72,14 +75,14 @@ export function MembershipsPage(props: {
}, },
} = usePreloadedQuery<MembershipsPageQuery>(membershipsPageQuery, queryRef); } = usePreloadedQuery<MembershipsPageQuery>(membershipsPageQuery, queryRef);
const memberships = useMemo(() => { const profiles = useMemo(() => {
if (!search.trim()) { if (!search.trim()) {
return initialMemberships; return initialProfiles;
} }
return initialMemberships.filter(({ node }) => return initialProfiles.filter(({ node }) =>
node.organization.name.toLowerCase().includes(search.toLowerCase()), node.organization.name.toLowerCase().includes(search.toLowerCase()),
); );
}, [initialMemberships, search]); }, [initialProfiles, search]);
return ( return (
<> <>
@@ -98,14 +101,13 @@ export function MembershipsPage(props: {
pendingInvitationsConnectionId={ pendingInvitationsConnectionId={
pendingInvitationsConnectionId pendingInvitationsConnectionId
} }
membershipConnectionId={membershipConnectionId}
key={node.id} key={node.id}
fKey={node} fKey={node}
/> />
))} ))}
</div> </div>
)} )}
{initialMemberships.length > 0 && ( {initialProfiles.length > 0 && (
<div className="space-y-3"> <div className="space-y-3">
<h2 className="text-xl font-semibold"> <h2 className="text-xl font-semibold">
{__("Your organizations")} {__("Your organizations")}
@@ -118,15 +120,19 @@ export function MembershipsPage(props: {
onValueChange={setSearch} onValueChange={setSearch}
/> />
</div> </div>
{memberships.length === 0 {profiles.length === 0
? ( ? (
<div className="text-center text-txt-secondary py-4"> <div className="text-center text-txt-secondary py-4">
{__("No organizations found")} {__("No organizations found")}
</div> </div>
) )
: ( : (
memberships.map(({ node }) => ( profiles.map(({ node }) => (
<MembershipCard key={node.id} fKey={node} /> <MembershipCard
key={node.id}
fKey={node.membership}
organizationFragmentRef={node.organization}
/>
)) ))
)} )}
</div> </div>

View File

@@ -22,21 +22,17 @@ const fragment = graphql`
const acceptMutation = graphql` const acceptMutation = graphql`
mutation InvitationCardMutation( mutation InvitationCardMutation(
$input: AcceptInvitationInput! $input: AcceptInvitationInput!
$membershipConnections: [ID!]! # FIXME: profiles
# $membershipConnections: [ID!]!
$pendingInvitationConnections: [ID!]! $pendingInvitationConnections: [ID!]!
) { ) {
acceptInvitation(input: $input) { acceptInvitation(input: $input) {
invitation { invitation {
id @deleteEdge(connections: $pendingInvitationConnections) id @deleteEdge(connections: $pendingInvitationConnections)
} }
membershipEdge @prependEdge(connections: $membershipConnections) { membership {
node {
id id
...MembershipCardFragment ...MembershipCardFragment
organization {
name
}
}
} }
} }
} }
@@ -44,12 +40,11 @@ const acceptMutation = graphql`
interface InvitationCardProps { interface InvitationCardProps {
pendingInvitationsConnectionId: DataID; pendingInvitationsConnectionId: DataID;
membershipConnectionId: DataID;
fKey: InvitationCardFragment$key; fKey: InvitationCardFragment$key;
} }
export function InvitationCard(props: InvitationCardProps) { export function InvitationCard(props: InvitationCardProps) {
const { pendingInvitationsConnectionId, membershipConnectionId, fKey } const { pendingInvitationsConnectionId, fKey }
= props; = props;
const { __ } = useTranslate(); const { __ } = useTranslate();
@@ -67,7 +62,6 @@ export function InvitationCard(props: InvitationCardProps) {
invitationId: invitation.id, invitationId: invitation.id,
}, },
pendingInvitationConnections: [pendingInvitationsConnectionId], pendingInvitationConnections: [pendingInvitationsConnectionId],
membershipConnections: [membershipConnectionId],
}, },
onCompleted: (_, e) => { onCompleted: (_, e) => {
if (e) { if (e) {

View File

@@ -13,6 +13,7 @@ import { useFragment } from "react-relay";
import { Link } from "react-router"; import { Link } from "react-router";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import type { MembershipCard_organizationFragment$key } from "#/__generated__/iam/MembershipCard_organizationFragment.graphql";
import type { MembershipCardFragment$key } from "#/__generated__/iam/MembershipCardFragment.graphql"; import type { MembershipCardFragment$key } from "#/__generated__/iam/MembershipCardFragment.graphql";
const fragment = graphql` const fragment = graphql`
@@ -21,26 +22,34 @@ const fragment = graphql`
id id
expiresAt expiresAt
} }
organization @required(action: THROW) { }
`;
const organizationFragment = graphql`
fragment MembershipCard_organizationFragment on Organization {
id id
name name
logoUrl logoUrl
} }
}
`; `;
interface MembershipCardProps { interface MembershipCardProps {
fKey: MembershipCardFragment$key; fKey: MembershipCardFragment$key;
organizationFragmentRef: MembershipCard_organizationFragment$key;
} }
export function MembershipCard(props: MembershipCardProps) { export function MembershipCard(props: MembershipCardProps) {
const { fKey } = props; const { fKey, organizationFragmentRef } = props;
const { __ } = useTranslate(); const { __ } = useTranslate();
const { lastSession, organization } = useFragment<MembershipCardFragment$key>( const { lastSession } = useFragment<MembershipCardFragment$key>(
fragment, fragment,
fKey, fKey,
); );
const organization = useFragment<MembershipCard_organizationFragment$key>(
organizationFragment,
organizationFragmentRef,
);
const isExpired const isExpired
= lastSession && parseDate(lastSession.expiresAt) < new Date(); = lastSession && parseDate(lastSession.expiresAt) < new Date();
const isAssuming = !!lastSession && !isExpired; const isAssuming = !!lastSession && !isExpired;

View File

@@ -23,10 +23,10 @@ export const viewerMembershipLayoutQuery = graphql`
...MembershipsDropdown_organizationFragment ...MembershipsDropdown_organizationFragment
...ViewerMembershipDropdownFragment ...ViewerMembershipDropdownFragment
...SidebarFragment @skip(if: $hideSidebar) ...SidebarFragment @skip(if: $hideSidebar)
viewerMembership @required(action: THROW) { viewer @required(action: THROW) {
role
profile @required(action: THROW) {
fullName fullName
membership @required(action: THROW) {
role
} }
} }
} }
@@ -91,11 +91,11 @@ export function ViewerMembershipLayout(props: {
<CurrentUser <CurrentUser
value={{ value={{
email: viewer.email, email: viewer.email,
fullName: organization.viewerMembership.profile.fullName, fullName: organization.viewer.fullName,
role: organization.viewerMembership.role, role: organization.viewer.membership.role,
}} }}
> >
<Outlet context={organization.viewerMembership.role} /> <Outlet context={organization.viewer.membership.role} />
</CurrentUser> </CurrentUser>
</CoreRelayProvider> </CoreRelayProvider>
</Layout> </Layout>

View File

@@ -8,7 +8,7 @@ import { MembershipsDropdownMenuItem } from "./MembershipsDropdownMenuItem";
export const membershipsDropdownMenuQuery = graphql` export const membershipsDropdownMenuQuery = graphql`
query MembershipsDropdownMenuQuery { query MembershipsDropdownMenuQuery {
viewer @required(action: THROW) { viewer @required(action: THROW) {
memberships( profiles(
first: 1000 first: 1000
orderBy: { direction: ASC, field: ORGANIZATION_NAME } orderBy: { direction: ASC, field: ORGANIZATION_NAME }
) @required(action: THROW) { ) @required(action: THROW) {
@@ -17,13 +17,16 @@ export const membershipsDropdownMenuQuery = graphql`
id id
organization @required(action: THROW) { organization @required(action: THROW) {
name name
...MembershipsDropdownMenuItem_organizationFragment
} }
membership @required(action: THROW) {
...MembershipsDropdownMenuItemFragment ...MembershipsDropdownMenuItemFragment
} }
} }
} }
} }
} }
}
`; `;
interface MembershipsDropdownMenuProps { interface MembershipsDropdownMenuProps {
@@ -36,27 +39,27 @@ export function MembershipsDropdownMenu(props: MembershipsDropdownMenuProps) {
const { const {
viewer: { viewer: {
memberships: { edges: initialMemberships }, profiles: { edges: initialProfiles },
}, },
} = usePreloadedQuery<MembershipsDropdownMenuQuery>( } = usePreloadedQuery<MembershipsDropdownMenuQuery>(
membershipsDropdownMenuQuery, membershipsDropdownMenuQuery,
queryRef, queryRef,
); );
const memberships = useMemo(() => { const profiles = useMemo(() => {
if (!search) { if (!search) {
return initialMemberships; return initialProfiles;
} }
return initialMemberships.filter(({ node: { organization } }) => return initialProfiles.filter(({ node: { organization } }) =>
organization.name.toLowerCase().includes(search.toLowerCase()), organization.name.toLowerCase().includes(search.toLowerCase()),
); );
}, [initialMemberships, search]); }, [initialProfiles, search]);
return ( return (
<> <>
{memberships.map(({ node }) => ( {profiles.map(({ node }) => (
<MembershipsDropdownMenuItem fKey={node} key={node.id} /> <MembershipsDropdownMenuItem fKey={node.membership} organizationFragmentRef={node.organization} key={node.id} />
))} ))}
</> </>
); );

View File

@@ -10,6 +10,7 @@ import { useFragment } from "react-relay";
import { Link } from "react-router"; import { Link } from "react-router";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import type { MembershipsDropdownMenuItem_organizationFragment$key } from "#/__generated__/iam/MembershipsDropdownMenuItem_organizationFragment.graphql";
import type { MembershipsDropdownMenuItemFragment$key } from "#/__generated__/iam/MembershipsDropdownMenuItemFragment.graphql"; import type { MembershipsDropdownMenuItemFragment$key } from "#/__generated__/iam/MembershipsDropdownMenuItemFragment.graphql";
const fragment = graphql` const fragment = graphql`
@@ -19,21 +20,27 @@ const fragment = graphql`
id id
expiresAt expiresAt
} }
organization @required(action: THROW) {
id
logoUrl
name
} }
`;
const organizationFragment = graphql`
fragment MembershipsDropdownMenuItem_organizationFragment on Organization {
id
name
logoUrl
} }
`; `;
export function MembershipsDropdownMenuItem(props: { export function MembershipsDropdownMenuItem(props: {
fKey: MembershipsDropdownMenuItemFragment$key; fKey: MembershipsDropdownMenuItemFragment$key;
organizationFragmentRef: MembershipsDropdownMenuItem_organizationFragment$key;
}) { }) {
const { fKey } = props; const { fKey, organizationFragmentRef } = props;
const { id, lastSession, organization } const { id, lastSession }
= useFragment<MembershipsDropdownMenuItemFragment$key>(fragment, fKey); = useFragment<MembershipsDropdownMenuItemFragment$key>(fragment, fKey);
const organization
= useFragment<MembershipsDropdownMenuItem_organizationFragment$key>(organizationFragment, organizationFragmentRef);
const isAssuming = !!lastSession; const isAssuming = !!lastSession;
const isExpired const isExpired

View File

@@ -14,18 +14,17 @@ import { useFragment, useMutation } from "react-relay";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import type { ViewerMembershipDropdownFragment$key } from "#/__generated__/iam/ViewerMembershipDropdownFragment.graphql"; import type { ViewerMembershipDropdownFragment$key } from "#/__generated__/iam/ViewerMembershipDropdownFragment.graphql";
import type { ViewerMembershipDropdownSignOutMutation } from "#/__generated__/iam/ViewerMembershipDropdownSignOutMutation.graphql";
import { useOrganizationId } from "#/hooks/useOrganizationId"; import { useOrganizationId } from "#/hooks/useOrganizationId";
export const fragment = graphql` export const fragment = graphql`
fragment ViewerMembershipDropdownFragment on Organization { fragment ViewerMembershipDropdownFragment on Organization {
viewerMembership @required(action: THROW) { viewer @required(action: THROW) {
fullName
identity @required(action: THROW) { identity @required(action: THROW) {
email email
canListAPIKeys: permission(action: "iam:personal-api-key:list") canListAPIKeys: permission(action: "iam:personal-api-key:list")
} }
profile @required(action: THROW) {
fullName
}
} }
} }
`; `;
@@ -48,12 +47,12 @@ export function ViewerMembershipDropdown(props: {
const { toast } = useToast(); const { toast } = useToast();
const { const {
viewerMembership: { viewer: {
fullName,
identity: { canListAPIKeys, email }, identity: { canListAPIKeys, email },
profile: { fullName },
}, },
} = useFragment<ViewerMembershipDropdownFragment$key>(fragment, fKey); } = useFragment<ViewerMembershipDropdownFragment$key>(fragment, fKey);
const [signOut] = useMutation(signOutMutation); const [signOut] = useMutation<ViewerMembershipDropdownSignOutMutation>(signOutMutation);
const handleLogout: React.MouseEventHandler<HTMLAnchorElement> = (e) => { const handleLogout: React.MouseEventHandler<HTMLAnchorElement> = (e) => {
e.preventDefault(); e.preventDefault();

View File

@@ -17,7 +17,7 @@ export const peoplePageQuery = graphql`
__typename __typename
... on Organization { ... on Organization {
canInviteUser: permission(action: "iam:invitation:create") canInviteUser: permission(action: "iam:invitation:create")
members(first: 20, orderBy: { direction: ASC, field: FULL_NAME }) profiles(first: 20, orderBy: { direction: ASC, field: FULL_NAME })
@required(action: THROW) { @required(action: THROW) {
totalCount totalCount
} }
@@ -50,8 +50,8 @@ export function PeoplePage(props: {
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { __ } = useTranslate(); const { __ } = useTranslate();
const [activeTab, setActiveTab] = useState<"memberships" | "invitations">( const [activeTab, setActiveTab] = useState<"people" | "invitations">(
"memberships", "people",
); );
const { organization } = usePreloadedQuery<PeoplePageQuery>( const { organization } = usePreloadedQuery<PeoplePageQuery>(
@@ -85,12 +85,12 @@ export function PeoplePage(props: {
<Tabs> <Tabs>
<TabItem <TabItem
active={activeTab === "memberships"} active={activeTab === "people"}
onClick={() => setActiveTab("memberships")} onClick={() => setActiveTab("people")}
> >
{__("Members")} {__("People")}
{(organization.members.totalCount ?? 0) > 0 && ( {(organization.profiles.totalCount ?? 0) > 0 && (
<TabBadge>{organization.members.totalCount}</TabBadge> <TabBadge>{organization.profiles.totalCount}</TabBadge>
)} )}
</TabItem> </TabItem>
<TabItem <TabItem
@@ -105,7 +105,7 @@ export function PeoplePage(props: {
</Tabs> </Tabs>
<div className="pb-6 pt-6"> <div className="pb-6 pt-6">
{activeTab === "memberships" && <PeopleList fKey={organization} />} {activeTab === "people" && <PeopleList fKey={organization} />}
{activeTab === "invitations" && ( {activeTab === "invitations" && (
<InvitationList <InvitationList

View File

@@ -15,9 +15,11 @@ export const personPageQuery = graphql`
query PersonPageQuery($personId: ID!) { query PersonPageQuery($personId: ID!) {
person: node(id: $personId) @required(action: THROW) { person: node(id: $personId) @required(action: THROW) {
__typename __typename
... on MembershipProfile { ... on Profile {
fullName fullName
membershipId membership @required(action: THROW) {
id
}
identity @required(action: THROW) { identity @required(action: THROW) {
email email
} }
@@ -47,7 +49,7 @@ export function PersonPage(props: { queryRef: PreloadedQuery<PersonPageQuery> })
const navigate = useNavigate(); const navigate = useNavigate();
const { person } = usePreloadedQuery<PersonPageQuery>(personPageQuery, queryRef); const { person } = usePreloadedQuery<PersonPageQuery>(personPageQuery, queryRef);
if (person.__typename !== "MembershipProfile") { if (person.__typename !== "Profile") {
throw new Error("invalid type for node"); throw new Error("invalid type for node");
} }
@@ -65,7 +67,7 @@ export function PersonPage(props: { queryRef: PreloadedQuery<PersonPageQuery> })
return removeMembership({ return removeMembership({
variables: { variables: {
input: { input: {
membershipId: person.membershipId, membershipId: person.membership.id,
organizationId: organizationId, organizationId: organizationId,
}, },
}, },

View File

@@ -15,20 +15,20 @@ const fragment = graphql`
@argumentDefinitions( @argumentDefinitions(
first: { type: "Int", defaultValue: 20 } first: { type: "Int", defaultValue: 20 }
order: { order: {
type: "MembershipOrder" type: "ProfileOrder"
defaultValue: { direction: ASC, field: FULL_NAME } defaultValue: { direction: ASC, field: FULL_NAME }
} }
after: { type: "CursorKey", defaultValue: null } after: { type: "CursorKey", defaultValue: null }
before: { type: "CursorKey", defaultValue: null } before: { type: "CursorKey", defaultValue: null }
last: { type: "Int", defaultValue: null } last: { type: "Int", defaultValue: null }
) { ) {
members( profiles(
first: $first first: $first
after: $after after: $after
last: $last last: $last
before: $before before: $before
orderBy: $order orderBy: $order
) @connection(key: "PeopleListFragment_members", filters: ["orderBy"]) @required(action: THROW) { ) @connection(key: "PeopleListFragment_profiles", filters: ["orderBy"]) @required(action: THROW) {
__id __id
totalCount totalCount
edges @required(action: THROW) { edges @required(action: THROW) {
@@ -51,7 +51,7 @@ export function PeopleList(props: { fKey: PeopleListFragment$key }) {
PeopleListFragment$key PeopleListFragment$key
>(fragment, fKey); >(fragment, fKey);
const refetchMemberships = () => { const refetchPeople = () => {
peoplePagination.refetch({}, { fetchPolicy: "network-only" }); peoplePagination.refetch({}, { fetchPolicy: "network-only" });
}; };
@@ -77,21 +77,21 @@ export function PeopleList(props: { fKey: PeopleListFragment$key }) {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{peoplePagination.data.members.totalCount === 0 {peoplePagination.data.profiles.totalCount === 0
? ( ? (
<Tr> <Tr>
<Td colSpan={7} className="text-center text-txt-secondary"> <Td colSpan={7} className="text-center text-txt-secondary">
{__("No members")} {__("No people")}
</Td> </Td>
</Tr> </Tr>
) )
: ( : (
peoplePagination.data.members.edges.map(({ node: membership }) => ( peoplePagination.data.profiles.edges.map(({ node: profile }) => (
<MemberListItem <MemberListItem
connectionId={peoplePagination.data.members.__id} connectionId={peoplePagination.data.profiles.__id}
key={membership.id} key={profile.id}
fKey={membership} fKey={profile}
onRefetch={refetchMemberships} onRefetch={refetchPeople}
/> />
)) ))
)} )}

View File

@@ -22,23 +22,24 @@ import { useOrganizationId } from "#/hooks/useOrganizationId";
import { EditMemberDialog } from "../../settings/_components/EditMemberDialog"; import { EditMemberDialog } from "../../settings/_components/EditMemberDialog";
const fragment = graphql` const fragment = graphql`
fragment PeopleListItemFragment on Membership { fragment PeopleListItemFragment on Profile {
id
role
source
state
profile @required(action: THROW) {
id id
fullName fullName
kind kind
position position
membership @required(action: THROW) {
id
role
source
state
canUpdate: permission(action: "iam:membership:update")
canDelete: permission(action: "iam:membership-profile:delete")
} }
identity @required(action: THROW) { identity @required(action: THROW) {
email email
} }
createdAt createdAt
canUpdate: permission(action: "iam:membership:update") canUpdate: permission(action: "iam:membership-profile:update")
canDelete: permission(action: "iam:membership:delete")
} }
`; `;
@@ -65,9 +66,9 @@ export function MemberListItem(props: {
const confirm = useConfirm(); const confirm = useConfirm();
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
const membership = useFragment<PeopleListItemFragment$key>(fragment, fKey); const profile = useFragment<PeopleListItemFragment$key>(fragment, fKey);
const isInactive = membership.state === "INACTIVE"; const isInactive = profile.membership.state === "INACTIVE";
const [removeMembership, isRemoving] = useMutationWithToasts( const [removeMembership, isRemoving] = useMutationWithToasts(
removeMemberMutation, removeMemberMutation,
@@ -83,7 +84,7 @@ export function MemberListItem(props: {
return removeMembership({ return removeMembership({
variables: { variables: {
input: { input: {
membershipId: membership.id, membershipId: profile.membership.id,
organizationId: organizationId, organizationId: organizationId,
}, },
connections: [connectionId], connections: [connectionId],
@@ -93,7 +94,7 @@ export function MemberListItem(props: {
{ {
message: sprintf( message: sprintf(
__("Are you sure you want to remove %s?"), __("Are you sure you want to remove %s?"),
membership.profile.fullName, profile.fullName,
), ),
}, },
); );
@@ -106,33 +107,33 @@ export function MemberListItem(props: {
isRemoving && "opacity-60 pointer-events-none", isRemoving && "opacity-60 pointer-events-none",
isInactive && "opacity-50", isInactive && "opacity-50",
)} )}
to={`/organizations/${organizationId}/people/${membership.profile.id}`} to={`/organizations/${organizationId}/people/${profile.id}`}
> >
<Td> <Td>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="font-semibold">{membership.profile.fullName}</span> <span className="font-semibold">{profile.fullName}</span>
{isInactive && <Badge variant="neutral">{__("Inactive")}</Badge>} {isInactive && <Badge variant="neutral">{__("Inactive")}</Badge>}
</div> </div>
</Td> </Td>
<Td> <Td>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{membership.identity.email} {profile.identity.email}
<Badge variant="info">{membership.source}</Badge> <Badge variant="info">{profile.membership.source}</Badge>
</div> </div>
</Td> </Td>
<Td>{membership.profile.kind}</Td> <Td>{profile.kind}</Td>
<Td> <Td>
<Badge>{membership.role}</Badge> <Badge>{profile.membership.role}</Badge>
</Td> </Td>
<Td>{new Date(membership.createdAt).toLocaleDateString()}</Td> <Td>{new Date(profile.createdAt).toLocaleDateString()}</Td>
<Td>{membership.profile.position}</Td> <Td>{profile.position}</Td>
<Td noLink width={160} className="text-end"> <Td noLink width={160} className="text-end">
{!isInactive && ( {!isInactive && (
<div <div
className="flex gap-2 justify-end" className="flex gap-2 justify-end"
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
> >
{membership.canUpdate && ( {profile.membership.canUpdate && (
<Button <Button
variant="secondary" variant="secondary"
onClick={() => setDialogOpen(true)} onClick={() => setDialogOpen(true)}
@@ -146,7 +147,7 @@ export function MemberListItem(props: {
<Spinner size={16} /> <Spinner size={16} />
) )
: ( : (
membership.canDelete && ( profile.membership.canDelete && (
<Button <Button
variant="danger" variant="danger"
onClick={handleRemove} onClick={handleRemove}
@@ -164,7 +165,7 @@ export function MemberListItem(props: {
{dialogOpen && ( {dialogOpen && (
<EditMemberDialog <EditMemberDialog
onClose={() => setDialogOpen(false)} onClose={() => setDialogOpen(false)}
membership={membership} profile={profile}
/> />
)} )}
</> </>

View File

@@ -13,7 +13,7 @@ import { useFormWithSchema } from "#/hooks/useFormWithSchema";
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
const fragment = graphql` const fragment = graphql`
fragment PersonFormFragment on MembershipProfile { fragment PersonFormFragment on Profile {
id id
fullName fullName
kind kind

View File

@@ -30,10 +30,10 @@ const updateMembershipMutation = graphql`
`; `;
export function EditMemberDialog(props: { export function EditMemberDialog(props: {
membership: PeopleListItemFragment$data; profile: PeopleListItemFragment$data;
onClose: () => void; onClose: () => void;
}) { }) {
const { membership, onClose } = props; const { profile, onClose } = props;
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { __ } = useTranslate(); const { __ } = useTranslate();
@@ -41,7 +41,7 @@ export function EditMemberDialog(props: {
const { role } = use(CurrentUser); const { role } = use(CurrentUser);
const availableRoles = getAssignableRoles(role); const availableRoles = getAssignableRoles(role);
const [selectedRole, setSelectedRole] = useState<string>(membership.role); const [selectedRole, setSelectedRole] = useState<string>(profile.membership.role);
const [updateMembership, isUpdating] = useMutationWithToasts( const [updateMembership, isUpdating] = useMutationWithToasts(
updateMembershipMutation, updateMembershipMutation,
@@ -56,7 +56,7 @@ export function EditMemberDialog(props: {
await updateMembership({ await updateMembership({
variables: { variables: {
input: { input: {
membershipId: membership.id, membershipId: profile.membership.id,
organizationId: organizationId, organizationId: organizationId,
role: selectedRole, role: selectedRole,
}, },
@@ -73,7 +73,7 @@ export function EditMemberDialog(props: {
<p className="text-txt-secondary text-sm mb-4"> <p className="text-txt-secondary text-sm mb-4">
{sprintf( {sprintf(
__("Update the role for %s"), __("Update the role for %s"),
membership.profile.fullName, profile.fullName,
)} )}
</p> </p>
@@ -121,7 +121,7 @@ export function EditMemberDialog(props: {
<DialogFooter> <DialogFooter>
<Button <Button
type="submit" type="submit"
disabled={isUpdating || selectedRole === membership.role} disabled={isUpdating || selectedRole === profile.membership.role}
> >
{isUpdating && <Spinner />} {isUpdating && <Spinner />}
{__("Update Role")} {__("Update Role")}

View File

@@ -8,20 +8,16 @@ import type { SCIMEventListItemFragment$key } from "#/__generated__/iam/SCIMEven
const SCIMEventListItemFragment = graphql` const SCIMEventListItemFragment = graphql`
fragment SCIMEventListItemFragment on SCIMEvent { fragment SCIMEventListItemFragment on SCIMEvent {
id
method method
path path
statusCode statusCode
errorMessage errorMessage
ipAddress ipAddress
createdAt createdAt
membership {
id
profile { profile {
fullName fullName
} }
} }
}
`; `;
const getResultBadge = (statusCode: number) => { const getResultBadge = (statusCode: number) => {
@@ -93,7 +89,8 @@ export function SCIMEventListItem(props: {
<div className="flex gap-8 text-sm"> <div className="flex gap-8 text-sm">
<div> <div>
<span className="text-txt-secondary">User: </span> <span className="text-txt-secondary">User: </span>
<span>{event.membership?.profile?.fullName || "-"}</span> {/* FIXME */}
{/* <span>{event.membership?.profile?.fullName || "-"}</span> */}
</div> </div>
<div> <div>
<span className="text-txt-secondary">IP Address: </span> <span className="text-txt-secondary">IP Address: </span>

View File

@@ -736,13 +736,10 @@ WHERE
args := pgx.StrictNamedArgs{"identity_id": identityID} args := pgx.StrictNamedArgs{"identity_id": identityID}
maps.Copy(args, filter.SQLArguments()) maps.Copy(args, filter.SQLArguments())
rows, err := conn.Query(ctx, q, args) row := conn.QueryRow(ctx, q, args)
if err != nil {
return 0, fmt.Errorf("cannot query iam_membership_profiles: %w", err)
}
var count int var count int
err = rows.Scan(&count) err := row.Scan(&count)
if err != nil { if err != nil {
return 0, fmt.Errorf("cannot collect count: %w", err) return 0, fmt.Errorf("cannot collect count: %w", err)
} }
@@ -774,13 +771,10 @@ WHERE
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments()) maps.Copy(args, filter.SQLArguments())
rows, err := conn.Query(ctx, q, args) row := conn.QueryRow(ctx, q, args)
if err != nil {
return 0, fmt.Errorf("cannot query iam_membership_profiles: %w", err)
}
var count int var count int
err = rows.Scan(&count) err := row.Scan(&count)
if err != nil { if err != nil {
return 0, fmt.Errorf("cannot collect count: %w", err) return 0, fmt.Errorf("cannot collect count: %w", err)
} }

View File

@@ -38,6 +38,8 @@ func (r *identityResolver) Profiles(ctx context.Context, obj *types.Identity, fi
return nil, err return nil, err
} }
filter := coredata.NewMembershipProfileFilter(nil)
if gqlutils.OnlyTotalCountSelected(ctx) { if gqlutils.OnlyTotalCountSelected(ctx) {
return &types.ProfileConnection{ return &types.ProfileConnection{
Resolver: r, Resolver: r,
@@ -45,8 +47,6 @@ func (r *identityResolver) Profiles(ctx context.Context, obj *types.Identity, fi
}, nil }, nil
} }
filter := coredata.NewMembershipProfileFilter(nil)
pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{
Field: coredata.MembershipProfileOrderFieldFullName, Field: coredata.MembershipProfileOrderFieldFullName,
Direction: page.OrderDirectionAsc, Direction: page.OrderDirectionAsc,
@@ -1247,6 +1247,8 @@ func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organiza
return nil, err return nil, err
} }
filter := coredata.NewMembershipProfileFilter(nil)
if gqlutils.OnlyTotalCountSelected(ctx) { if gqlutils.OnlyTotalCountSelected(ctx) {
return &types.ProfileConnection{ return &types.ProfileConnection{
Resolver: r, Resolver: r,
@@ -1254,8 +1256,6 @@ func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organiza
}, nil }, nil
} }
filter := coredata.NewMembershipProfileFilter(nil)
pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{
Field: coredata.MembershipProfileOrderFieldFullName, Field: coredata.MembershipProfileOrderFieldFullName,
Direction: page.OrderDirectionAsc, Direction: page.OrderDirectionAsc,
@@ -1514,7 +1514,7 @@ func (r *profileConnectionResolver) TotalCount(ctx context.Context, obj *types.P
case *identityResolver: case *identityResolver:
count, err := r.iam.AccountService.CountProfiles(ctx, obj.ParentID, coredata.NewMembershipProfileFilter(nil)) count, err := r.iam.AccountService.CountProfiles(ctx, obj.ParentID, coredata.NewMembershipProfileFilter(nil))
if err != nil { if err != nil {
r.logger.ErrorCtx(ctx, "cannot count sprofiles", log.Error(err)) r.logger.ErrorCtx(ctx, "cannot count profiles", log.Error(err))
return nil, gqlutils.Internal(ctx) return nil, gqlutils.Internal(ctx)
} }
return &count, nil return &count, nil

View File

@@ -6576,7 +6576,7 @@ func (r *profileConnectionResolver) TotalCount(ctx context.Context, obj *types.P
} }
switch obj.Resolver.(type) { switch obj.Resolver.(type) {
case *stateOfApplicabilityResolver: case *organizationResolver:
count, err := r.iam.OrganizationService.CountProfiles(ctx, obj.ParentID, obj.Filters) count, err := r.iam.OrganizationService.CountProfiles(ctx, obj.ParentID, obj.Filters)
if err != nil { if err != nil {
panic(fmt.Errorf("cannot count profiles: %w", err)) panic(fmt.Errorf("cannot count profiles: %w", err))