Enhance people

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-08-12 10:29:13 +02:00
parent 6eb9a39c17
commit d65bc5f7e2
23 changed files with 430 additions and 118 deletions

View File

@@ -38,7 +38,7 @@ function PeopleSelectWithQuery(
) { ) {
const { __ } = useTranslate(); const { __ } = useTranslate();
const { name, organizationId, control } = props; const { name, organizationId, control } = props;
const people = usePeople(organizationId); const people = usePeople(organizationId, { excludeContractEnded: true });
return ( return (
<> <>

View File

@@ -16,10 +16,10 @@ import { promisifyMutation, sprintf } from "@probo/helpers";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
const peopleQuery = graphql` const peopleQuery = graphql`
query PeopleGraphQuery($organizationId: ID!) { query PeopleGraphQuery($organizationId: ID!, $filter: PeopleFilter) {
organization: node(id: $organizationId) { organization: node(id: $organizationId) {
... on Organization { ... on Organization {
peoples(first: 100, orderBy: { direction: ASC, field: CREATED_AT }) { peoples(first: 100, orderBy: { direction: ASC, field: FULL_NAME }, filter: $filter) {
edges { edges {
node { node {
id id
@@ -36,10 +36,15 @@ const peopleQuery = graphql`
/** /**
* Return a list of people (used for people selectors) * Return a list of people (used for people selectors)
*/ */
export function usePeople(organizationId: string) { export function usePeople(organizationId: string, { excludeContractEnded }: { excludeContractEnded?: boolean } = {}) {
const data = useLazyLoadQuery<PeopleGraphQuery>(peopleQuery, { const data = useLazyLoadQuery<PeopleGraphQuery>(
organizationId: organizationId, peopleQuery,
}); {
organizationId: organizationId,
filter: excludeContractEnded ? { excludeContractEnded: true } : null,
},
{ fetchPolicy: "network-only" }
);
return useMemo(() => { return useMemo(() => {
return data.organization?.peoples?.edges.map((edge) => edge.node) ?? []; return data.organization?.peoples?.edges.map((edge) => edge.node) ?? [];
}, [data]); }, [data]);
@@ -61,7 +66,8 @@ export const paginatedPeopleFragment = graphql`
@refetchable(queryName: "PeopleListQuery") @refetchable(queryName: "PeopleListQuery")
@argumentDefinitions( @argumentDefinitions(
first: { type: "Int", defaultValue: 50 } first: { type: "Int", defaultValue: 50 }
order: { type: "PeopleOrder", defaultValue: null } order: { type: "PeopleOrder", defaultValue: { direction: ASC, field: FULL_NAME } }
filter: { type: "PeopleFilter", defaultValue: null }
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 }
@@ -72,6 +78,7 @@ export const paginatedPeopleFragment = graphql`
last: $last last: $last
before: $before before: $before
orderBy: $order orderBy: $order
filter: $filter
) @connection(key: "PeopleGraphPaginatedQuery_peoples") { ) @connection(key: "PeopleGraphPaginatedQuery_peoples") {
__id __id
edges { edges {
@@ -82,6 +89,8 @@ export const paginatedPeopleFragment = graphql`
kind kind
position position
additionalEmailAddresses additionalEmailAddresses
contractStartDate
contractEndDate
} }
} }
} }
@@ -161,6 +170,8 @@ export const peopleNodeQuery = graphql`
position position
kind kind
additionalEmailAddresses additionalEmailAddresses
contractStartDate
contractEndDate
} }
} }
} }
@@ -176,6 +187,8 @@ export const updatePeopleMutation = graphql`
position position
kind kind
additionalEmailAddresses additionalEmailAddresses
contractStartDate
contractEndDate
} }
} }
} }

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<71df408810680ded020fb0e1cc93ff79>> * @generated SignedSource<<e263259b92f7b1fe14bca5fca49b20bf>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -16,6 +16,8 @@ export type PeopleGraphNodeQuery$variables = {
export type PeopleGraphNodeQuery$data = { export type PeopleGraphNodeQuery$data = {
readonly node: { readonly node: {
readonly additionalEmailAddresses?: ReadonlyArray<string>; readonly additionalEmailAddresses?: ReadonlyArray<string>;
readonly contractEndDate?: any | null | undefined;
readonly contractStartDate?: any | null | undefined;
readonly fullName?: string; readonly fullName?: string;
readonly id?: string; readonly id?: string;
readonly kind?: PeopleKind; readonly kind?: PeopleKind;
@@ -84,6 +86,20 @@ v7 = {
"kind": "ScalarField", "kind": "ScalarField",
"name": "additionalEmailAddresses", "name": "additionalEmailAddresses",
"storageKey": null "storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "contractStartDate",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "contractEndDate",
"storageKey": null
}; };
return { return {
"fragment": { "fragment": {
@@ -108,7 +124,9 @@ return {
(v4/*: any*/), (v4/*: any*/),
(v5/*: any*/), (v5/*: any*/),
(v6/*: any*/), (v6/*: any*/),
(v7/*: any*/) (v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/)
], ],
"type": "People", "type": "People",
"abstractKey": null "abstractKey": null
@@ -149,7 +167,9 @@ return {
(v4/*: any*/), (v4/*: any*/),
(v5/*: any*/), (v5/*: any*/),
(v6/*: any*/), (v6/*: any*/),
(v7/*: any*/) (v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/)
], ],
"type": "People", "type": "People",
"abstractKey": null "abstractKey": null
@@ -160,16 +180,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "850de97d42fdda9d2335c0e5219fd067", "cacheID": "096d011a1e9a7374a13d037738b46fc6",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PeopleGraphNodeQuery", "name": "PeopleGraphNodeQuery",
"operationKind": "query", "operationKind": "query",
"text": "query PeopleGraphNodeQuery(\n $peopleId: ID!\n) {\n node(id: $peopleId) {\n __typename\n ... on People {\n id\n fullName\n primaryEmailAddress\n position\n kind\n additionalEmailAddresses\n }\n id\n }\n}\n" "text": "query PeopleGraphNodeQuery(\n $peopleId: ID!\n) {\n node(id: $peopleId) {\n __typename\n ... on People {\n id\n fullName\n primaryEmailAddress\n position\n kind\n additionalEmailAddresses\n contractStartDate\n contractEndDate\n }\n id\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "f39d45664dba2b3bcdad07ef8baebfe9"; (node as any).hash = "af18ca63be219f85baa39bf99294b4f6";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<5eba326084bfab4966727af60e2aa85c>> * @generated SignedSource<<940911e0b7ae1ffe53a31f98216dcfe6>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -18,6 +18,8 @@ export type PeopleGraphPaginatedFragment$data = {
readonly edges: ReadonlyArray<{ readonly edges: ReadonlyArray<{
readonly node: { readonly node: {
readonly additionalEmailAddresses: ReadonlyArray<string>; readonly additionalEmailAddresses: ReadonlyArray<string>;
readonly contractEndDate: any | null | undefined;
readonly contractStartDate: any | null | undefined;
readonly fullName: string; readonly fullName: string;
readonly id: string; readonly id: string;
readonly kind: PeopleKind; readonly kind: PeopleKind;
@@ -58,6 +60,11 @@ return {
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "before" "name": "before"
}, },
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "filter"
},
{ {
"defaultValue": 50, "defaultValue": 50,
"kind": "LocalArgument", "kind": "LocalArgument",
@@ -69,7 +76,10 @@ return {
"name": "last" "name": "last"
}, },
{ {
"defaultValue": null, "defaultValue": {
"direction": "ASC",
"field": "FULL_NAME"
},
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "order" "name": "order"
} }
@@ -111,6 +121,11 @@ return {
{ {
"alias": "peoples", "alias": "peoples",
"args": [ "args": [
{
"kind": "Variable",
"name": "filter",
"variableName": "filter"
},
{ {
"kind": "Variable", "kind": "Variable",
"name": "orderBy", "name": "orderBy",
@@ -174,6 +189,20 @@ return {
"name": "additionalEmailAddresses", "name": "additionalEmailAddresses",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "contractStartDate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "contractEndDate",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -255,6 +284,6 @@ return {
}; };
})(); })();
(node as any).hash = "5e095baf6a65e7cdbaa804d4658cbb6b"; (node as any).hash = "c658d1fc05ce8d1c12e6ba4fd3cf8c52";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<1ec11120f71d4bc3040bcdd2665904c0>> * @generated SignedSource<<c6717878cc2ffa24d0d56aab35c431c7>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -58,6 +58,14 @@ v4 = [
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
"value": 50 "value": 50
},
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "ASC",
"field": "FULL_NAME"
}
} }
]; ];
return { return {
@@ -174,6 +182,20 @@ return {
"name": "additionalEmailAddresses", "name": "additionalEmailAddresses",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "contractStartDate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "contractEndDate",
"storageKey": null
},
(v3/*: any*/) (v3/*: any*/)
], ],
"storageKey": null "storageKey": null
@@ -240,13 +262,14 @@ return {
] ]
} }
], ],
"storageKey": "peoples(first:50)" "storageKey": "peoples(first:50,orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})"
}, },
{ {
"alias": null, "alias": null,
"args": (v4/*: any*/), "args": (v4/*: any*/),
"filters": [ "filters": [
"orderBy" "orderBy",
"filter"
], ],
"handle": "connection", "handle": "connection",
"key": "PeopleGraphPaginatedQuery_peoples", "key": "PeopleGraphPaginatedQuery_peoples",
@@ -263,12 +286,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "1139d090f54b12e62e08f0a1970c9af0", "cacheID": "dca6a1202b168afd54cac8484cf6fadd",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PeopleGraphPaginatedQuery", "name": "PeopleGraphPaginatedQuery",
"operationKind": "query", "operationKind": "query",
"text": "query PeopleGraphPaginatedQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n ...PeopleGraphPaginatedFragment\n }\n id\n }\n}\n\nfragment PeopleGraphPaginatedFragment on Organization {\n peoples(first: 50) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n kind\n position\n additionalEmailAddresses\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n" "text": "query PeopleGraphPaginatedQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n ...PeopleGraphPaginatedFragment\n }\n id\n }\n}\n\nfragment PeopleGraphPaginatedFragment on Organization {\n peoples(first: 50, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n kind\n position\n additionalEmailAddresses\n contractStartDate\n contractEndDate\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<fbc31c6da75cbb26f402b2c88664950a>> * @generated SignedSource<<f8f5d2692a9b278d16d997a3ba952b03>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -9,7 +9,11 @@
// @ts-nocheck // @ts-nocheck
import { ConcreteRequest } from 'relay-runtime'; import { ConcreteRequest } from 'relay-runtime';
export type PeopleFilter = {
excludeContractEnded?: boolean | null | undefined;
};
export type PeopleGraphQuery$variables = { export type PeopleGraphQuery$variables = {
filter?: PeopleFilter | null | undefined;
organizationId: string; organizationId: string;
}; };
export type PeopleGraphQuery$data = { export type PeopleGraphQuery$data = {
@@ -31,33 +35,41 @@ export type PeopleGraphQuery = {
}; };
const node: ConcreteRequest = (function(){ const node: ConcreteRequest = (function(){
var v0 = [ var v0 = {
{ "defaultValue": null,
"defaultValue": null, "kind": "LocalArgument",
"kind": "LocalArgument", "name": "filter"
"name": "organizationId" },
} v1 = {
], "defaultValue": null,
v1 = [ "kind": "LocalArgument",
"name": "organizationId"
},
v2 = [
{ {
"kind": "Variable", "kind": "Variable",
"name": "id", "name": "id",
"variableName": "organizationId" "variableName": "organizationId"
} }
], ],
v2 = { v3 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "id", "name": "id",
"storageKey": null "storageKey": null
}, },
v3 = { v4 = {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
{ {
"alias": null, "alias": null,
"args": [ "args": [
{
"kind": "Variable",
"name": "filter",
"variableName": "filter"
},
{ {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
@@ -68,7 +80,7 @@ v3 = {
"name": "orderBy", "name": "orderBy",
"value": { "value": {
"direction": "ASC", "direction": "ASC",
"field": "CREATED_AT" "field": "FULL_NAME"
} }
} }
], ],
@@ -93,7 +105,7 @@ v3 = {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v2/*: any*/), (v3/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -115,7 +127,7 @@ v3 = {
"storageKey": null "storageKey": null
} }
], ],
"storageKey": "peoples(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" "storageKey": null
} }
], ],
"type": "Organization", "type": "Organization",
@@ -123,20 +135,23 @@ v3 = {
}; };
return { return {
"fragment": { "fragment": {
"argumentDefinitions": (v0/*: any*/), "argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment", "kind": "Fragment",
"metadata": null, "metadata": null,
"name": "PeopleGraphQuery", "name": "PeopleGraphQuery",
"selections": [ "selections": [
{ {
"alias": "organization", "alias": "organization",
"args": (v1/*: any*/), "args": (v2/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/) (v4/*: any*/)
], ],
"storageKey": null "storageKey": null
} }
@@ -146,13 +161,16 @@ return {
}, },
"kind": "Request", "kind": "Request",
"operation": { "operation": {
"argumentDefinitions": (v0/*: any*/), "argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation", "kind": "Operation",
"name": "PeopleGraphQuery", "name": "PeopleGraphQuery",
"selections": [ "selections": [
{ {
"alias": "organization", "alias": "organization",
"args": (v1/*: any*/), "args": (v2/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
@@ -165,24 +183,24 @@ return {
"name": "__typename", "name": "__typename",
"storageKey": null "storageKey": null
}, },
(v3/*: any*/), (v4/*: any*/),
(v2/*: any*/) (v3/*: any*/)
], ],
"storageKey": null "storageKey": null
} }
] ]
}, },
"params": { "params": {
"cacheID": "cf76244f6aaa0c573bdeb7bf118ad2a3", "cacheID": "cfb5be60bd39783154a815d178a8fa51",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PeopleGraphQuery", "name": "PeopleGraphQuery",
"operationKind": "query", "operationKind": "query",
"text": "query PeopleGraphQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n peoples(first: 100, orderBy: {direction: ASC, field: CREATED_AT}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n }\n }\n }\n }\n id\n }\n}\n" "text": "query PeopleGraphQuery(\n $organizationId: ID!\n $filter: PeopleFilter\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}, filter: $filter) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n }\n }\n }\n }\n id\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "11220f45320400c451abeaf7479a8e47"; (node as any).hash = "79f4d511be3c7cfa63a774ac017a6f41";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<18d62863109c347d5fcf7b5701a9c57b>> * @generated SignedSource<<a91a49863ad5c692ae869d3bcc0584cb>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -27,6 +27,8 @@ export type PeopleGraphUpdateMutation$data = {
readonly updatePeople: { readonly updatePeople: {
readonly people: { readonly people: {
readonly additionalEmailAddresses: ReadonlyArray<string>; readonly additionalEmailAddresses: ReadonlyArray<string>;
readonly contractEndDate: any | null | undefined;
readonly contractStartDate: any | null | undefined;
readonly fullName: string; readonly fullName: string;
readonly id: string; readonly id: string;
readonly kind: PeopleKind; readonly kind: PeopleKind;
@@ -112,6 +114,20 @@ v1 = [
"kind": "ScalarField", "kind": "ScalarField",
"name": "additionalEmailAddresses", "name": "additionalEmailAddresses",
"storageKey": null "storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "contractStartDate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "contractEndDate",
"storageKey": null
} }
], ],
"storageKey": null "storageKey": null
@@ -138,16 +154,16 @@ return {
"selections": (v1/*: any*/) "selections": (v1/*: any*/)
}, },
"params": { "params": {
"cacheID": "814551d1fb24548c41d417342625071f", "cacheID": "23c51259f3ff117441f9c6e11de5ba5a",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PeopleGraphUpdateMutation", "name": "PeopleGraphUpdateMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation PeopleGraphUpdateMutation(\n $input: UpdatePeopleInput!\n) {\n updatePeople(input: $input) {\n people {\n id\n fullName\n primaryEmailAddress\n position\n kind\n additionalEmailAddresses\n }\n }\n}\n" "text": "mutation PeopleGraphUpdateMutation(\n $input: UpdatePeopleInput!\n) {\n updatePeople(input: $input) {\n people {\n id\n fullName\n primaryEmailAddress\n position\n kind\n additionalEmailAddresses\n contractStartDate\n contractEndDate\n }\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "ecc5666046434d5c8e80eaab0e508c11"; (node as any).hash = "fc0946729d6a2729aa1b7c9b2dfbdf7a";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<4cfc7218bc48a818eedc2ea87ac521e6>> * @generated SignedSource<<e4afda2e1cb4e3fcfccfca64b086ad35>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -12,6 +12,9 @@ import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime"; import { FragmentRefs } from "relay-runtime";
export type OrderDirection = "ASC" | "DESC"; export type OrderDirection = "ASC" | "DESC";
export type PeopleOrderField = "CREATED_AT" | "FULL_NAME" | "KIND"; export type PeopleOrderField = "CREATED_AT" | "FULL_NAME" | "KIND";
export type PeopleFilter = {
excludeContractEnded?: boolean | null | undefined;
};
export type PeopleOrder = { export type PeopleOrder = {
direction: OrderDirection; direction: OrderDirection;
field: PeopleOrderField; field: PeopleOrderField;
@@ -19,6 +22,7 @@ export type PeopleOrder = {
export type PeopleListQuery$variables = { export type PeopleListQuery$variables = {
after?: any | null | undefined; after?: any | null | undefined;
before?: any | null | undefined; before?: any | null | undefined;
filter?: PeopleFilter | null | undefined;
first?: number | null | undefined; first?: number | null | undefined;
id: string; id: string;
last?: number | null | undefined; last?: number | null | undefined;
@@ -46,71 +50,85 @@ v1 = {
"name": "before" "name": "before"
}, },
v2 = { v2 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "filter"
},
v3 = {
"defaultValue": 50, "defaultValue": 50,
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "first" "name": "first"
}, },
v3 = { v4 = {
"defaultValue": null, "defaultValue": null,
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "id" "name": "id"
}, },
v4 = { v5 = {
"defaultValue": null, "defaultValue": null,
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "last" "name": "last"
}, },
v5 = { v6 = {
"defaultValue": null, "defaultValue": {
"direction": "ASC",
"field": "FULL_NAME"
},
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "order" "name": "order"
}, },
v6 = [ v7 = [
{ {
"kind": "Variable", "kind": "Variable",
"name": "id", "name": "id",
"variableName": "id" "variableName": "id"
} }
], ],
v7 = { v8 = {
"kind": "Variable", "kind": "Variable",
"name": "after", "name": "after",
"variableName": "after" "variableName": "after"
}, },
v8 = { v9 = {
"kind": "Variable", "kind": "Variable",
"name": "before", "name": "before",
"variableName": "before" "variableName": "before"
}, },
v9 = { v10 = {
"kind": "Variable",
"name": "filter",
"variableName": "filter"
},
v11 = {
"kind": "Variable", "kind": "Variable",
"name": "first", "name": "first",
"variableName": "first" "variableName": "first"
}, },
v10 = { v12 = {
"kind": "Variable", "kind": "Variable",
"name": "last", "name": "last",
"variableName": "last" "variableName": "last"
}, },
v11 = { v13 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "__typename", "name": "__typename",
"storageKey": null "storageKey": null
}, },
v12 = { v14 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "id", "name": "id",
"storageKey": null "storageKey": null
}, },
v13 = [ v15 = [
(v7/*: any*/),
(v8/*: any*/), (v8/*: any*/),
(v9/*: any*/), (v9/*: any*/),
(v10/*: any*/), (v10/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
{ {
"kind": "Variable", "kind": "Variable",
"name": "orderBy", "name": "orderBy",
@@ -125,7 +143,8 @@ return {
(v2/*: any*/), (v2/*: any*/),
(v3/*: any*/), (v3/*: any*/),
(v4/*: any*/), (v4/*: any*/),
(v5/*: any*/) (v5/*: any*/),
(v6/*: any*/)
], ],
"kind": "Fragment", "kind": "Fragment",
"metadata": null, "metadata": null,
@@ -133,7 +152,7 @@ return {
"selections": [ "selections": [
{ {
"alias": null, "alias": null,
"args": (v6/*: any*/), "args": (v7/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
@@ -141,10 +160,11 @@ return {
"selections": [ "selections": [
{ {
"args": [ "args": [
(v7/*: any*/),
(v8/*: any*/), (v8/*: any*/),
(v9/*: any*/), (v9/*: any*/),
(v10/*: any*/), (v10/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
{ {
"kind": "Variable", "kind": "Variable",
"name": "order", "name": "order",
@@ -167,29 +187,30 @@ return {
(v0/*: any*/), (v0/*: any*/),
(v1/*: any*/), (v1/*: any*/),
(v2/*: any*/), (v2/*: any*/),
(v4/*: any*/), (v3/*: any*/),
(v5/*: any*/), (v5/*: any*/),
(v3/*: any*/) (v6/*: any*/),
(v4/*: any*/)
], ],
"kind": "Operation", "kind": "Operation",
"name": "PeopleListQuery", "name": "PeopleListQuery",
"selections": [ "selections": [
{ {
"alias": null, "alias": null,
"args": (v6/*: any*/), "args": (v7/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v11/*: any*/), (v13/*: any*/),
(v12/*: any*/), (v14/*: any*/),
{ {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
{ {
"alias": null, "alias": null,
"args": (v13/*: any*/), "args": (v15/*: any*/),
"concreteType": "PeopleConnection", "concreteType": "PeopleConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "peoples", "name": "peoples",
@@ -211,7 +232,7 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v12/*: any*/), (v14/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -247,7 +268,21 @@ return {
"name": "additionalEmailAddresses", "name": "additionalEmailAddresses",
"storageKey": null "storageKey": null
}, },
(v11/*: any*/) {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "contractStartDate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "contractEndDate",
"storageKey": null
},
(v13/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -317,9 +352,10 @@ return {
}, },
{ {
"alias": null, "alias": null,
"args": (v13/*: any*/), "args": (v15/*: any*/),
"filters": [ "filters": [
"orderBy" "orderBy",
"filter"
], ],
"handle": "connection", "handle": "connection",
"key": "PeopleGraphPaginatedQuery_peoples", "key": "PeopleGraphPaginatedQuery_peoples",
@@ -336,16 +372,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "8134caa0eab10c19236a49728f1f77b6", "cacheID": "7df8a360f24224a73dc98c9b8f6da287",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PeopleListQuery", "name": "PeopleListQuery",
"operationKind": "query", "operationKind": "query",
"text": "query PeopleListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: PeopleOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PeopleGraphPaginatedFragment_16fISc\n id\n }\n}\n\nfragment PeopleGraphPaginatedFragment_16fISc on Organization {\n peoples(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n kind\n position\n additionalEmailAddresses\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n" "text": "query PeopleListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $filter: PeopleFilter = null\n $first: Int = 50\n $last: Int = null\n $order: PeopleOrder = {direction: ASC, field: FULL_NAME}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PeopleGraphPaginatedFragment_4cFWzS\n id\n }\n}\n\nfragment PeopleGraphPaginatedFragment_4cFWzS on Organization {\n peoples(first: $first, after: $after, last: $last, before: $before, orderBy: $order, filter: $filter) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n kind\n position\n additionalEmailAddresses\n contractStartDate\n contractEndDate\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n"
} }
}; };
})(); })();
(node as any).hash = "5e095baf6a65e7cdbaa804d4658cbb6b"; (node as any).hash = "c658d1fc05ce8d1c12e6ba4fd3cf8c52";
export default node; export default node;

View File

@@ -31,7 +31,7 @@ import { useOrganizationId } from "/hooks/useOrganizationId";
import { ControlledField } from "/components/form/ControlledField"; import { ControlledField } from "/components/form/ControlledField";
import { useFormWithSchema } from "/hooks/useFormWithSchema"; import { useFormWithSchema } from "/hooks/useFormWithSchema";
import z from "zod"; import z from "zod";
import { getAuditStateLabel, getAuditStateVariant, auditStates, fileSize, sprintf } from "@probo/helpers"; import { getAuditStateLabel, getAuditStateVariant, auditStates, fileSize, sprintf, formatDatetime } from "@probo/helpers";
import type { AuditGraphNodeQuery } from "/hooks/graph/__generated__/AuditGraphNodeQuery.graphql"; import type { AuditGraphNodeQuery } from "/hooks/graph/__generated__/AuditGraphNodeQuery.graphql";
const updateAuditSchema = z.object({ const updateAuditSchema = z.object({
@@ -77,11 +77,6 @@ export default function AuditDetailsPage(props: Props) {
if (!auditEntry.id) return; if (!auditEntry.id) return;
try { try {
const formatDatetime = (dateString?: string) => {
if (!dateString) return undefined;
return `${dateString}T00:00:00Z`;
};
await updateAudit({ await updateAudit({
id: auditEntry.id, id: auditEntry.id,
validFrom: formatDatetime(formData.validFrom), validFrom: formatDatetime(formData.validFrom),

View File

@@ -16,7 +16,7 @@ import z from "zod";
import { useFormWithSchema } from "/hooks/useFormWithSchema"; import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { ControlledField } from "/components/form/ControlledField"; import { ControlledField } from "/components/form/ControlledField";
import { useCreateAudit } from "/hooks/graph/AuditGraph"; import { useCreateAudit } from "/hooks/graph/AuditGraph";
import { auditStates, getAuditStateLabel } from "@probo/helpers"; import { auditStates, getAuditStateLabel, formatDatetime } from "@probo/helpers";
import { useLazyLoadQuery } from "react-relay"; import { useLazyLoadQuery } from "react-relay";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import { Suspense } from "react"; import { Suspense } from "react";
@@ -74,12 +74,6 @@ export function CreateAuditDialog({
const onSubmit = handleSubmit(async (data) => { const onSubmit = handleSubmit(async (data) => {
try { try {
// Convert date strings to datetime format
const formatDatetime = (dateString?: string) => {
if (!dateString) return undefined;
return `${dateString}T00:00:00Z`;
};
await createAudit({ await createAudit({
organizationId, organizationId,
frameworkId: data.frameworkId, frameworkId: data.frameworkId,

View File

@@ -26,6 +26,14 @@ import { useOrganizationId } from "/hooks/useOrganizationId";
type People = NodeOf<PeopleGraphPaginatedFragment$data["peoples"]>; type People = NodeOf<PeopleGraphPaginatedFragment$data["peoples"]>;
const isContractEnded = (person: People): boolean => {
if (!person.contractEndDate) return false;
const endDate = new Date(person.contractEndDate);
const today = new Date();
today.setHours(0, 0, 0, 0);
return endDate < today;
};
export default function PeopleListPage({ export default function PeopleListPage({
queryRef, queryRef,
}: { }: {
@@ -87,9 +95,13 @@ function PeopleRow({
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { __ } = useTranslate(); const { __ } = useTranslate();
const deletePeople = useDeletePeople(people, connectionId); const deletePeople = useDeletePeople(people, connectionId);
const contractEnded = isContractEnded(people);
return ( return (
<Tr to={`/organizations/${organizationId}/people/${people.id}/tasks`}> <Tr
to={`/organizations/${organizationId}/people/${people.id}/tasks`}
className={contractEnded ? "opacity-50" : ""}
>
<Td> <Td>
<div className="flex gap-3 items-center"> <div className="flex gap-3 items-center">
<Avatar name={people.fullName} /> <Avatar name={people.fullName} />

View File

@@ -1,5 +1,5 @@
import z from "zod"; import z from "zod";
import { peopleRoles } from "@probo/helpers"; import { peopleRoles, formatDatetime } from "@probo/helpers";
import { useOutletContext } from "react-router"; import { useOutletContext } from "react-router";
import type { PeopleGraphNodeQuery$data } from "/hooks/graph/__generated__/PeopleGraphNodeQuery.graphql"; import type { PeopleGraphNodeQuery$data } from "/hooks/graph/__generated__/PeopleGraphNodeQuery.graphql";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
@@ -7,7 +7,7 @@ import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import type { PeopleGraphUpdateMutation } from "/hooks/graph/__generated__/PeopleGraphUpdateMutation.graphql"; import type { PeopleGraphUpdateMutation } from "/hooks/graph/__generated__/PeopleGraphUpdateMutation.graphql";
import { updatePeopleMutation } from "/hooks/graph/PeopleGraph"; import { updatePeopleMutation } from "/hooks/graph/PeopleGraph";
import { Button, Card, Field } from "@probo/ui"; import { Button, Card, Field, Input } from "@probo/ui";
import { EmailsField } from "/components/form/EmailsField"; import { EmailsField } from "/components/form/EmailsField";
const schema = z.object({ const schema = z.object({
@@ -20,6 +20,8 @@ const schema = z.object({
z.array(z.string().email()) z.array(z.string().email())
), ),
kind: z.enum(peopleRoles), kind: z.enum(peopleRoles),
contractStartDate: z.string().optional().nullable(),
contractEndDate: z.string().optional().nullable(),
}); });
export default function PeopleProfileTab() { export default function PeopleProfileTab() {
@@ -35,6 +37,8 @@ export default function PeopleProfileTab() {
primaryEmailAddress: people.primaryEmailAddress, primaryEmailAddress: people.primaryEmailAddress,
position: people.position, position: people.position,
additionalEmailAddresses: [...(people.additionalEmailAddresses ?? [])], additionalEmailAddresses: [...(people.additionalEmailAddresses ?? [])],
contractStartDate: people.contractStartDate?.split('T')[0] || "",
contractEndDate: people.contractEndDate?.split('T')[0] || "",
}, },
}); });
const [mutate, isMutating] = useMutationWithToasts<PeopleGraphUpdateMutation>( const [mutate, isMutating] = useMutationWithToasts<PeopleGraphUpdateMutation>(
@@ -46,18 +50,19 @@ export default function PeopleProfileTab() {
); );
const onSubmit = handleSubmit((data) => { const onSubmit = handleSubmit((data) => {
const input = {
id: people.id!,
fullName: data.fullName,
primaryEmailAddress: data.primaryEmailAddress,
position: data.position,
additionalEmailAddresses: data.additionalEmailAddresses,
kind: people.kind,
contractStartDate: formatDatetime(data.contractStartDate) ?? null,
contractEndDate: formatDatetime(data.contractEndDate) ?? null,
};
mutate({ mutate({
variables: { variables: { input },
input: {
id: people.id!,
fullName: data.fullName,
primaryEmailAddress: data.primaryEmailAddress,
position: data.position,
additionalEmailAddresses: data.additionalEmailAddresses,
// TODO : make these field optional in the query (server side)
kind: people.kind,
},
},
onCompleted: () => { onCompleted: () => {
reset(data); reset(data);
}, },
@@ -80,6 +85,12 @@ export default function PeopleProfileTab() {
type="email" type="email"
/> />
<EmailsField control={control} register={register} /> <EmailsField control={control} register={register} />
<Field label={__("Contract start date")}>
<Input {...register("contractStartDate")} type="date" />
</Field>
<Field label={__("Contract end date")}>
<Input {...register("contractEndDate")} type="date" />
</Field>
</Card> </Card>
<div className="flex justify-end"> <div className="flex justify-end">
{formState.isDirty && ( {formState.isDirty && (

View File

@@ -47,6 +47,8 @@ export default function PeopleRoleTab() {
fullName: people.fullName!, fullName: people.fullName!,
primaryEmailAddress: people.primaryEmailAddress!, primaryEmailAddress: people.primaryEmailAddress!,
additionalEmailAddresses: people.additionalEmailAddresses ?? [], additionalEmailAddresses: people.additionalEmailAddresses ?? [],
contractStartDate: people.contractStartDate,
contractEndDate: people.contractEndDate,
}, },
}, },
onCompleted: () => { onCompleted: () => {

View File

@@ -0,0 +1,4 @@
export function formatDatetime(dateString?: string | null): string | undefined {
if (!dateString) return undefined;
return `${dateString}T00:00:00Z`;
}

View File

@@ -18,3 +18,4 @@ export { getAssetTypeVariant, getCriticityVariant } from "./assets";
export { getAuditStateLabel, getAuditStateVariant, auditStates } from "./audits"; export { getAuditStateLabel, getAuditStateVariant, auditStates } from "./audits";
export { promisifyMutation } from "./relay"; export { promisifyMutation } from "./relay";
export { fileType, fileSize } from "./file"; export { fileType, fileSize } from "./file";
export { formatDatetime } from "./date";

View File

@@ -300,6 +300,7 @@ func (p *Peoples) CountByOrganizationID(
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
organizationID gid.GID, organizationID gid.GID,
filter *PeopleFilter,
) (int, error) { ) (int, error) {
q := ` q := `
SELECT SELECT
@@ -309,12 +310,14 @@ FROM
WHERE WHERE
%s %s
AND organization_id = @organization_id AND organization_id = @organization_id
AND %s
` `
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID} args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
row := conn.QueryRow(ctx, q, args) row := conn.QueryRow(ctx, q, args)
@@ -333,6 +336,7 @@ func (p *Peoples) LoadByOrganizationID(
scope Scoper, scope Scoper,
organizationID gid.GID, organizationID gid.GID,
cursor *page.Cursor[PeopleOrderField], cursor *page.Cursor[PeopleOrderField],
filter *PeopleFilter,
) error { ) error {
q := ` q := `
SELECT SELECT
@@ -354,13 +358,15 @@ WHERE
%s %s
AND organization_id = @organization_id AND organization_id = @organization_id
AND %s AND %s
AND %s
` `
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID} args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, cursor.SQLArguments()) maps.Copy(args, cursor.SQLArguments())
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package coredata
import (
"time"
"github.com/jackc/pgx/v5"
)
type (
PeopleFilter struct {
excludeContractEnded *bool
currentDate time.Time
}
)
func NewPeopleFilter(excludeContractEnded *bool) *PeopleFilter {
return &PeopleFilter{
excludeContractEnded: excludeContractEnded,
currentDate: time.Now(),
}
}
func (f *PeopleFilter) SQLArguments() pgx.StrictNamedArgs {
return pgx.StrictNamedArgs{
"exclude_contract_ended": f.excludeContractEnded,
"current_date": f.currentDate,
}
}
func (f *PeopleFilter) SQLFragment() string {
return `
(
CASE
WHEN @exclude_contract_ended::boolean IS NOT NULL AND @exclude_contract_ended::boolean = true THEN
(contract_end_date IS NULL OR contract_end_date >= @current_date::date)
ELSE TRUE
END
)`
}

View File

@@ -98,6 +98,7 @@ func (s PeopleService) GetByUserID(
func (s PeopleService) CountForOrganizationID( func (s PeopleService) CountForOrganizationID(
ctx context.Context, ctx context.Context,
organizationID gid.GID, organizationID gid.GID,
filter *coredata.PeopleFilter,
) (int, error) { ) (int, error) {
var count int var count int
@@ -105,7 +106,7 @@ func (s PeopleService) CountForOrganizationID(
ctx, ctx,
func(conn pg.Conn) (err error) { func(conn pg.Conn) (err error) {
peoples := coredata.Peoples{} peoples := coredata.Peoples{}
count, err = peoples.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID) count, err = peoples.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, filter)
if err != nil { if err != nil {
return fmt.Errorf("cannot count peoples: %w", err) return fmt.Errorf("cannot count peoples: %w", err)
} }
@@ -125,6 +126,7 @@ func (s PeopleService) ListForOrganizationID(
ctx context.Context, ctx context.Context,
organizationID gid.GID, organizationID gid.GID,
cursor *page.Cursor[coredata.PeopleOrderField], cursor *page.Cursor[coredata.PeopleOrderField],
filter *coredata.PeopleFilter,
) (*page.Page[*coredata.People, coredata.PeopleOrderField], error) { ) (*page.Page[*coredata.People, coredata.PeopleOrderField], error) {
var peoples coredata.Peoples var peoples coredata.Peoples
@@ -137,6 +139,7 @@ func (s PeopleService) ListForOrganizationID(
s.svc.scope, s.svc.scope,
organizationID, organizationID,
cursor, cursor,
filter,
) )
}, },
) )

View File

@@ -718,6 +718,10 @@ input RiskFilter {
query: String query: String
} }
input PeopleFilter {
excludeContractEnded: Boolean
}
input OrganizationFilter { input OrganizationFilter {
trustCenterSlug: String trustCenterSlug: String
} }
@@ -796,6 +800,7 @@ type Organization implements Node {
last: Int last: Int
before: CursorKey before: CursorKey
orderBy: PeopleOrder orderBy: PeopleOrder
filter: PeopleFilter
): PeopleConnection! @goField(forceResolver: true) ): PeopleConnection! @goField(forceResolver: true)
documents( documents(

View File

@@ -665,7 +665,7 @@ type ComplexityRoot struct {
LogoURL func(childComplexity int) int LogoURL func(childComplexity int) int
Measures func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) int Measures func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) int
Name func(childComplexity int) int Name func(childComplexity int) int
Peoples func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PeopleOrderBy) int Peoples func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PeopleOrderBy, filter *types.PeopleFilter) int
Risks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy, filter *types.RiskFilter) int Risks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy, filter *types.RiskFilter) int
Tasks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) int Tasks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) int
TrustCenter func(childComplexity int) int TrustCenter func(childComplexity int) int
@@ -1212,7 +1212,7 @@ type OrganizationResolver interface {
Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FrameworkOrderBy) (*types.FrameworkConnection, error) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FrameworkOrderBy) (*types.FrameworkConnection, error)
Controls(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) Controls(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error)
Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) (*types.VendorConnection, error) Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) (*types.VendorConnection, error)
Peoples(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PeopleOrderBy) (*types.PeopleConnection, error) Peoples(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PeopleOrderBy, filter *types.PeopleFilter) (*types.PeopleConnection, error)
Documents(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error) Documents(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error)
Measures(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error) Measures(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error)
Risks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy, filter *types.RiskFilter) (*types.RiskConnection, error) Risks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy, filter *types.RiskFilter) (*types.RiskConnection, error)
@@ -4009,7 +4009,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return 0, false return 0, false
} }
return e.complexity.Organization.Peoples(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.PeopleOrderBy)), true return e.complexity.Organization.Peoples(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.PeopleOrderBy), args["filter"].(*types.PeopleFilter)), true
case "Organization.risks": case "Organization.risks":
if e.complexity.Organization.Risks == nil { if e.complexity.Organization.Risks == nil {
@@ -5551,6 +5551,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputMeasureOrder, ec.unmarshalInputMeasureOrder,
ec.unmarshalInputOrganizationFilter, ec.unmarshalInputOrganizationFilter,
ec.unmarshalInputOrganizationOrder, ec.unmarshalInputOrganizationOrder,
ec.unmarshalInputPeopleFilter,
ec.unmarshalInputPeopleOrder, ec.unmarshalInputPeopleOrder,
ec.unmarshalInputPublishDocumentVersionInput, ec.unmarshalInputPublishDocumentVersionInput,
ec.unmarshalInputRemoveUserInput, ec.unmarshalInputRemoveUserInput,
@@ -6402,6 +6403,10 @@ input RiskFilter {
query: String query: String
} }
input PeopleFilter {
excludeContractEnded: Boolean
}
input OrganizationFilter { input OrganizationFilter {
trustCenterSlug: String trustCenterSlug: String
} }
@@ -6480,6 +6485,7 @@ type Organization implements Node {
last: Int last: Int
before: CursorKey before: CursorKey
orderBy: PeopleOrder orderBy: PeopleOrder
filter: PeopleFilter
): PeopleConnection! @goField(forceResolver: true) ): PeopleConnection! @goField(forceResolver: true)
documents( documents(
@@ -12229,6 +12235,11 @@ func (ec *executionContext) field_Organization_peoples_args(ctx context.Context,
return nil, err return nil, err
} }
args["orderBy"] = arg4 args["orderBy"] = arg4
arg5, err := ec.field_Organization_peoples_argsFilter(ctx, rawArgs)
if err != nil {
return nil, err
}
args["filter"] = arg5
return args, nil return args, nil
} }
func (ec *executionContext) field_Organization_peoples_argsFirst( func (ec *executionContext) field_Organization_peoples_argsFirst(
@@ -12296,6 +12307,19 @@ func (ec *executionContext) field_Organization_peoples_argsOrderBy(
return zeroVal, nil return zeroVal, nil
} }
func (ec *executionContext) field_Organization_peoples_argsFilter(
ctx context.Context,
rawArgs map[string]any,
) (*types.PeopleFilter, error) {
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("filter"))
if tmp, ok := rawArgs["filter"]; ok {
return ec.unmarshalOPeopleFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐPeopleFilter(ctx, tmp)
}
var zeroVal *types.PeopleFilter
return zeroVal, nil
}
func (ec *executionContext) field_Organization_risks_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Organization_risks_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -30201,7 +30225,7 @@ func (ec *executionContext) _Organization_peoples(ctx context.Context, field gra
}() }()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children ctx = rctx // use context from middleware stack in children
return ec.resolvers.Organization().Peoples(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.PeopleOrderBy)) return ec.resolvers.Organization().Peoples(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.PeopleOrderBy), fc.Args["filter"].(*types.PeopleFilter))
}) })
if err != nil { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
@@ -45554,6 +45578,33 @@ func (ec *executionContext) unmarshalInputOrganizationOrder(ctx context.Context,
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputPeopleFilter(ctx context.Context, obj any) (types.PeopleFilter, error) {
var it types.PeopleFilter
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"excludeContractEnded"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "excludeContractEnded":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("excludeContractEnded"))
data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v)
if err != nil {
return it, err
}
it.ExcludeContractEnded = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputPeopleOrder(ctx context.Context, obj any) (types.PeopleOrderBy, error) { func (ec *executionContext) unmarshalInputPeopleOrder(ctx context.Context, obj any) (types.PeopleOrderBy, error) {
var it types.PeopleOrderBy var it types.PeopleOrderBy
asMap := map[string]any{} asMap := map[string]any{}
@@ -63240,6 +63291,14 @@ func (ec *executionContext) marshalOPeople2ᚖgithubᚗcomᚋgetproboᚋproboᚋ
return ec._People(ctx, sel, v) return ec._People(ctx, sel, v)
} }
func (ec *executionContext) unmarshalOPeopleFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐPeopleFilter(ctx context.Context, v any) (*types.PeopleFilter, error) {
if v == nil {
return nil, nil
}
res, err := ec.unmarshalInputPeopleFilter(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalOPeopleKind2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐPeopleKind(ctx context.Context, v any) (*coredata.PeopleKind, error) { func (ec *executionContext) unmarshalOPeopleKind2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐPeopleKind(ctx context.Context, v any) (*coredata.PeopleKind, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil

View File

@@ -30,6 +30,7 @@ type (
Resolver any Resolver any
ParentID gid.GID ParentID gid.GID
Filters *coredata.PeopleFilter
} }
) )
@@ -37,6 +38,7 @@ func NewPeopleConnection(
p *page.Page[*coredata.People, coredata.PeopleOrderField], p *page.Page[*coredata.People, coredata.PeopleOrderField],
parentType any, parentType any,
parentID gid.GID, parentID gid.GID,
filters *coredata.PeopleFilter,
) *PeopleConnection { ) *PeopleConnection {
var edges = make([]*PeopleEdge, len(p.Data)) var edges = make([]*PeopleEdge, len(p.Data))
@@ -50,6 +52,7 @@ func NewPeopleConnection(
Resolver: parentType, Resolver: parentType,
ParentID: parentID, ParentID: parentID,
Filters: filters,
} }
} }

View File

@@ -907,6 +907,10 @@ type PeopleEdge struct {
Node *People `json:"node"` Node *People `json:"node"`
} }
type PeopleFilter struct {
ExcludeContractEnded *bool `json:"excludeContractEnded,omitempty"`
}
type PublishDocumentVersionInput struct { type PublishDocumentVersionInput struct {
DocumentID gid.GID `json:"documentId"` DocumentID gid.GID `json:"documentId"`
Changelog *string `json:"changelog,omitempty"` Changelog *string `json:"changelog,omitempty"`

View File

@@ -2587,7 +2587,7 @@ func (r *organizationResolver) Vendors(ctx context.Context, obj *types.Organizat
} }
// Peoples is the resolver for the peoples field. // Peoples is the resolver for the peoples field.
func (r *organizationResolver) Peoples(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PeopleOrderBy) (*types.PeopleConnection, error) { func (r *organizationResolver) Peoples(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PeopleOrderBy, filter *types.PeopleFilter) (*types.PeopleConnection, error) {
prb := r.ProboService(ctx, obj.ID.TenantID()) prb := r.ProboService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.PeopleOrderField]{ pageOrderBy := page.OrderBy[coredata.PeopleOrderField]{
@@ -2603,12 +2603,17 @@ func (r *organizationResolver) Peoples(ctx context.Context, obj *types.Organizat
cursor := types.NewCursor(first, after, last, before, pageOrderBy) cursor := types.NewCursor(first, after, last, before, pageOrderBy)
page, err := prb.Peoples.ListForOrganizationID(ctx, obj.ID, cursor) var peopleFilter = coredata.NewPeopleFilter(nil)
if filter != nil {
peopleFilter = coredata.NewPeopleFilter(filter.ExcludeContractEnded)
}
page, err := prb.Peoples.ListForOrganizationID(ctx, obj.ID, cursor, peopleFilter)
if err != nil { if err != nil {
panic(fmt.Errorf("cannot list organization peoples: %w", err)) panic(fmt.Errorf("cannot list organization peoples: %w", err))
} }
return types.NewPeopleConnection(page, r, obj.ID), nil return types.NewPeopleConnection(page, r, obj.ID, peopleFilter), nil
} }
// Documents is the resolver for the documents field. // Documents is the resolver for the documents field.
@@ -2819,7 +2824,7 @@ func (r *peopleConnectionResolver) TotalCount(ctx context.Context, obj *types.Pe
switch obj.Resolver.(type) { switch obj.Resolver.(type) {
case *organizationResolver: case *organizationResolver:
count, err := prb.Peoples.CountForOrganizationID(ctx, obj.ParentID) count, err := prb.Peoples.CountForOrganizationID(ctx, obj.ParentID, obj.Filters)
if err != nil { if err != nil {
return 0, fmt.Errorf("cannot count peoples: %w", err) return 0, fmt.Errorf("cannot count peoples: %w", err)
} }