diff --git a/client/src/app/generated/server.model.graphql b/client/src/app/generated/server.model.graphql index ccaf05d19..3fbbe6840 100644 --- a/client/src/app/generated/server.model.graphql +++ b/client/src/app/generated/server.model.graphql @@ -92,6 +92,11 @@ interface ActivityInterface { The connection type for ActivityInterface. """ type ActivityInterfaceConnection { + """ + List of activity types that have occured on this entity. + """ + activityTypes: [ActivityTypeInput!]! + """ A list of edges. """ @@ -112,10 +117,27 @@ type ActivityInterfaceConnection { """ pageInfo: PageInfo! + """ + List of all organizations who are involved in this activity stream. + """ + participatingOrganizations: [Organization!]! + subjectTypes: [ActivitySubjectInput!]! + """ The total number of records in this filtered collection. """ totalCount: Int! + + """ + When filtered on a subject, user, or organization, the total number of events + for that subject/user/organization, irregardless of other filters. + """ + unfilteredCount: Int! + + """ + List of all users that have performed an activity on the subject entity. + """ + uniqueParticipants: [User!]! } """ @@ -133,6 +155,41 @@ type ActivityInterfaceEdge { node: ActivityInterface } +enum ActivitySubjectInput { + ASSERTION + EVIDENCE_ITEM + FEATURE + FLAG + MOLECULAR_PROFILE + REVISION + REVISION_SET + SOURCE + SOURCE_SUGGESTION + VARIANT + VARIANT_GROUP +} + +enum ActivityTypeInput { + ACCEPT_REVISIONS + COMMENT + CREATE_COMPLEX_MOLECULAR_PROFILE + CREATE_FEATURE + CREATE_VARIANT + DEPRECATE_COMPLEX_MOLECULAR_PROFILE + DEPRECATE_FEATURE + DEPRECATE_VARIANT + FLAG_ENTITY + MODERATE_ASSERTION + MODERATE_EVIDENCE_ITEM + REJECT_REVISIONS + RESOLVE_FLAG + SUBMIT_ASSERTION + SUBMIT_EVIDENCE_ITEM + SUGGEST_REVISION + SUGGEST_SOURCE + UPDATE_SOURCE_SUGGESTION +} + """ Autogenerated input type of AddComment """ @@ -2205,7 +2262,9 @@ enum EventAction { ASSERTION_SUBMITTED COMMENTED COMPLEX_MOLECULAR_PROFILE_CREATED + CREATED_FEATURE CURATED_SOURCE_SUGGESTION + DEPRECATED_FEATURE DEPRECATED_MOLECULAR_PROFILE DEPRECATED_VARIANT FLAGGED @@ -5553,6 +5612,8 @@ type Query { List and filter activities """ activities( + activityType: [ActivityTypeInput!] + """ Returns the elements in the list that come after the specified cursor. """ @@ -5572,7 +5633,16 @@ type Query { Returns the last _n_ elements from the list. """ last: Int - userId: Int + mode: EventFeedMode + organizationId: [Int!] + + """ + Sort order for the activities. Defaults to most recent. + """ + sortBy: DateSort + subject: [SubscribableQueryInput!] + subjectType: [ActivitySubjectInput!] + userId: [Int!] ): ActivityInterfaceConnection! """ diff --git a/client/src/app/generated/server.schema.json b/client/src/app/generated/server.schema.json index 78aaf0c7d..7b282f4a6 100644 --- a/client/src/app/generated/server.schema.json +++ b/client/src/app/generated/server.schema.json @@ -716,6 +716,30 @@ "name": "ActivityInterfaceConnection", "description": "The connection type for ActivityInterface.", "fields": [ + { + "name": "activityTypes", + "description": "List of activity types that have occured on this entity.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "ActivityTypeInput", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "edges", "description": "A list of edges.", @@ -796,6 +820,54 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "participatingOrganizations", + "description": "List of all organizations who are involved in this activity stream.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Organization", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subjectTypes", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "ActivitySubjectInput", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "totalCount", "description": "The total number of records in this filtered collection.", @@ -811,6 +883,46 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "unfilteredCount", + "description": "When filtered on a subject, user, or organization, the total number of events for that subject/user/organization, irregardless of other filters.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "uniqueParticipants", + "description": "List of all users that have performed an activity on the subject entity.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "User", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -857,6 +969,202 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "ENUM", + "name": "ActivitySubjectInput", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "FEATURE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VARIANT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "EVIDENCE_ITEM", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ASSERTION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "REVISION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SOURCE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SOURCE_SUGGESTION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VARIANT_GROUP", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MOLECULAR_PROFILE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FLAG", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "REVISION_SET", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "ActivityTypeInput", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "FLAG_ENTITY", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "RESOLVE_FLAG", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBMIT_EVIDENCE_ITEM", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBMIT_ASSERTION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MODERATE_ASSERTION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MODERATE_EVIDENCE_ITEM", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "DEPRECATE_VARIANT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUGGEST_SOURCE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "COMMENT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUGGEST_REVISION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UPDATE_SOURCE_SUGGESTION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "REJECT_REVISIONS", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ACCEPT_REVISIONS", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATE_VARIANT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "DEPRECATE_COMPLEX_MOLECULAR_PROFILE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATE_COMPLEX_MOLECULAR_PROFILE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATE_FEATURE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "DEPRECATE_FEATURE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "AddCommentInput", @@ -11382,6 +11690,18 @@ "description": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "DEPRECATED_FEATURE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATED_FEATURE", + "description": null, + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null @@ -26491,8 +26811,120 @@ "name": "userId", "description": null, "type": { - "kind": "SCALAR", - "name": "Int", + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "organizationId", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "activityType", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "ActivityTypeInput", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subjectType", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "ActivitySubjectInput", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "sortBy", + "description": "Sort order for the activities. Defaults to most recent.", + "type": { + "kind": "INPUT_OBJECT", + "name": "DateSort", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subject", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "SubscribableQueryInput", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mode", + "description": null, + "type": { + "kind": "ENUM", + "name": "EventFeedMode", "ofType": null }, "defaultValue": null, diff --git a/server/Gemfile.lock b/server/Gemfile.lock index 29e5882d2..270db5001 100644 --- a/server/Gemfile.lock +++ b/server/Gemfile.lock @@ -94,14 +94,14 @@ GEM awesome_nested_set (3.6.0) activerecord (>= 4.0.0, < 7.2) aws-eventstream (1.3.0) - aws-partitions (1.883.0) - aws-sdk-core (3.190.3) + aws-partitions (1.888.0) + aws-sdk-core (3.191.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.76.0) - aws-sdk-core (~> 3, >= 3.188.0) + aws-sdk-kms (1.77.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) aws-sdk-s3 (1.94.1) aws-sdk-core (~> 3, >= 3.112.0) @@ -115,7 +115,7 @@ GEM benchmark (0.3.0) bigdecimal (3.1.6) bindex (0.8.1) - bootsnap (1.17.1) + bootsnap (1.18.3) msgpack (~> 1.2) builder (3.2.4) byebug (11.1.3) @@ -132,11 +132,11 @@ GEM capistrano-rbenv (2.2.0) capistrano (~> 3.1) sshkit (~> 1.3) - capybara (3.39.2) + capybara (3.40.0) addressable matrix mini_mime (>= 0.1.3) - nokogiri (~> 1.8) + nokogiri (~> 1.11) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) @@ -146,10 +146,10 @@ GEM connection_pool (2.4.1) crass (1.0.6) date (3.3.4) - diff-lcs (1.5.0) + diff-lcs (1.5.1) diffy (3.4.2) docile (1.4.0) - dockerfile-rails (1.6.3) + dockerfile-rails (1.6.5) rails (>= 3.0.0) drb (2.2.0) ruby2_keywords @@ -200,9 +200,8 @@ GEM graphiql-rails (1.8.0) railties sprockets-rails - graphql (2.2.5) - racc (~> 1.4) - graphql-batch (0.5.3) + graphql (2.2.8) + graphql-batch (0.5.4) graphql (>= 1.12.18, < 3) promise.rb (~> 0.7.2) hashie (5.0.0) @@ -213,7 +212,7 @@ GEM mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) io-console (0.7.2) - irb (1.11.1) + irb (1.11.2) rdoc reline (>= 0.4.2) jaro_winkler (1.5.6) @@ -253,16 +252,16 @@ GEM method_source (1.0.0) mini_magick (4.12.0) mini_mime (1.1.5) - minitest (5.21.2) + minitest (5.22.2) msgpack (1.7.2) multi_json (1.15.0) multi_xml (0.6.0) - multipart-post (2.3.0) + multipart-post (2.4.0) mutex_m (0.2.0) net-ftp (0.3.4) net-protocol time - net-imap (0.4.9.1) + net-imap (0.4.10) date net-protocol net-pop (0.1.2) @@ -277,17 +276,17 @@ GEM net-protocol net-ssh (7.2.1) nio4r (2.7.0) - nokogiri (1.16.0-aarch64-linux) + nokogiri (1.16.2-aarch64-linux) racc (~> 1.4) - nokogiri (1.16.0-arm-linux) + nokogiri (1.16.2-arm-linux) racc (~> 1.4) - nokogiri (1.16.0-arm64-darwin) + nokogiri (1.16.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.0-x86-linux) + nokogiri (1.16.2-x86-linux) racc (~> 1.4) - nokogiri (1.16.0-x86_64-darwin) + nokogiri (1.16.2-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.0-x86_64-linux) + nokogiri (1.16.2-x86_64-linux) racc (~> 1.4) oauth2 (1.4.11) faraday (>= 0.17.3, < 3.0) @@ -480,7 +479,7 @@ GEM solargraph-rails (1.1.0) activesupport solargraph - solid_errors (0.3.4) + solid_errors (0.3.5) rails (~> 7.0) sprockets (4.2.1) concurrent-ruby (~> 1.0) @@ -529,7 +528,7 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) yard (0.9.34) - zeitwerk (2.6.12) + zeitwerk (2.6.13) PLATFORMS aarch64-linux @@ -601,4 +600,4 @@ RUBY VERSION ruby 3.3.0p0 BUNDLED WITH - 2.5.3 + 2.5.6 diff --git a/server/app/graphql/resolvers/activities.rb b/server/app/graphql/resolvers/activities.rb index ddb06089d..33b4deed9 100644 --- a/server/app/graphql/resolvers/activities.rb +++ b/server/app/graphql/resolvers/activities.rb @@ -13,10 +13,32 @@ class Activities < GraphQL::Schema::Resolver Activity.order(created_at: :desc).distinct end - option(:user_id, type: Int) do |scope, value| + option(:user_id, type: [Int]) do |scope, value| scope.where(user_id: value) end - end + option(:organization_id, type: [Int]) do |scope, value| + scope.where(organization_id: value) + end + + option(:activity_type, type: [Types::Activities::ActivityTypeInputType]) do |scope, value| + scope.where(type: value) + end + + option(:subject_type, type: [Types::Activities::ActivitySubjectInputType]) do |scope, value| + scope.where(subject_type: value) + end + option(:sort_by, type: Types::DateSortType, description: 'Sort order for the activities. Defaults to most recent.') do |scope, value| + scope.reorder("activities.#{value.column} #{value.direction}") + end + + option(:subject, type: [Types::Subscribable::SubscribableQueryInput]) do |scope, value| + scope.where(subject: value) + end + + option(:mode, type: Types::Events::EventFeedMode) do |_, _| + #accesed in connection, yuck + end + end end diff --git a/server/app/graphql/resolvers/top_level_events.rb b/server/app/graphql/resolvers/top_level_events.rb index 3f25f74d8..2c7f76398 100644 --- a/server/app/graphql/resolvers/top_level_events.rb +++ b/server/app/graphql/resolvers/top_level_events.rb @@ -4,13 +4,6 @@ class Resolvers::TopLevelEvents < GraphQL::Schema::Resolver include SearchObject.module(:graphql) - class EventFeedMode < Types::BaseEnum - description 'The context of an event feed, i.e. what is the root subject of the feed. This option is a no-op when accessing events via a parent.' - value 'USER', value: :user - value 'ORGANIZATION', value: :organization - value 'SUBJECT', value: :subject - value 'UNSCOPED', value: :unscoped - end type Types::Entities::EventType.connection_type, null: false @@ -50,7 +43,7 @@ class EventFeedMode < Types::BaseEnum end end - option(:mode, type: EventFeedMode) do |_, _| + option(:mode, type: Types::Events::EventFeedMode) do |_, _| #accesed in connection, yuck end end diff --git a/server/app/graphql/resolvers/top_level_evidence_items.rb b/server/app/graphql/resolvers/top_level_evidence_items.rb index 10d1e7d22..69f86a9fe 100644 --- a/server/app/graphql/resolvers/top_level_evidence_items.rb +++ b/server/app/graphql/resolvers/top_level_evidence_items.rb @@ -12,6 +12,7 @@ class Resolvers::TopLevelEvidenceItems < GraphQL::Schema::Resolver .all .order("evidence_level ASC, rating DESC, id ASC") .where.not(status: 'rejected') + .distinct } option(:id, type: GraphQL::Types::Int, description: 'Exact match filtering on the ID of the evidence item.') do |scope, value| diff --git a/server/app/graphql/types/activities/activity_subject_input_type.rb b/server/app/graphql/types/activities/activity_subject_input_type.rb new file mode 100644 index 000000000..9ee657c28 --- /dev/null +++ b/server/app/graphql/types/activities/activity_subject_input_type.rb @@ -0,0 +1,15 @@ +module Types::Activities + class ActivitySubjectInputType < Types::BaseEnum + value 'FEATURE', value: 'Feature' + value 'VARIANT', value: 'Variant' + value 'EVIDENCE_ITEM', value: 'EvidenceItem' + value 'ASSERTION', value: 'Assertion' + value 'REVISION', value: 'Revision' + value 'SOURCE', value: 'Source' + value 'SOURCE_SUGGESTION', value: 'SourceSuggestion' + value 'VARIANT_GROUP', value: 'VariantGroup' + value 'MOLECULAR_PROFILE', value: 'MolecularProfile' + value 'FLAG', value: 'Flag' + value 'REVISION_SET', value: 'RevisionSet' + end +end diff --git a/server/app/graphql/types/activities/activity_type_input_type.rb b/server/app/graphql/types/activities/activity_type_input_type.rb new file mode 100644 index 000000000..ad675b64a --- /dev/null +++ b/server/app/graphql/types/activities/activity_type_input_type.rb @@ -0,0 +1,22 @@ +module Types::Activities + class ActivityTypeInputType < Types::BaseEnum + value 'FLAG_ENTITY', value: 'FlagEntityActivity' + value 'RESOLVE_FLAG', value: 'ResolveFlagActivity' + value 'SUBMIT_EVIDENCE_ITEM', value: 'SubmitEvidenceItemActivity' + value 'SUBMIT_ASSERTION', value: 'SubmitAssertionActivity' + value 'MODERATE_ASSERTION', value: 'ModerateAssertionActivity' + value 'MODERATE_EVIDENCE_ITEM', value: 'ModerateEvidenceItemActivity' + value 'DEPRECATE_VARIANT', value: 'DeprecateVariantActivity' + value 'SUGGEST_SOURCE', value: 'SuggestSourceActivity' + value 'COMMENT', value: 'CommentActivity' + value 'SUGGEST_REVISION', value: 'SuggestRevisionSetActivity' + value 'UPDATE_SOURCE_SUGGESTION', value: 'UpdateSourceSuggestionStatusActivity' + value 'REJECT_REVISIONS', value: 'RejectRevisionsActivity' + value 'ACCEPT_REVISIONS', value: 'AcceptRevisionsActivity' + value 'CREATE_VARIANT', value: 'CreateVariantActivity' + value 'DEPRECATE_COMPLEX_MOLECULAR_PROFILE', value: 'DeprecateComplexMolecularProfileActivity' + value 'CREATE_COMPLEX_MOLECULAR_PROFILE', value: 'CreateComplexMolecularProfileActivity' + value 'CREATE_FEATURE', value: 'CreateFeatureActivity' + value 'DEPRECATE_FEATURE', value: 'DeprecateFeatureActivity' + end +end diff --git a/server/app/graphql/types/connections/activities_connection.rb b/server/app/graphql/types/connections/activities_connection.rb new file mode 100644 index 000000000..fdf726767 --- /dev/null +++ b/server/app/graphql/types/connections/activities_connection.rb @@ -0,0 +1,86 @@ +module Types::Connections + class ActivitiesConnection < Types::BaseConnection + description 'Connection type for the activity feed' + + field :unique_participants, [Types::Entities::UserType], null: false, + description: 'List of all users that have performed an activity on the subject entity.' + + field :participating_organizations, [Types::Entities::OrganizationType], null: false, + description: 'List of all organizations who are involved in this activity stream.' + + field :unfiltered_count, Int, null: false, + description: 'When filtered on a subject, user, or organization, the total number of events for that subject/user/organization, irregardless of other filters.' + + field :activity_types, [Types::Activities::ActivityTypeInputType], null: false, + description: 'List of activity types that have occured on this entity.' + + field :subject_types, [Types::Activities::ActivitySubjectInputType], null: false + + def unique_participants + #Users who's originating ids appear in the activities query, + #joined to activities table, limited to only activities in the activities query + #select the user id and the time of the newest relevant activity + ranked_user_ids = User.where(id: unscoped_activities_base_query.select(:user_id)) + .joins(:activities) + .where(activities: { id: unscoped_activities_base_query.select(:id) } ) + .select("users.id as user_id, max(activities.created_at) newest_activity_timestamp") + .group("users.id") + + + #users, ranked by most recent relevant event + User.joins("INNER JOIN (#{ranked_user_ids.to_sql}) ordered ON ordered.user_id = users.id") + .order('ordered.newest_activity_timestamp desc') + .limit(15) + end + + def participating_organizations + Organization.where(id: + unscoped_activities_base_query.select(:organization_id) + ).distinct + end + + def unfiltered_count + unscoped_activities_base_query.distinct.count + end + + def activity_types + unscoped_activities_base_query.distinct.pluck(:type) + end + + def subject_types + unscoped_activities_base_query.distinct.pluck(:subject_type) + end + + private + def unscoped_activities_base_query + @unscoped_base ||= unscoped_activities + end + + def unscoped_activities + if feed_mode = object.arguments[:mode] + if feed_mode == :user + if !object.arguments[:user_id] + raise GraphQL::ExecutionError, "Must provide a user id when activity feed is in User mode." + end + return Activity.where(user_id: object.arguments[:user_id]) + elsif feed_mode == :organization + if !object.arguments[:organization_id] + raise GraphQL::ExecutionError, "Must provide an organization id when activity feed is in Organization mode." + end + return Activity.where(organization_id: object.arguments[:organization_id]) + elsif feed_mode == :unscoped + Activity.all + #subject mode + else + if !object.arguments[:subject] + raise GraphQL::ExecutionError, "Must provide a subject when activity feed is in Subject mode." + end + return Activity.where(subject: object.arguments[:subject]) + end + end + + #no mode,no parent, unscoped + return Activity.all + end + end +end diff --git a/server/app/graphql/types/events/event_action_type.rb b/server/app/graphql/types/events/event_action_type.rb index 6efb42d27..25e48b3fd 100644 --- a/server/app/graphql/types/events/event_action_type.rb +++ b/server/app/graphql/types/events/event_action_type.rb @@ -23,6 +23,8 @@ class EventActionType < Types::BaseEnum value 'DEPRECATED_MOLECULAR_PROFILE', value: 'deprecated molecular profile' value 'VARIANT_CREATED', value: 'variant created' value 'COMPLEX_MOLECULAR_PROFILE_CREATED', value: 'complex molecular profile created' + value 'DEPRECATED_FEATURE', value: 'deprecated feature' + value 'CREATED_FEATURE', value: 'created feature' end end diff --git a/server/app/graphql/types/events/event_feed_mode.rb b/server/app/graphql/types/events/event_feed_mode.rb new file mode 100644 index 000000000..fab31ec57 --- /dev/null +++ b/server/app/graphql/types/events/event_feed_mode.rb @@ -0,0 +1,9 @@ +module Types::Events + class EventFeedMode < Types::BaseEnum + description 'The context of an event feed, i.e. what is the root subject of the feed. This option is a no-op when accessing events via a parent.' + value 'USER', value: :user + value 'ORGANIZATION', value: :organization + value 'SUBJECT', value: :subject + value 'UNSCOPED', value: :unscoped + end +end diff --git a/server/app/graphql/types/interfaces/activity_interface.rb b/server/app/graphql/types/interfaces/activity_interface.rb index 54b9be6ec..a93ccaa59 100644 --- a/server/app/graphql/types/interfaces/activity_interface.rb +++ b/server/app/graphql/types/interfaces/activity_interface.rb @@ -1,6 +1,7 @@ module Types::Interfaces module ActivityInterface include Types::BaseInterface + connection_type_class Types::Connections::ActivitiesConnection description 'An activity done by a curator or editor' diff --git a/server/app/jobs/set_allele_registry_id_single_variant.rb b/server/app/jobs/set_allele_registry_id_single_variant.rb index eaa760866..49149d312 100644 --- a/server/app/jobs/set_allele_registry_id_single_variant.rb +++ b/server/app/jobs/set_allele_registry_id_single_variant.rb @@ -21,5 +21,6 @@ def perform(variant) delete_allele_registry_link(old_allele_registry_id) end end + GenerateOpenCravatLink.perform_later(self) end end diff --git a/server/app/models/frontend_router.rb b/server/app/models/frontend_router.rb index f65d5be38..49a74eaea 100644 --- a/server/app/models/frontend_router.rb +++ b/server/app/models/frontend_router.rb @@ -8,11 +8,14 @@ def initialize(id_type, id, domain) end def url - (entity, query_field) = query_info + (entity, query_field, transform) = query_info if [entity, query_field, id].any? { |i| i.blank? } nil else obj = entity.find_by!(query_field => id) + #identity function if none defined + transform ||= -> {_1} + obj = entity.find_by!(query_field => transform.call(id)) adaptor = "LinkAdaptors::#{obj.class.to_s.demodulize}".constantize.new(obj) "#{domain}#{adaptor.base_path}" end @@ -22,8 +25,8 @@ def url def query_info case id_type when /genes?/ - [ - Feature.where(feature_instance_type: "Features::Gene"), :feature_instance_id, + [ + Feature.where(feature_instance_type: "Features::Gene"), :feature_instance_id, ] when /features?/ [ Feature, :id, ] @@ -36,8 +39,8 @@ def query_info when /entrez_id/ [ Features::Gene, :entrez_id ] when /entrez_name/ - [ - Feature.where(feature_instance_type: "Features::Gene"), :name, + [ + Feature.where(feature_instance_type: "Features::Gene"), :name, -> { _1.upcase } ] when /variant_groups?/ [ VariantGroup, :id, ] diff --git a/server/app/models/user.rb b/server/app/models/user.rb index d3300a289..27d2ab446 100644 --- a/server/app/models/user.rb +++ b/server/app/models/user.rb @@ -9,6 +9,9 @@ class User < ActiveRecord::Base has_many :events, ->() { order('events.created_at DESC') }, foreign_key: :originating_user_id + has_many :activities, + ->() { order('activities.created_at DESC') } + has_one :most_recent_event, ->() { order('created_at DESC').limit(1) }, diff --git a/server/app/models/variant.rb b/server/app/models/variant.rb index 604a0927f..928c21ec1 100644 --- a/server/app/models/variant.rb +++ b/server/app/models/variant.rb @@ -102,7 +102,6 @@ def reindex_mps def on_revision_accepted SetAlleleRegistryIdSingleVariant.perform_later(self) if Rails.env.production? - GenerateOpenCravatLink.perform_later(self) update_single_variant_mp_aliases end