Rework console pages and implement profile update
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<10bf686b430d7a736370638d91ba0b58>>
|
||||
* @generated SignedSource<<d9088a212dfa65ed0a833670cf8bc6d4>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -9,7 +9,7 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type MembershipProfileKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT";
|
||||
export type ProfileKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type PeopleGraphPaginatedFragment$data = {
|
||||
readonly canCreatePeople: boolean;
|
||||
@@ -26,7 +26,7 @@ export type PeopleGraphPaginatedFragment$data = {
|
||||
readonly emailAddress: string;
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
readonly kind: MembershipProfileKind;
|
||||
readonly kind: ProfileKind;
|
||||
readonly position: string | null | undefined;
|
||||
};
|
||||
}>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<6205161c89403b10363ea54bbb10ccea>>
|
||||
* @generated SignedSource<<07e8369de4150113311dccdc51e4d1da>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -251,6 +251,7 @@ return {
|
||||
"name": "profile",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v12/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -258,7 +259,20 @@ return {
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
(v12/*: any*/)
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "kind",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "position",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
@@ -403,12 +417,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "8089993871afc0b3d2c0defd9182c31a",
|
||||
"cacheID": "8a5e322c63c4dfa1c5528d50638ec6cc",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "MemberListFragment_RefetchQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query MemberListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: MembershipOrder = {direction: ASC, field: FULL_NAME}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...MemberListFragment_16fISc\n id\n }\n}\n\nfragment MemberListFragment_16fISc on Organization {\n members(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n source\n state\n profile {\n fullName\n id\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n"
|
||||
"text": "query MemberListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: MembershipOrder = {direction: ASC, field: FULL_NAME}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...MemberListFragment_16fISc\n id\n }\n}\n\nfragment MemberListFragment_16fISc on Organization {\n members(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n source\n state\n profile {\n id\n fullName\n kind\n position\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<81a5acfba89528014adf0731f16b9a90>>
|
||||
* @generated SignedSource<<770fb773083cc4f819594e268d9eb0cc>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -12,6 +12,7 @@ import { ReaderFragment } from 'relay-runtime';
|
||||
export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER";
|
||||
export type MembershipSource = "MANUAL" | "SAML" | "SCIM";
|
||||
export type MembershipState = "ACTIVE" | "INACTIVE";
|
||||
export type ProfileKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type MemberListItemFragment$data = {
|
||||
readonly canDelete: boolean;
|
||||
@@ -23,6 +24,9 @@ export type MemberListItemFragment$data = {
|
||||
};
|
||||
readonly profile: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
readonly kind: ProfileKind;
|
||||
readonly position: string | null | undefined;
|
||||
};
|
||||
readonly role: MembershipRole;
|
||||
readonly source: MembershipSource;
|
||||
@@ -34,19 +38,21 @@ export type MemberListItemFragment$key = {
|
||||
readonly " $fragmentSpreads": FragmentRefs<"MemberListItemFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = {
|
||||
const node: ReaderFragment = (function(){
|
||||
var v0 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "MemberListItemFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -78,12 +84,27 @@ const node: ReaderFragment = {
|
||||
"name": "profile",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "kind",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "position",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
@@ -149,7 +170,8 @@ const node: ReaderFragment = {
|
||||
"type": "Membership",
|
||||
"abstractKey": null
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "ffa3431077faeeb5e5779975d68c82bb";
|
||||
(node as any).hash = "378a4cdc212c16aecae368d41d80739e";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<11a6b18e62a7a93fa6c2365a7e3a86f6>>
|
||||
* @generated SignedSource<<9a67aa27afef79bc9334f341f01d986d>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -368,8 +368,22 @@ return {
|
||||
"name": "profile",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v10/*: any*/),
|
||||
(v12/*: any*/),
|
||||
(v10/*: any*/)
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "kind",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "position",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
@@ -532,12 +546,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "438ac1a112e204b2a9ff30e3b36ec3ae",
|
||||
"cacheID": "761152b197433a0f5f9e0734f98723d8",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "MembersPageQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query MembersPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n canInviteUser: permission(action: \"iam:invitation:create\")\n ...MemberListFragment_8lnpd\n members(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n }\n ...InvitationListFragment_1PypFi\n invitations(first: 20, orderBy: {direction: DESC, field: CREATED_AT}) {\n totalCount\n __typename\n }\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 expiresAt\n acceptedAt\n canDelete: permission(action: \"iam:invitation:delete\")\n}\n\nfragment MemberListFragment_8lnpd on Organization {\n members(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n source\n state\n profile {\n fullName\n id\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n"
|
||||
"text": "query MembersPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n canInviteUser: permission(action: \"iam:invitation:create\")\n ...MemberListFragment_8lnpd\n members(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n }\n ...InvitationListFragment_1PypFi\n invitations(first: 20, orderBy: {direction: DESC, field: CREATED_AT}) {\n totalCount\n __typename\n }\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 expiresAt\n acceptedAt\n canDelete: permission(action: \"iam:invitation:delete\")\n}\n\nfragment MemberListFragment_8lnpd on Organization {\n members(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n source\n state\n profile {\n id\n fullName\n kind\n position\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<ff50473cdcfc65676ac6e50da0ecd9f7>>
|
||||
* @generated SignedSource<<de9ca0854fde114ecd6fc68643f999d3>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -20,6 +20,7 @@ export type SidebarFragment$data = {
|
||||
readonly canListFrameworks: boolean;
|
||||
readonly canListMeasures: boolean;
|
||||
readonly canListMeetings: boolean;
|
||||
readonly canListMembers: boolean;
|
||||
readonly canListNonconformities: boolean;
|
||||
readonly canListObligations: boolean;
|
||||
readonly canListProcessingActivities: boolean;
|
||||
@@ -108,6 +109,19 @@ const node: ReaderFragment = {
|
||||
"name": "permission",
|
||||
"storageKey": "permission(action:\"core:framework:list\")"
|
||||
},
|
||||
{
|
||||
"alias": "canListMembers",
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "action",
|
||||
"value": "iam:membership:list"
|
||||
}
|
||||
],
|
||||
"kind": "ScalarField",
|
||||
"name": "permission",
|
||||
"storageKey": "permission(action:\"iam:membership:list\")"
|
||||
},
|
||||
{
|
||||
"alias": "canListVendors",
|
||||
"args": [
|
||||
@@ -295,6 +309,6 @@ const node: ReaderFragment = {
|
||||
"abstractKey": null
|
||||
};
|
||||
|
||||
(node as any).hash = "b5d26b301116eb710832a55c425ed168";
|
||||
(node as any).hash = "73a632deb40d322a670cd72fd855723f";
|
||||
|
||||
export default node;
|
||||
|
||||
105
apps/console/src/__generated__/iam/UserFormFragment.graphql.ts
generated
Normal file
105
apps/console/src/__generated__/iam/UserFormFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @generated SignedSource<<a74d82c45ea0f57774194b3acb02227e>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type ProfileKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type UserFormFragment$data = {
|
||||
readonly additionalEmailAddresses: ReadonlyArray<string>;
|
||||
readonly canUpdate: boolean;
|
||||
readonly contractEndDate: string | null | undefined;
|
||||
readonly contractStartDate: string | null | undefined;
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
readonly kind: ProfileKind;
|
||||
readonly position: string | null | undefined;
|
||||
readonly " $fragmentType": "UserFormFragment";
|
||||
};
|
||||
export type UserFormFragment$key = {
|
||||
readonly " $data"?: UserFormFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"UserFormFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "UserFormFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "kind",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "position",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "additionalEmailAddresses",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "contractStartDate",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "contractEndDate",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "canUpdate",
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "action",
|
||||
"value": "iam:membership-profile:update"
|
||||
}
|
||||
],
|
||||
"kind": "ScalarField",
|
||||
"name": "permission",
|
||||
"storageKey": "permission(action:\"iam:membership-profile:update\")"
|
||||
}
|
||||
],
|
||||
"type": "MembershipProfile",
|
||||
"abstractKey": null
|
||||
};
|
||||
|
||||
(node as any).hash = "b7c1eb9f3703338d5d0fb4c5411bf2eb";
|
||||
|
||||
export default node;
|
||||
112
apps/console/src/__generated__/iam/UserFormMutation.graphql.ts
generated
Normal file
112
apps/console/src/__generated__/iam/UserFormMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* @generated SignedSource<<7a6d038560759279a60a293bc4be5bb8>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type ProfileKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT";
|
||||
export type UpdateProfileInput = {
|
||||
additionalEmailAddresses?: ReadonlyArray<string> | null | undefined;
|
||||
contractEndDate?: string | null | undefined;
|
||||
contractStartDate?: string | null | undefined;
|
||||
fullName: string;
|
||||
id: string;
|
||||
kind: ProfileKind;
|
||||
position?: string | null | undefined;
|
||||
};
|
||||
export type UserFormMutation$variables = {
|
||||
input: UpdateProfileInput;
|
||||
};
|
||||
export type UserFormMutation$data = {
|
||||
readonly updateProfile: {
|
||||
readonly profile: {
|
||||
readonly id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
export type UserFormMutation = {
|
||||
response: UserFormMutation$data;
|
||||
variables: UserFormMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "UpdateProfilePayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "updateProfile",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MembershipProfile",
|
||||
"kind": "LinkedField",
|
||||
"name": "profile",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "UserFormMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "UserFormMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "edb3d0ac8f6a87857c470114ff6ad7f4",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "UserFormMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation UserFormMutation(\n $input: UpdateProfileInput!\n) {\n updateProfile(input: $input) {\n profile {\n id\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "6ae1b8fd927611dd3b3b530ccd1de7d1";
|
||||
|
||||
export default node;
|
||||
263
apps/console/src/__generated__/iam/UserPageQuery.graphql.ts
generated
Normal file
263
apps/console/src/__generated__/iam/UserPageQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* @generated SignedSource<<10ec7c14d725880998dcd5ba2b99004e>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type UserPageQuery$variables = {
|
||||
userId: string;
|
||||
};
|
||||
export type UserPageQuery$data = {
|
||||
readonly user: {
|
||||
readonly __typename: "MembershipProfile";
|
||||
readonly canDelete: boolean;
|
||||
readonly fullName: string;
|
||||
readonly identity: {
|
||||
readonly email: string;
|
||||
};
|
||||
readonly membershipId: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"UserFormFragment">;
|
||||
} | {
|
||||
// This will never be '%other', but we need some
|
||||
// value in case none of the concrete values match.
|
||||
readonly __typename: "%other";
|
||||
};
|
||||
};
|
||||
export type UserPageQuery = {
|
||||
response: UserPageQuery$data;
|
||||
variables: UserPageQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "userId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "userId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "membershipId",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "email",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = {
|
||||
"alias": "canDelete",
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "action",
|
||||
"value": "iam:membership-profile:delete"
|
||||
}
|
||||
],
|
||||
"kind": "ScalarField",
|
||||
"name": "permission",
|
||||
"storageKey": "permission(action:\"iam:membership-profile:delete\")"
|
||||
},
|
||||
v7 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "UserPageQuery",
|
||||
"selections": [
|
||||
{
|
||||
"kind": "RequiredField",
|
||||
"field": {
|
||||
"alias": "user",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
{
|
||||
"kind": "RequiredField",
|
||||
"field": {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Identity",
|
||||
"kind": "LinkedField",
|
||||
"name": "identity",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v5/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
"action": "THROW"
|
||||
},
|
||||
(v6/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "UserFormFragment"
|
||||
}
|
||||
],
|
||||
"type": "MembershipProfile",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
"action": "THROW"
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "UserPageQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "user",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v7/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Identity",
|
||||
"kind": "LinkedField",
|
||||
"name": "identity",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v5/*: any*/),
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v6/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "kind",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "position",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "additionalEmailAddresses",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "contractStartDate",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "contractEndDate",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "canUpdate",
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "action",
|
||||
"value": "iam:membership-profile:update"
|
||||
}
|
||||
],
|
||||
"kind": "ScalarField",
|
||||
"name": "permission",
|
||||
"storageKey": "permission(action:\"iam:membership-profile:update\")"
|
||||
}
|
||||
],
|
||||
"type": "MembershipProfile",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "9d437c761ed1795dde80f30a63e400fc",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "UserPageQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query UserPageQuery(\n $userId: ID!\n) {\n user: node(id: $userId) {\n __typename\n ... on MembershipProfile {\n fullName\n membershipId\n identity {\n email\n id\n }\n canDelete: permission(action: \"iam:membership-profile:delete\")\n ...UserFormFragment\n }\n id\n }\n}\n\nfragment UserFormFragment on MembershipProfile {\n id\n fullName\n kind\n position\n additionalEmailAddresses\n contractStartDate\n contractEndDate\n canUpdate: permission(action: \"iam:membership-profile:update\")\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "7711b4e7a7463f0134a47017f3e65aca";
|
||||
|
||||
export default node;
|
||||
93
apps/console/src/__generated__/iam/UserPage_removeMutation.graphql.ts
generated
Normal file
93
apps/console/src/__generated__/iam/UserPage_removeMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @generated SignedSource<<0cb21a9122ca2763aaa5dbe4d33e2b4a>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type RemoveMemberInput = {
|
||||
membershipId: string;
|
||||
organizationId: string;
|
||||
};
|
||||
export type UserPage_removeMutation$variables = {
|
||||
input: RemoveMemberInput;
|
||||
};
|
||||
export type UserPage_removeMutation$data = {
|
||||
readonly removeMember: {
|
||||
readonly deletedMembershipId: string;
|
||||
} | null | undefined;
|
||||
};
|
||||
export type UserPage_removeMutation = {
|
||||
response: UserPage_removeMutation$data;
|
||||
variables: UserPage_removeMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "RemoveMemberPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "removeMember",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "deletedMembershipId",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "UserPage_removeMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "UserPage_removeMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "7aa8db55205ca16403a5919a5151d69f",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "UserPage_removeMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation UserPage_removeMutation(\n $input: RemoveMemberInput!\n) {\n removeMember(input: $input) {\n deletedMembershipId\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "3c9804054834be923048845b796a2dfd";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<6a26345e6949e02e37f5ab52c4d6fe05>>
|
||||
* @generated SignedSource<<f3fe7986abd30c9414ea3361c3b1162d>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -385,6 +385,19 @@ return {
|
||||
"name": "permission",
|
||||
"storageKey": "permission(action:\"core:framework:list\")"
|
||||
},
|
||||
{
|
||||
"alias": "canListMembers",
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "action",
|
||||
"value": "iam:membership:list"
|
||||
}
|
||||
],
|
||||
"kind": "ScalarField",
|
||||
"name": "permission",
|
||||
"storageKey": "permission(action:\"iam:membership:list\")"
|
||||
},
|
||||
{
|
||||
"alias": "canListVendors",
|
||||
"args": [
|
||||
@@ -605,12 +618,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "6f7fa430d00e6734258bfc03bebb1b29",
|
||||
"cacheID": "a553750ba3bad35fb5b3ed7c1bebcbb4",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "ViewerMembershipLayoutQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query ViewerMembershipLayoutQuery(\n $organizationId: ID!\n $hideSidebar: Boolean!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n ...ViewerMembershipDropdownFragment\n ...SidebarFragment @skip(if: $hideSidebar)\n viewerMembership {\n role\n profile {\n fullName\n id\n }\n id\n }\n }\n id\n }\n viewer {\n email\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SidebarFragment on Organization {\n canListMeetings: permission(action: \"core:meeting:list\")\n canListTasks: permission(action: \"core:task:list\")\n canListMeasures: permission(action: \"core:measure:list\")\n canListRisks: permission(action: \"core:risk:list\")\n canListFrameworks: permission(action: \"core:framework:list\")\n canListVendors: permission(action: \"core:vendor:list\")\n canListDocuments: permission(action: \"core:document:list\")\n canListAssets: permission(action: \"core:asset:list\")\n canListData: permission(action: \"core:datum:list\")\n canListAudits: permission(action: \"core:audit:list\")\n canListNonconformities: permission(action: \"core:nonconformity:list\")\n canListObligations: permission(action: \"core:obligation:list\")\n canListContinualImprovements: permission(action: \"core:continual-improvement:list\")\n canListProcessingActivities: permission(action: \"core:processing-activity:list\")\n canListRightsRequests: permission(action: \"core:rights-request:list\")\n canListSnapshots: permission(action: \"core:snapshot:list\")\n canGetTrustCenter: permission(action: \"core:trust-center:get\")\n canUpdateOrganization: permission(action: \"iam:organization:update\")\n canListStatesOfApplicability: permission(action: \"core:state-of-applicability:list\")\n}\n\nfragment ViewerMembershipDropdownFragment on Organization {\n viewerMembership {\n identity {\n email\n canListAPIKeys: permission(action: \"iam:personal-api-key:list\")\n id\n }\n profile {\n fullName\n id\n }\n id\n }\n}\n"
|
||||
"text": "query ViewerMembershipLayoutQuery(\n $organizationId: ID!\n $hideSidebar: Boolean!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n ...ViewerMembershipDropdownFragment\n ...SidebarFragment @skip(if: $hideSidebar)\n viewerMembership {\n role\n profile {\n fullName\n id\n }\n id\n }\n }\n id\n }\n viewer {\n email\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SidebarFragment on Organization {\n canListMeetings: permission(action: \"core:meeting:list\")\n canListTasks: permission(action: \"core:task:list\")\n canListMeasures: permission(action: \"core:measure:list\")\n canListRisks: permission(action: \"core:risk:list\")\n canListFrameworks: permission(action: \"core:framework:list\")\n canListMembers: permission(action: \"iam:membership:list\")\n canListVendors: permission(action: \"core:vendor:list\")\n canListDocuments: permission(action: \"core:document:list\")\n canListAssets: permission(action: \"core:asset:list\")\n canListData: permission(action: \"core:datum:list\")\n canListAudits: permission(action: \"core:audit:list\")\n canListNonconformities: permission(action: \"core:nonconformity:list\")\n canListObligations: permission(action: \"core:obligation:list\")\n canListContinualImprovements: permission(action: \"core:continual-improvement:list\")\n canListProcessingActivities: permission(action: \"core:processing-activity:list\")\n canListRightsRequests: permission(action: \"core:rights-request:list\")\n canListSnapshots: permission(action: \"core:snapshot:list\")\n canGetTrustCenter: permission(action: \"core:trust-center:get\")\n canUpdateOrganization: permission(action: \"iam:organization:update\")\n canListStatesOfApplicability: permission(action: \"core:state-of-applicability:list\")\n}\n\nfragment ViewerMembershipDropdownFragment on Organization {\n viewerMembership {\n identity {\n email\n canListAPIKeys: permission(action: \"iam:personal-api-key:list\")\n id\n }\n profile {\n fullName\n id\n }\n id\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
IconClock,
|
||||
IconCrossLargeX,
|
||||
IconFire3,
|
||||
IconGroup1,
|
||||
IconInboxEmpty,
|
||||
IconListStack,
|
||||
IconLock,
|
||||
@@ -34,6 +35,7 @@ const fragment = graphql`
|
||||
canListMeasures: permission(action: "core:measure:list")
|
||||
canListRisks: permission(action: "core:risk:list")
|
||||
canListFrameworks: permission(action: "core:framework:list")
|
||||
canListMembers: permission(action: "iam:membership:list")
|
||||
canListVendors: permission(action: "core:vendor:list")
|
||||
canListDocuments: permission(action: "core:document:list")
|
||||
canListAssets: permission(action: "core:asset:list")
|
||||
@@ -104,6 +106,13 @@ export function Sidebar(props: { fKey: SidebarFragment$key }) {
|
||||
to={`${prefix}/frameworks`}
|
||||
/>
|
||||
)}
|
||||
{organization.canListMembers && (
|
||||
<SidebarItem
|
||||
label={__("People")}
|
||||
icon={IconGroup1}
|
||||
to={`${prefix}/users`}
|
||||
/>
|
||||
)}
|
||||
{organization.canListVendors && (
|
||||
<SidebarItem
|
||||
label={__("Vendors")}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
IconKey,
|
||||
IconLock,
|
||||
IconPeopleAdd,
|
||||
IconSettingsGear2,
|
||||
PageHeader,
|
||||
TabLink,
|
||||
@@ -25,10 +24,6 @@ export default function SettingsLayout() {
|
||||
<IconSettingsGear2 size={20} />
|
||||
{__("General")}
|
||||
</TabLink>
|
||||
<TabLink to={`/organizations/${organizationId}/settings/members`}>
|
||||
<IconPeopleAdd size={20} />
|
||||
{__("Members")}
|
||||
</TabLink>
|
||||
<TabLink to={`/organizations/${organizationId}/settings/saml-sso`}>
|
||||
<IconLock size={20} />
|
||||
{__("SAML SSO")}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Card, TabBadge, TabItem, Tabs } from "@probo/ui";
|
||||
import { Button, TabBadge, TabItem, Tabs } from "@probo/ui";
|
||||
import { useState } from "react";
|
||||
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||
import { ConnectionHandler, graphql } from "relay-runtime";
|
||||
@@ -73,7 +73,7 @@ export function MembersPage(props: {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-base font-medium">{__("Workspace members")}</h2>
|
||||
<h2 className="text-base font-medium">{__("People")}</h2>
|
||||
{organization.canInviteUser && (
|
||||
<InviteUserDialog
|
||||
connectionId={invitationListConnectionId}
|
||||
@@ -105,20 +105,18 @@ export function MembersPage(props: {
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Card>
|
||||
<div className="px-6 pb-6 pt-6">
|
||||
{activeTab === "memberships" && <MemberList fKey={organization} />}
|
||||
<div className="pb-6 pt-6">
|
||||
{activeTab === "memberships" && <MemberList fKey={organization} />}
|
||||
|
||||
{activeTab === "invitations" && (
|
||||
<InvitationList
|
||||
fKey={organization}
|
||||
totalCount={organization.invitations.totalCount ?? 0}
|
||||
totalCountFKey={organization.invitations}
|
||||
onConnectionIdChange={setInvitationListConnectionId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
{activeTab === "invitations" && (
|
||||
<InvitationList
|
||||
fKey={organization}
|
||||
totalCount={organization.invitations.totalCount ?? 0}
|
||||
totalCountFKey={organization.invitations}
|
||||
onConnectionIdChange={setInvitationListConnectionId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
125
apps/console/src/pages/iam/organizations/users/UserPage.tsx
Normal file
125
apps/console/src/pages/iam/organizations/users/UserPage.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { ActionDropdown, Avatar, Breadcrumb, DropdownItem, IconTrashCan, useConfirm } from "@probo/ui";
|
||||
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||
import { useNavigate } from "react-router";
|
||||
import { graphql } from "relay-runtime";
|
||||
|
||||
import type { UserPageQuery } from "#/__generated__/iam/UserPageQuery.graphql";
|
||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||
|
||||
import { UserForm } from "./_components/UserForm";
|
||||
|
||||
export const userPageQuery = graphql`
|
||||
query UserPageQuery($userId: ID!) {
|
||||
user: node(id: $userId) @required(action: THROW) {
|
||||
__typename
|
||||
... on MembershipProfile {
|
||||
fullName
|
||||
membershipId
|
||||
identity @required(action: THROW) {
|
||||
email
|
||||
}
|
||||
canDelete: permission(action: "iam:membership-profile:delete")
|
||||
...UserFormFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const removeMemberMutation = graphql`
|
||||
mutation UserPage_removeMutation(
|
||||
$input: RemoveMemberInput!
|
||||
) {
|
||||
removeMember(input: $input) {
|
||||
deletedMembershipId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function UserPage(props: { queryRef: PreloadedQuery<UserPageQuery> }) {
|
||||
const { queryRef } = props;
|
||||
|
||||
const organizationId = useOrganizationId();
|
||||
const { __ } = useTranslate();
|
||||
const confirm = useConfirm();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { user } = usePreloadedQuery<UserPageQuery>(userPageQuery, queryRef);
|
||||
if (user.__typename !== "MembershipProfile") {
|
||||
throw new Error("invalid type for node");
|
||||
}
|
||||
|
||||
const [removeMembership, isRemoving] = useMutationWithToasts(
|
||||
removeMemberMutation,
|
||||
{
|
||||
successMessage: __("Member removed successfully"),
|
||||
errorMessage: __("Failed to remove member"),
|
||||
},
|
||||
);
|
||||
|
||||
const handleRemove = () => {
|
||||
confirm(
|
||||
() => {
|
||||
return removeMembership({
|
||||
variables: {
|
||||
input: {
|
||||
membershipId: user.membershipId,
|
||||
organizationId: organizationId,
|
||||
},
|
||||
},
|
||||
onCompleted: () => {
|
||||
void navigate(`/organizations/${organizationId}/users`);
|
||||
},
|
||||
});
|
||||
},
|
||||
{
|
||||
message: sprintf(
|
||||
__("Are you sure you want to remove %s?"),
|
||||
user.fullName,
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{
|
||||
label: __("People"),
|
||||
to: `/organizations/${organizationId}/users`,
|
||||
},
|
||||
{
|
||||
label: user.fullName,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center gap-6">
|
||||
<Avatar name={user.fullName} size="xl" />
|
||||
<div>
|
||||
|
||||
<div className="text-2xl">{user.fullName}</div>
|
||||
<div className="text-lg text-txt-secondary">{user.identity.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
{user.canDelete && (
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
onClick={handleRemove}
|
||||
disabled={isRemoving}
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<UserForm fragmentRef={user} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useEffect } from "react";
|
||||
import { useQueryLoader } from "react-relay";
|
||||
import { useParams } from "react-router";
|
||||
|
||||
import type { UserPageQuery } from "#/__generated__/iam/UserPageQuery.graphql";
|
||||
import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton";
|
||||
import { IAMRelayProvider } from "#/providers/IAMRelayProvider";
|
||||
|
||||
import { UserPage, userPageQuery } from "./UserPage";
|
||||
|
||||
function UserPageQueryLoader() {
|
||||
const { userId } = useParams();
|
||||
if (!userId) {
|
||||
throw new Error(":userId missing in route params");
|
||||
}
|
||||
const [queryRef, loadQuery] = useQueryLoader<UserPageQuery>(userPageQuery);
|
||||
|
||||
useEffect(() => {
|
||||
if (!queryRef) {
|
||||
loadQuery({ userId });
|
||||
}
|
||||
}, [loadQuery, queryRef, userId]);
|
||||
|
||||
if (!queryRef) return <LinkCardSkeleton />;
|
||||
|
||||
return <UserPage queryRef={queryRef} />;
|
||||
}
|
||||
|
||||
export default function UserPageLoader() {
|
||||
return (
|
||||
<IAMRelayProvider>
|
||||
<UserPageQueryLoader />
|
||||
</IAMRelayProvider>
|
||||
);
|
||||
}
|
||||
@@ -69,8 +69,10 @@ export function MemberList(props: { fKey: MemberListFragment$key }) {
|
||||
<Tr>
|
||||
<SortableTh field="FULL_NAME">{__("Name")}</SortableTh>
|
||||
<SortableTh field="EMAIL_ADDRESS">{__("Email")}</SortableTh>
|
||||
<Th>{__("Type")}</Th>
|
||||
<SortableTh field="ROLE">{__("Role")}</SortableTh>
|
||||
<SortableTh field="CREATED_AT">{__("Joined")}</SortableTh>
|
||||
<Th>{__("Position")}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
@@ -19,7 +19,7 @@ import type { MemberListItemFragment$key } from "#/__generated__/iam/MemberListI
|
||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||
|
||||
import { EditMemberDialog } from "./EditMemberDialog";
|
||||
import { EditMemberDialog } from "../../settings/_components/EditMemberDialog";
|
||||
|
||||
const fragment = graphql`
|
||||
fragment MemberListItemFragment on Membership {
|
||||
@@ -28,7 +28,10 @@ const fragment = graphql`
|
||||
source
|
||||
state
|
||||
profile @required(action: THROW) {
|
||||
id
|
||||
fullName
|
||||
kind
|
||||
position
|
||||
}
|
||||
identity @required(action: THROW) {
|
||||
email
|
||||
@@ -103,6 +106,7 @@ export function MemberListItem(props: {
|
||||
isRemoving && "opacity-60 pointer-events-none",
|
||||
isInactive && "opacity-50",
|
||||
)}
|
||||
to={`/organizations/${organizationId}/users/${membership.profile.id}`}
|
||||
>
|
||||
<Td>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -116,10 +120,12 @@ export function MemberListItem(props: {
|
||||
<Badge variant="info">{membership.source}</Badge>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>{membership.profile.kind}</Td>
|
||||
<Td>
|
||||
<Badge>{membership.role}</Badge>
|
||||
</Td>
|
||||
<Td>{new Date(membership.createdAt).toLocaleDateString()}</Td>
|
||||
<Td>{membership.profile.position}</Td>
|
||||
<Td noLink width={160} className="text-end">
|
||||
{!isInactive && (
|
||||
<div
|
||||
@@ -0,0 +1,144 @@
|
||||
import { formatDatetime, getRoles, peopleRoles } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Card, Field, Input, Option } from "@probo/ui";
|
||||
import { useFragment } from "react-relay";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { UserFormFragment$key } from "#/__generated__/iam/UserFormFragment.graphql";
|
||||
import type { UserFormMutation } from "#/__generated__/iam/UserFormMutation.graphql";
|
||||
import { ControlledField } from "#/components/form/ControlledField";
|
||||
import { EmailsField } from "#/components/form/EmailsField";
|
||||
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||
|
||||
const fragment = graphql`
|
||||
fragment UserFormFragment on MembershipProfile {
|
||||
id
|
||||
fullName
|
||||
kind
|
||||
position
|
||||
additionalEmailAddresses
|
||||
contractStartDate
|
||||
contractEndDate
|
||||
canUpdate: permission(action: "iam:membership-profile:update")
|
||||
}
|
||||
`;
|
||||
|
||||
const updateUserMutation = graphql`
|
||||
mutation UserFormMutation($input: UpdateProfileInput!) {
|
||||
updateProfile(input: $input) {
|
||||
profile {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const schema = z.object({
|
||||
fullName: z.string().min(1),
|
||||
position: z.string().min(1).nullable(),
|
||||
additionalEmailAddresses: z.preprocess(
|
||||
// Empty additional emails are skipped
|
||||
v => (v as string[]).filter(v => !!v),
|
||||
z.array(z.string().email()),
|
||||
),
|
||||
kind: z.enum(peopleRoles),
|
||||
contractStartDate: z.string().optional().nullable(),
|
||||
contractEndDate: z.string().optional().nullable(),
|
||||
});
|
||||
|
||||
export function UserForm(props: { fragmentRef: UserFormFragment$key }) {
|
||||
const { fragmentRef } = props;
|
||||
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const user = useFragment<UserFormFragment$key>(fragment, fragmentRef);
|
||||
|
||||
const { control, formState, handleSubmit, register, reset }
|
||||
= useFormWithSchema(schema, {
|
||||
defaultValues: {
|
||||
kind: user.kind,
|
||||
fullName: user.fullName,
|
||||
position: user.position,
|
||||
additionalEmailAddresses: [...user.additionalEmailAddresses],
|
||||
contractStartDate: user.contractStartDate?.split("T")[0] || "",
|
||||
contractEndDate: user.contractEndDate?.split("T")[0] || "",
|
||||
},
|
||||
});
|
||||
const [mutate, isMutating] = useMutationWithToasts<UserFormMutation>(
|
||||
updateUserMutation,
|
||||
{
|
||||
successMessage: __("Member updated successfully."),
|
||||
errorMessage: __("Failed to update member"),
|
||||
},
|
||||
);
|
||||
const onSubmit = handleSubmit(async (data: z.infer<typeof schema>) => {
|
||||
const input = {
|
||||
id: user.id,
|
||||
fullName: data.fullName,
|
||||
additionalEmailAddresses: data.additionalEmailAddresses,
|
||||
kind: data.kind,
|
||||
position: data.position,
|
||||
contractStartDate: formatDatetime(data.contractStartDate) ?? null,
|
||||
contractEndDate: formatDatetime(data.contractEndDate) ?? null,
|
||||
};
|
||||
|
||||
await mutate({
|
||||
variables: { input },
|
||||
onCompleted: () => {
|
||||
reset(data);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={e => void onSubmit(e)} className="space-y-4">
|
||||
<Card padded className="space-y-4">
|
||||
<Field label={__("Full name")} {...register("fullName")} type="text" />
|
||||
<ControlledField
|
||||
control={control}
|
||||
name="kind"
|
||||
type="select"
|
||||
label={__("Type")}
|
||||
disabled={!user.canUpdate}
|
||||
>
|
||||
{getRoles(__).map(role => (
|
||||
<Option key={role.value} value={role.value}>
|
||||
{role.label}
|
||||
</Option>
|
||||
))}
|
||||
</ControlledField>
|
||||
<Field
|
||||
label={__("Position")}
|
||||
{...register("position")}
|
||||
type="text"
|
||||
placeholder={__("e.g. CEO, CFO, etc.")}
|
||||
disabled={!user.canUpdate}
|
||||
/>
|
||||
<EmailsField control={control} register={register} />
|
||||
<Field label={__("Contract start date")}>
|
||||
<Input
|
||||
{...register("contractStartDate")}
|
||||
type="date"
|
||||
disabled={!user.canUpdate}
|
||||
/>
|
||||
</Field>
|
||||
<Field label={__("Contract end date")}>
|
||||
<Input
|
||||
{...register("contractEndDate")}
|
||||
type="date"
|
||||
disabled={!user.canUpdate}
|
||||
/>
|
||||
</Field>
|
||||
</Card>
|
||||
<div className="flex justify-end">
|
||||
{formState.isDirty && user.canUpdate && (
|
||||
<Button type="submit" disabled={isMutating}>
|
||||
{__("Update")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
21
apps/console/src/pages/iam/organizations/users/routes.ts
Normal file
21
apps/console/src/pages/iam/organizations/users/routes.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { lazy } from "@probo/react-lazy";
|
||||
|
||||
import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton";
|
||||
|
||||
export const userRoutes = [
|
||||
{
|
||||
path: "users",
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
Component: lazy(() => import("#/pages/iam/organizations/users/MembersPageLoader")),
|
||||
Fallback: LinkCardSkeleton,
|
||||
},
|
||||
{
|
||||
path: ":userId",
|
||||
Component: lazy(() => import("#/pages/iam/organizations/users/UserPageLoader")),
|
||||
Fallback: LinkCardSkeleton,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -14,6 +14,7 @@ import { PageError } from "./components/PageError";
|
||||
import { RootErrorBoundary } from "./components/RootErrorBoundary";
|
||||
import { PageSkeleton } from "./components/skeletons/PageSkeleton";
|
||||
import { ViewerLayoutLoading } from "./pages/iam/memberships/ViewerLayoutLoading";
|
||||
import { userRoutes } from "./pages/iam/organizations/users/routes";
|
||||
import { compliancePageRoutes } from "./pages/organizations/compliance-page/routes";
|
||||
import { CurrentUser } from "./providers/CurrentUser";
|
||||
import { assetRoutes } from "./routes/assetRoutes";
|
||||
@@ -190,13 +191,6 @@ const routes = [
|
||||
import("./pages/iam/organizations/settings/GeneralSettingsPageLoader"),
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "members",
|
||||
Component: lazy(
|
||||
() =>
|
||||
import("./pages/iam/organizations/settings/MembersPageLoader"),
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "saml-sso",
|
||||
Component: lazy(
|
||||
@@ -213,6 +207,7 @@ const routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
...userRoutes,
|
||||
...riskRoutes,
|
||||
...measureRoutes,
|
||||
...documentsRoutes,
|
||||
|
||||
@@ -65,14 +65,18 @@ func (p *MembershipProfile) AuthorizationAttributes(ctx context.Context, conn pg
|
||||
q := `SELECT m.organization_id, mp.identity_id FROM iam_membership_profiles mp JOIN iam_memberships m ON mp.membership_id = m.id WHERE mp.id = $1 LIMIT 1;`
|
||||
|
||||
var organizationID gid.GID
|
||||
if err := conn.QueryRow(ctx, q, p.ID).Scan(&organizationID); err != nil {
|
||||
var identityID gid.GID
|
||||
if err := conn.QueryRow(ctx, q, p.ID).Scan(&organizationID, &identityID); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrResourceNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("cannot query membership profile authorization attributes: %w", err)
|
||||
}
|
||||
|
||||
return map[string]string{"organization_id": organizationID.String()}, nil
|
||||
return map[string]string{
|
||||
"organization_id": organizationID.String(),
|
||||
"identity_id": identityID.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *MembershipProfile) LoadByMembershipID(
|
||||
|
||||
@@ -52,8 +52,9 @@ const (
|
||||
ActionMembershipRoleSetOwner = "iam:membership-role:set-owner"
|
||||
|
||||
// Membership Profile actions
|
||||
ActionMembershipProfileGet = "iam:membership-profile:get"
|
||||
ActionMembershipProfileList = "iam:membership-profile:list"
|
||||
ActionMembershipProfileGet = "iam:membership-profile:get"
|
||||
ActionMembershipProfileList = "iam:membership-profile:list"
|
||||
ActionMembershipProfileUpdate = "iam:membership-profile:update"
|
||||
|
||||
// Personal API Key actions
|
||||
ActionPersonalAPIKeyCreate = "iam:personal-api-key:create"
|
||||
|
||||
@@ -161,6 +161,7 @@ var IAMOwnerPolicy = policy.NewPolicy(
|
||||
policy.Allow(
|
||||
ActionMembershipProfileGet,
|
||||
ActionMembershipProfileList,
|
||||
ActionMembershipProfileUpdate,
|
||||
).
|
||||
WithSID("full-membership-profile-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
@@ -239,6 +240,7 @@ var IAMAdminPolicy = policy.NewPolicy(
|
||||
policy.Allow(
|
||||
ActionMembershipProfileGet,
|
||||
ActionMembershipProfileList,
|
||||
ActionMembershipProfileUpdate,
|
||||
).
|
||||
WithSID("membership-profile-admin-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
|
||||
@@ -97,6 +97,16 @@ type (
|
||||
FullName string
|
||||
Role coredata.MembershipRole
|
||||
}
|
||||
|
||||
UpdateProfileRequest struct {
|
||||
ID gid.GID
|
||||
FullName string
|
||||
AdditionalEmailAddresses mail.Addrs
|
||||
Kind coredata.MembershipProfileKind
|
||||
Position **string
|
||||
ContractStartDate **time.Time
|
||||
ContractEndDate **time.Time
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -124,6 +134,8 @@ var (
|
||||
const (
|
||||
TokenTypeAPIKey = "api_key"
|
||||
|
||||
NameMaxLength = 100
|
||||
TitleMaxLength = 1000
|
||||
ContentMaxLength = 5000
|
||||
|
||||
DefaultAttributeEmail = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
|
||||
@@ -180,6 +192,22 @@ func (req UpdateOrganizationRequest) Validate() error {
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (upr *UpdateProfileRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(upr.ID, "id", validator.Required(), validator.GID(coredata.MembershipProfileEntityType))
|
||||
v.Check(upr.Kind, "kind", validator.OneOfSlice(coredata.PeopleKinds()))
|
||||
v.Check(upr.FullName, "full_name", validator.SafeTextNoNewLine(NameMaxLength))
|
||||
v.CheckEach(upr.AdditionalEmailAddresses, "additional_email_addresses", func(index int, item any) {
|
||||
v.Check(item, fmt.Sprintf("additional_email_addresses[%d]", index), validator.Required(), validator.NotEmpty())
|
||||
})
|
||||
v.Check(upr.Position, "position", validator.SafeText(TitleMaxLength))
|
||||
v.Check(upr.ContractStartDate, "contract_start_date", validator.Before(upr.ContractEndDate))
|
||||
v.Check(upr.ContractEndDate, "contract_end_date", validator.After(upr.ContractStartDate))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func NewOrganizationService(svc *Service) *OrganizationService {
|
||||
return &OrganizationService{Service: svc}
|
||||
}
|
||||
@@ -946,6 +974,57 @@ func (s *OrganizationService) ListMembers(
|
||||
return page.NewPage(memberships, cursor), nil
|
||||
}
|
||||
|
||||
func (s *OrganizationService) UpdateProfile(ctx context.Context, req *UpdateProfileRequest) (*coredata.MembershipProfile, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
scope = coredata.NewScopeFromObjectID(req.ID)
|
||||
profile = &coredata.MembershipProfile{}
|
||||
)
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := profile.LoadByID(ctx, conn, scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
|
||||
profile.FullName = req.FullName
|
||||
profile.Kind = req.Kind
|
||||
|
||||
profile.AdditionalEmailAddresses = req.AdditionalEmailAddresses
|
||||
|
||||
profile.Position = *req.Position
|
||||
|
||||
if req.ContractStartDate != nil {
|
||||
profile.ContractStartDate = *req.ContractStartDate
|
||||
}
|
||||
|
||||
if req.ContractEndDate != nil {
|
||||
profile.ContractEndDate = *req.ContractEndDate
|
||||
}
|
||||
|
||||
if profile.ContractStartDate != nil && profile.ContractEndDate != nil {
|
||||
if profile.ContractEndDate.Before(*profile.ContractStartDate) {
|
||||
return fmt.Errorf("contract end date must be after or equal to start date")
|
||||
}
|
||||
}
|
||||
|
||||
profile.UpdatedAt = time.Now()
|
||||
|
||||
return profile.Update(ctx, conn, scope)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
func (s *OrganizationService) GetProfile(ctx context.Context, profileID gid.GID) (*coredata.MembershipProfile, error) {
|
||||
profile := &coredata.MembershipProfile{}
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ type Mutation {
|
||||
@session(required: PRESENT)
|
||||
deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload
|
||||
@session(required: PRESENT)
|
||||
updateProfile(input: UpdateProfileInput!): UpdateProfilePayload!
|
||||
updateMembership(input: UpdateMembershipInput!): UpdateMembershipPayload!
|
||||
removeMember(input: RemoveMemberInput!): RemoveMemberPayload
|
||||
@session(required: PRESENT)
|
||||
@@ -176,9 +177,20 @@ type Identity implements Node {
|
||||
type MembershipProfile implements Node {
|
||||
id: ID!
|
||||
fullName: String!
|
||||
additionalEmailAddresses: [EmailAddr!]!
|
||||
kind: ProfileKind!
|
||||
position: String
|
||||
contractStartDate: Datetime
|
||||
contractEndDate: Datetime
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
|
||||
identity: Identity @goField(forceResolver: true)
|
||||
organization: Organization @goField(forceResolver: true)
|
||||
|
||||
# TODO: remove when memberships are under profile
|
||||
membershipId: ID!
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
@@ -229,6 +241,17 @@ type Organization implements Node {
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
enum ProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
|
||||
EMPLOYEE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindEmployee")
|
||||
CONTRACTOR
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindContractor")
|
||||
SERVICE_ACCOUNT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindServiceAccount"
|
||||
)
|
||||
}
|
||||
|
||||
enum MembershipRole
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
|
||||
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
|
||||
@@ -717,6 +740,16 @@ input InviteMemberInput {
|
||||
role: MembershipRole!
|
||||
}
|
||||
|
||||
input UpdateProfileInput {
|
||||
id: ID!
|
||||
fullName: String!
|
||||
additionalEmailAddresses: [EmailAddr!]
|
||||
kind: ProfileKind!
|
||||
position: String @goField(omittable: true)
|
||||
contractStartDate: Datetime @goField(omittable: true)
|
||||
contractEndDate: Datetime @goField(omittable: true)
|
||||
}
|
||||
|
||||
input UpdateMembershipInput {
|
||||
organizationId: ID!
|
||||
membershipId: ID!
|
||||
@@ -868,6 +901,10 @@ type InviteMemberPayload {
|
||||
invitationEdge: InvitationEdge!
|
||||
}
|
||||
|
||||
type UpdateProfilePayload {
|
||||
profile: MembershipProfile!
|
||||
}
|
||||
|
||||
type UpdateMembershipPayload {
|
||||
membership: Membership!
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,9 +18,21 @@ import "go.probo.inc/probo/pkg/coredata"
|
||||
|
||||
func NewMembershipProfile(profile *coredata.MembershipProfile) *MembershipProfile {
|
||||
return &MembershipProfile{
|
||||
ID: profile.ID,
|
||||
FullName: profile.FullName,
|
||||
CreatedAt: profile.CreatedAt,
|
||||
UpdatedAt: profile.UpdatedAt,
|
||||
ID: profile.ID,
|
||||
FullName: profile.FullName,
|
||||
AdditionalEmailAddresses: profile.AdditionalEmailAddresses,
|
||||
Kind: profile.Kind,
|
||||
Position: profile.Position,
|
||||
ContractStartDate: profile.ContractStartDate,
|
||||
ContractEndDate: profile.ContractEndDate,
|
||||
CreatedAt: profile.CreatedAt,
|
||||
UpdatedAt: profile.UpdatedAt,
|
||||
MembershipID: profile.MembershipID,
|
||||
Identity: &Identity{
|
||||
ID: profile.IdentityID,
|
||||
},
|
||||
Organization: &Organization{
|
||||
ID: profile.OrganizationID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,11 +240,19 @@ type MembershipEdge struct {
|
||||
}
|
||||
|
||||
type MembershipProfile struct {
|
||||
ID gid.GID `json:"id"`
|
||||
FullName string `json:"fullName"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Permission bool `json:"permission"`
|
||||
ID gid.GID `json:"id"`
|
||||
FullName string `json:"fullName"`
|
||||
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses"`
|
||||
Kind coredata.MembershipProfileKind `json:"kind"`
|
||||
Position *string `json:"position,omitempty"`
|
||||
ContractStartDate *time.Time `json:"contractStartDate,omitempty"`
|
||||
ContractEndDate *time.Time `json:"contractEndDate,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Identity *Identity `json:"identity,omitempty"`
|
||||
Organization *Organization `json:"organization,omitempty"`
|
||||
MembershipID gid.GID `json:"membershipId"`
|
||||
Permission bool `json:"permission"`
|
||||
}
|
||||
|
||||
func (MembershipProfile) IsNode() {}
|
||||
@@ -545,6 +553,20 @@ type UpdateOrganizationPayload struct {
|
||||
Organization *Organization `json:"organization,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateProfileInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
FullName string `json:"fullName"`
|
||||
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses,omitempty"`
|
||||
Kind coredata.MembershipProfileKind `json:"kind"`
|
||||
Position graphql.Omittable[*string] `json:"position,omitempty"`
|
||||
ContractStartDate graphql.Omittable[*time.Time] `json:"contractStartDate,omitempty"`
|
||||
ContractEndDate graphql.Omittable[*time.Time] `json:"contractEndDate,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateProfilePayload struct {
|
||||
Profile *MembershipProfile `json:"profile"`
|
||||
}
|
||||
|
||||
type UpdateSAMLConfigurationInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
SamlConfigurationID gid.GID `json:"samlConfigurationId"`
|
||||
|
||||
@@ -349,6 +349,53 @@ func (r *membershipConnectionResolver) TotalCount(ctx context.Context, obj *type
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
// Identity is the resolver for the identity field.
|
||||
func (r *membershipProfileResolver) Identity(ctx context.Context, obj *types.MembershipProfile) (*types.Identity, error) {
|
||||
if err := r.authorize(
|
||||
ctx,
|
||||
obj.Identity.ID,
|
||||
iam.ActionIdentityGet,
|
||||
authz.WithAttr("organization_id", obj.Organization.ID.String()),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gqlutils.OnlyIDSelected(ctx) {
|
||||
return &types.Identity{
|
||||
ID: obj.Identity.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
identity, err := r.iam.AccountService.GetIdentity(ctx, obj.Identity.ID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot get identity for membership", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewIdentity(identity), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *membershipProfileResolver) Organization(ctx context.Context, obj *types.MembershipProfile) (*types.Organization, error) {
|
||||
if err := r.authorize(ctx, obj.Organization.ID, iam.ActionOrganizationGet, authz.WithSkipAssumptionCheck()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gqlutils.OnlyIDSelected(ctx) {
|
||||
return &types.Organization{
|
||||
ID: obj.Organization.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
organization, err := r.iam.OrganizationService.GetOrganizationForMembership(ctx, obj.ID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot get organization for membership", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *membershipProfileResolver) Permission(ctx context.Context, obj *types.MembershipProfile, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
@@ -991,6 +1038,34 @@ func (r *mutationResolver) DeleteInvitation(ctx context.Context, input types.Del
|
||||
return &types.DeleteInvitationPayload{DeletedInvitationID: input.InvitationID}, nil
|
||||
}
|
||||
|
||||
// UpdateProfile is the resolver for the updateProfile field.
|
||||
func (r *mutationResolver) UpdateProfile(ctx context.Context, input types.UpdateProfileInput) (*types.UpdateProfilePayload, error) {
|
||||
if err := r.authorize(ctx, input.ID, iam.ActionMembershipProfileUpdate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
profile, err := r.iam.OrganizationService.UpdateProfile(
|
||||
ctx,
|
||||
&iam.UpdateProfileRequest{
|
||||
ID: input.ID,
|
||||
FullName: input.FullName,
|
||||
AdditionalEmailAddresses: input.AdditionalEmailAddresses,
|
||||
Kind: input.Kind,
|
||||
Position: gqlutils.UnwrapOmittable(input.Position),
|
||||
ContractStartDate: gqlutils.UnwrapOmittable(input.ContractStartDate),
|
||||
ContractEndDate: gqlutils.UnwrapOmittable(input.ContractEndDate),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.UpdateProfilePayload{
|
||||
Profile: types.NewMembershipProfile(profile),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateMembership is the resolver for the updateMembership field.
|
||||
func (r *mutationResolver) UpdateMembership(ctx context.Context, input types.UpdateMembershipInput) (*types.UpdateMembershipPayload, error) {
|
||||
if err := r.authorize(ctx, input.MembershipID, iam.ActionMembershipUpdate); err != nil {
|
||||
@@ -1494,6 +1569,16 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
||||
|
||||
return types.NewSession(session), nil
|
||||
}
|
||||
case coredata.MembershipProfileEntityType:
|
||||
action = iam.ActionMembershipGet
|
||||
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
profile, err := r.iam.OrganizationService.GetProfile(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewMembershipProfile(profile), nil
|
||||
}
|
||||
case coredata.MembershipEntityType:
|
||||
action = iam.ActionMembershipGet
|
||||
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
|
||||
@@ -69,7 +69,7 @@ enum EvidenceState
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested")
|
||||
}
|
||||
|
||||
enum MembershipProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
|
||||
enum ProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
|
||||
EMPLOYEE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindEmployee")
|
||||
CONTRACTOR
|
||||
@@ -1846,7 +1846,7 @@ type Profile implements Node {
|
||||
fullName: String!
|
||||
emailAddress: EmailAddr!
|
||||
additionalEmailAddresses: [EmailAddr!]!
|
||||
kind: MembershipProfileKind!
|
||||
kind: ProfileKind!
|
||||
position: String
|
||||
contractStartDate: Datetime
|
||||
contractEndDate: Datetime
|
||||
|
||||
@@ -10347,7 +10347,7 @@ enum EvidenceState
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested")
|
||||
}
|
||||
|
||||
enum MembershipProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
|
||||
enum ProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
|
||||
EMPLOYEE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindEmployee")
|
||||
CONTRACTOR
|
||||
@@ -12124,7 +12124,7 @@ type Profile implements Node {
|
||||
fullName: String!
|
||||
emailAddress: EmailAddr!
|
||||
additionalEmailAddresses: [EmailAddr!]!
|
||||
kind: MembershipProfileKind!
|
||||
kind: ProfileKind!
|
||||
position: String
|
||||
contractStartDate: Datetime
|
||||
contractEndDate: Datetime
|
||||
@@ -44174,7 +44174,7 @@ func (ec *executionContext) _Profile_kind(ctx context.Context, field graphql.Col
|
||||
return obj.Kind, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind,
|
||||
ec.marshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -44187,7 +44187,7 @@ func (ec *executionContext) fieldContext_Profile_kind(_ context.Context, field g
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type MembershipProfileKind does not have child fields")
|
||||
return nil, errors.New("field of type ProfileKind does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
@@ -94083,36 +94083,6 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) unmarshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind(ctx context.Context, v any) (coredata.MembershipProfileKind, error) {
|
||||
tmp, err := graphql.UnmarshalString(v)
|
||||
res := unmarshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind[tmp]
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind(ctx context.Context, sel ast.SelectionSet, v coredata.MembershipProfileKind) graphql.Marshaler {
|
||||
_ = sel
|
||||
res := graphql.MarshalString(marshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind[v])
|
||||
if res == graphql.Null {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
var (
|
||||
unmarshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind = map[string]coredata.MembershipProfileKind{
|
||||
"EMPLOYEE": coredata.MembershipProfileKindEmployee,
|
||||
"CONTRACTOR": coredata.MembershipProfileKindContractor,
|
||||
"SERVICE_ACCOUNT": coredata.MembershipProfileKindServiceAccount,
|
||||
}
|
||||
marshalNMembershipProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind = map[coredata.MembershipProfileKind]string{
|
||||
coredata.MembershipProfileKindEmployee: "EMPLOYEE",
|
||||
coredata.MembershipProfileKindContractor: "CONTRACTOR",
|
||||
coredata.MembershipProfileKindServiceAccount: "SERVICE_ACCOUNT",
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) marshalNNode2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐNode(ctx context.Context, sel ast.SelectionSet, v types.Node) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
@@ -94885,6 +94855,36 @@ func (ec *executionContext) marshalNProfileEdge2ᚖgoᚗproboᚗincᚋproboᚋpk
|
||||
return ec._ProfileEdge(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind(ctx context.Context, v any) (coredata.MembershipProfileKind, error) {
|
||||
tmp, err := graphql.UnmarshalString(v)
|
||||
res := unmarshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind[tmp]
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind(ctx context.Context, sel ast.SelectionSet, v coredata.MembershipProfileKind) graphql.Marshaler {
|
||||
_ = sel
|
||||
res := graphql.MarshalString(marshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind[v])
|
||||
if res == graphql.Null {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
var (
|
||||
unmarshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind = map[string]coredata.MembershipProfileKind{
|
||||
"EMPLOYEE": coredata.MembershipProfileKindEmployee,
|
||||
"CONTRACTOR": coredata.MembershipProfileKindContractor,
|
||||
"SERVICE_ACCOUNT": coredata.MembershipProfileKindServiceAccount,
|
||||
}
|
||||
marshalNProfileKind2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileKind = map[coredata.MembershipProfileKind]string{
|
||||
coredata.MembershipProfileKindEmployee: "EMPLOYEE",
|
||||
coredata.MembershipProfileKindContractor: "CONTRACTOR",
|
||||
coredata.MembershipProfileKindServiceAccount: "SERVICE_ACCOUNT",
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) unmarshalNProfileOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileOrderField(ctx context.Context, v any) (coredata.MembershipProfileOrderField, error) {
|
||||
tmp, err := graphql.UnmarshalString(v)
|
||||
res := unmarshalNProfileOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipProfileOrderField[tmp]
|
||||
|
||||
Reference in New Issue
Block a user