Browse Source

Maintenance: Improve member handling for organization graphql queries.

Martin Gruner 3 days ago
parent
commit
b5dc16bf99

+ 23 - 0
app/frontend/shared/graphql/types.ts

@@ -1848,6 +1848,8 @@ export type OnlineNotificationsCountPayload = {
 export type Organization = ObjectAttributeValuesInterface & {
   __typename?: 'Organization';
   active?: Maybe<Scalars['Boolean']['output']>;
+  /** All assigned users */
+  allMembers?: Maybe<UserConnection>;
   /** Create date/time of the record */
   createdAt: Scalars['ISO8601DateTime']['output'];
   /** User that created this record */
@@ -1857,12 +1859,15 @@ export type Organization = ObjectAttributeValuesInterface & {
   id: Scalars['ID']['output'];
   /** Internal database ID */
   internalId: Scalars['Int']['output'];
+  /** Users assigned via primary organization */
   members?: Maybe<UserConnection>;
   name?: Maybe<Scalars['String']['output']>;
   /** Internal note */
   note?: Maybe<Scalars['String']['output']>;
   objectAttributeValues?: Maybe<Array<ObjectAttributeValue>>;
   policy: PolicyDefault;
+  /** Users assigned via secondary organization */
+  secondaryMembers?: Maybe<UserConnection>;
   shared?: Maybe<Scalars['Boolean']['output']>;
   ticketsCount?: Maybe<TicketCount>;
   /** Last update date/time of the record */
@@ -1873,6 +1878,15 @@ export type Organization = ObjectAttributeValuesInterface & {
 };
 
 
+/** Organizations that users can belong to */
+export type OrganizationAllMembersArgs = {
+  after?: InputMaybe<Scalars['String']['input']>;
+  before?: InputMaybe<Scalars['String']['input']>;
+  first?: InputMaybe<Scalars['Int']['input']>;
+  last?: InputMaybe<Scalars['Int']['input']>;
+};
+
+
 /** Organizations that users can belong to */
 export type OrganizationMembersArgs = {
   after?: InputMaybe<Scalars['String']['input']>;
@@ -1881,6 +1895,15 @@ export type OrganizationMembersArgs = {
   last?: InputMaybe<Scalars['Int']['input']>;
 };
 
+
+/** Organizations that users can belong to */
+export type OrganizationSecondaryMembersArgs = {
+  after?: InputMaybe<Scalars['String']['input']>;
+  before?: InputMaybe<Scalars['String']['input']>;
+  first?: InputMaybe<Scalars['Int']['input']>;
+  last?: InputMaybe<Scalars['Int']['input']>;
+};
+
 /** The connection type for Organization. */
 export type OrganizationConnection = {
   __typename?: 'OrganizationConnection';

+ 30 - 0
app/frontend/tests/graphql/factories/Organization.ts

@@ -24,11 +24,41 @@ export default (parent: any): DeepPartial<Organization> => {
       },
       totalCount: 1,
     }
+
+    organization.secondaryMembers = {
+      edges: [{ __typename: 'UserEdge', node: parent, cursor: 'AB' }],
+      pageInfo: {
+        endCursor: 'AB',
+        startCursor: 'AB',
+        hasNextPage: false,
+        hasPreviousPage: false,
+      },
+      totalCount: 1,
+    }
+
+    organization.allMembers = {
+      edges: [{ __typename: 'UserEdge', node: parent, cursor: 'AB' }],
+      pageInfo: {
+        endCursor: 'AB',
+        startCursor: 'AB',
+        hasNextPage: false,
+        hasPreviousPage: false,
+      },
+      totalCount: 2,
+    }
   } else {
     organization.members = {
       edges: [],
       totalCount: 0,
     }
+    organization.secondaryMembers = {
+      edges: [],
+      totalCount: 0,
+    }
+    organization.allMembers = {
+      edges: [],
+      totalCount: 0,
+    }
   }
   return organization
 }

+ 3 - 1
app/graphql/gql/types/organization_type.rb

@@ -19,7 +19,9 @@ module Gql::Types
       field :vip, Boolean
       field :domain, String
       field :domain_assignment, Boolean
-      field :members, Gql::Types::UserType.connection_type
+      field :members, Gql::Types::UserType.connection_type, description: 'Users assigned via primary organization'
+      field :secondary_members, Gql::Types::UserType.connection_type, description: 'Users assigned via secondary organization'
+      field :all_members, Gql::Types::UserType.connection_type, description: 'All assigned users'
       field :tickets_count, Gql::Types::TicketCountType, method: :itself
     end
 

+ 107 - 1
app/graphql/graphql_introspection.json

@@ -10713,6 +10713,59 @@
               "isDeprecated": false,
               "deprecationReason": null
             },
+            {
+              "name": "allMembers",
+              "description": "All assigned users",
+              "args": [
+                {
+                  "name": "after",
+                  "description": "Returns the elements in the list that come after the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "before",
+                  "description": "Returns the elements in the list that come before the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "first",
+                  "description": "Returns the first _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "last",
+                  "description": "Returns the last _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                }
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "UserConnection",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
             {
               "name": "createdAt",
               "description": "Create date/time of the record",
@@ -10811,7 +10864,7 @@
             },
             {
               "name": "members",
-              "description": null,
+              "description": "Users assigned via primary organization",
               "args": [
                 {
                   "name": "after",
@@ -10930,6 +10983,59 @@
               "isDeprecated": false,
               "deprecationReason": null
             },
+            {
+              "name": "secondaryMembers",
+              "description": "Users assigned via secondary organization",
+              "args": [
+                {
+                  "name": "after",
+                  "description": "Returns the elements in the list that come after the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "before",
+                  "description": "Returns the elements in the list that come before the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "first",
+                  "description": "Returns the first _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "last",
+                  "description": "Returns the last _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                }
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "UserConnection",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
             {
               "name": "shared",
               "description": null,

+ 6 - 0
app/models/organization.rb

@@ -50,6 +50,12 @@ class Organization < ApplicationModel
   validates :note, length: { maximum: 5000 }
   sanitized_html :note, no_images: true
 
+  def all_members
+    User
+      .left_outer_joins(:organization, :organizations_users)
+      .where('organizations.id = :id OR organizations_users.organization_id = :id', id:)
+  end
+
   def destroy(associations: false)
     if associations
       delete_associations

+ 9 - 0
spec/models/organization_spec.rb

@@ -242,4 +242,13 @@ RSpec.describe Organization, type: :model do
     end
 
   end
+
+  describe '#all_members' do
+    let!(:primary_user) { create(:user, organization:) }
+    let!(:secondary_user) { create(:user, organization: create(:organization), organizations: [organization]) }
+
+    it 'lists all assigned members' do
+      expect(organization.all_members).to contain_exactly(primary_user, secondary_user)
+    end
+  end
 end