curation_concerns 1.6.3 → 1.7.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (236) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/.travis.yml +2 -8
  4. data/README.md +27 -1
  5. data/app/actors/curation_concerns/actors/base_actor.rb +2 -3
  6. data/app/actors/curation_concerns/actors/file_set_actor.rb +1 -1
  7. data/app/actors/curation_concerns/actors/grant_edit_to_depositor_actor.rb +19 -0
  8. data/app/actors/curation_concerns/actors/initialize_workflow_actor.rb +19 -0
  9. data/app/assets/javascripts/curation_concerns/charts.js +47 -0
  10. data/app/assets/javascripts/curation_concerns/curation_concerns.js +2 -0
  11. data/app/assets/javascripts/curation_concerns/workflow_actions_affix.js +11 -0
  12. data/app/assets/stylesheets/curation_concerns/_admin.scss +18 -0
  13. data/app/assets/stylesheets/curation_concerns/_curation_concerns.scss +1 -0
  14. data/app/assets/stylesheets/curation_concerns/_positioning.scss +11 -0
  15. data/app/controllers/concerns/curation_concerns/act_as_admin_controller.rb +25 -0
  16. data/app/controllers/concerns/curation_concerns/admin_controller_behavior.rb +38 -0
  17. data/app/controllers/concerns/curation_concerns/admin_page.rb +18 -0
  18. data/app/controllers/concerns/curation_concerns/collections_controller_behavior.rb +3 -2
  19. data/app/controllers/concerns/curation_concerns/curation_concern_controller.rb +158 -159
  20. data/app/controllers/concerns/curation_concerns/embargoes_controller_behavior.rb +4 -6
  21. data/app/controllers/concerns/curation_concerns/file_sets_controller_behavior.rb +2 -1
  22. data/app/controllers/concerns/curation_concerns/leases_controller_behavior.rb +4 -6
  23. data/app/controllers/concerns/curation_concerns/single_use_links_viewer_controller_behavior.rb +2 -1
  24. data/app/controllers/curation_concerns/admin/workflow_roles_controller.rb +36 -0
  25. data/app/controllers/curation_concerns/workflow_actions_controller.rb +25 -0
  26. data/app/conversions/power_converters/polymorphic_type.rb +7 -0
  27. data/app/conversions/power_converters/sipity_action.rb +9 -0
  28. data/app/conversions/power_converters/sipity_action_name.rb +8 -0
  29. data/app/conversions/power_converters/sipity_agent.rb +6 -0
  30. data/app/conversions/power_converters/sipity_entity.rb +12 -0
  31. data/app/conversions/power_converters/sipity_role.rb +8 -0
  32. data/app/conversions/power_converters/sipity_workflow_id.rb +16 -0
  33. data/app/conversions/power_converters/sipity_workflow_state.rb +10 -0
  34. data/app/conversions/power_converters.rb +6 -0
  35. data/app/forms/curation_concerns/forms/workflow_action_form.rb +62 -0
  36. data/app/forms/curation_concerns/forms/workflow_responsibility_form.rb +42 -0
  37. data/app/helpers/curation_concerns/charts_helper.rb +23 -0
  38. data/app/helpers/curation_concerns/main_app_helpers.rb +1 -0
  39. data/app/helpers/curation_concerns/url_helper.rb +2 -5
  40. data/app/indexers/curation_concerns/indexes_workflow.rb +44 -0
  41. data/app/indexers/curation_concerns/work_indexer.rb +1 -2
  42. data/app/jobs/import_export_job.rb +46 -0
  43. data/app/models/concerns/curation_concerns/ability.rb +2 -0
  44. data/app/models/concerns/curation_concerns/permissions/writable.rb +7 -8
  45. data/app/models/concerns/curation_concerns/publishable.rb +6 -0
  46. data/app/models/concerns/curation_concerns/solr_document_behavior.rb +8 -0
  47. data/app/models/concerns/curation_concerns/user.rb +5 -0
  48. data/app/models/curation_concerns/group.rb +23 -0
  49. data/app/models/curation_concerns/workflow_action_info.rb +13 -0
  50. data/app/models/sipity/agent.rb +21 -0
  51. data/app/models/sipity/comment.rb +13 -0
  52. data/app/models/sipity/entity.rb +32 -0
  53. data/app/models/sipity/entity_specific_responsibility.rb +21 -0
  54. data/app/models/sipity/method.rb +5 -0
  55. data/app/models/sipity/notifiable_context.rb +26 -0
  56. data/app/models/sipity/notification.rb +28 -0
  57. data/app/models/sipity/notification_recipient.rb +17 -0
  58. data/app/models/sipity/role.rb +43 -0
  59. data/app/models/sipity/workflow.rb +19 -0
  60. data/app/models/sipity/workflow_action.rb +25 -0
  61. data/app/models/sipity/workflow_responsibility.rb +23 -0
  62. data/app/models/sipity/workflow_role.rb +13 -0
  63. data/app/models/sipity/workflow_state.rb +26 -0
  64. data/app/models/sipity/workflow_state_action.rb +9 -0
  65. data/app/models/sipity/workflow_state_action_permission.rb +8 -0
  66. data/app/models/vocab/fedora_resource_status.rb +4 -1
  67. data/app/presenters/curation_concerns/admin/workflow_role_presenter.rb +40 -0
  68. data/app/presenters/curation_concerns/work_show_presenter.rb +4 -0
  69. data/app/presenters/curation_concerns/workflow_presenter.rb +43 -0
  70. data/app/renderers/curation_concerns/renderers/rights_attribute_renderer.rb +1 -1
  71. data/app/search_builders/curation_concerns/search_filters.rb +0 -1
  72. data/app/services/curation_concerns/actors/actor_factory.rb +3 -1
  73. data/app/services/curation_concerns/admin_set_service.rb +13 -4
  74. data/app/services/curation_concerns/work_form_service.rb +11 -0
  75. data/app/services/curation_concerns/workflow/action_taken_service.rb +57 -0
  76. data/app/services/curation_concerns/workflow/activate_object.rb +9 -0
  77. data/app/services/curation_concerns/workflow/deactivate_object.rb +11 -0
  78. data/app/services/curation_concerns/workflow/default_workflow_strategy.rb +13 -0
  79. data/app/services/curation_concerns/workflow/method_generator.rb +55 -0
  80. data/app/services/curation_concerns/workflow/notification_configuration_parameter.rb +42 -0
  81. data/app/services/curation_concerns/workflow/notification_generator.rb +64 -0
  82. data/app/services/curation_concerns/workflow/notification_service.rb +66 -0
  83. data/app/services/curation_concerns/workflow/permission_generator.rb +95 -0
  84. data/app/services/curation_concerns/workflow/permission_query.rb +432 -0
  85. data/app/services/curation_concerns/workflow/remove_depositor_permissions.rb +11 -0
  86. data/app/services/curation_concerns/workflow/sipity_actions_generator.rb +49 -0
  87. data/app/services/curation_concerns/workflow/state_machine_generator.rb +102 -0
  88. data/app/services/curation_concerns/workflow/status_list_service.rb +52 -0
  89. data/app/services/curation_concerns/workflow/workflow_action_service.rb +56 -0
  90. data/app/services/curation_concerns/workflow/workflow_by_model_name_strategy.rb +14 -0
  91. data/app/services/curation_concerns/workflow/workflow_factory.rb +64 -0
  92. data/app/services/curation_concerns/workflow/workflow_importer.rb +103 -0
  93. data/app/services/curation_concerns/workflow/workflow_permissions_generator.rb +49 -0
  94. data/app/services/curation_concerns/workflow/workflow_schema.rb +56 -0
  95. data/app/services/rights_service.rb +1 -1
  96. data/app/services/sipity.rb +2 -0
  97. data/app/sources/curation_concerns/resource_statistics_source.rb +101 -0
  98. data/app/values/curation_concerns/chart_data.rb +61 -0
  99. data/app/views/curation_concerns/admin/_admin_menu.html.erb +5 -0
  100. data/app/views/curation_concerns/admin/_resource_stats.html.erb +43 -0
  101. data/app/views/curation_concerns/admin/_sidebar.html.erb +3 -0
  102. data/app/views/curation_concerns/admin/_total_embargo_visibility.html.erb +21 -0
  103. data/app/views/curation_concerns/admin/_total_objects.html.erb +1 -0
  104. data/app/views/curation_concerns/admin/_total_objects_charts.html.erb +12 -0
  105. data/app/views/curation_concerns/admin/index.html.erb +11 -0
  106. data/app/views/curation_concerns/admin/widgets/_pie.html.erb +1 -0
  107. data/app/views/curation_concerns/admin/workflow.html.erb +31 -0
  108. data/app/views/curation_concerns/admin/workflow_roles/index.html.erb +45 -0
  109. data/app/views/curation_concerns/base/_form_rights.html.erb +1 -1
  110. data/app/views/curation_concerns/base/_workflow_actions.html.erb +39 -0
  111. data/app/views/curation_concerns/base/show.html.erb +2 -0
  112. data/app/views/curation_concerns/file_sets/media_display/_office_document.html.erb +0 -1
  113. data/app/views/layouts/_head_tag_content.html.erb +13 -0
  114. data/app/views/layouts/admin.html.erb +10 -0
  115. data/app/views/shared/_my_actions.html.erb +4 -1
  116. data/config/initializers/precompile_assets.rb +1 -0
  117. data/config/locales/curation_concerns.en.yml +97 -56
  118. data/config/routes.rb +7 -0
  119. data/curation_concerns.gemspec +9 -3
  120. data/db/migrate/20160919151348_create_sipity.rb +163 -0
  121. data/db/migrate/20161012182404_create_sipity_workflow_methods.rb +10 -0
  122. data/lib/curation_concerns/configuration.rb +68 -0
  123. data/lib/curation_concerns/engine.rb +9 -1
  124. data/lib/curation_concerns/rails/routes.rb +1 -0
  125. data/lib/curation_concerns/version.rb +1 -1
  126. data/lib/curation_concerns.rb +1 -1
  127. data/lib/generators/curation_concerns/admin_dashboard_generator.rb +15 -0
  128. data/lib/generators/curation_concerns/install_generator.rb +7 -0
  129. data/lib/generators/curation_concerns/sample_data_generator.rb +12 -0
  130. data/lib/generators/curation_concerns/templates/app/controllers/curation_concerns/admin_controller.rb +61 -0
  131. data/lib/generators/curation_concerns/templates/catalog_controller.rb +1 -3
  132. data/lib/generators/curation_concerns/templates/config/curation_concerns.rb +39 -0
  133. data/lib/generators/curation_concerns/templates/db/seeds.rb +94 -0
  134. data/lib/generators/curation_concerns/templates/spec/models/collection_spec.rb +1 -1
  135. data/lib/generators/curation_concerns/templates/spec/models/file_set_spec.rb +1 -1
  136. data/lib/generators/curation_concerns/templates/workflow.json.erb +19 -0
  137. data/lib/generators/curation_concerns/work/USAGE +1 -0
  138. data/lib/generators/curation_concerns/work/templates/actor_spec.rb.erb +1 -1
  139. data/lib/generators/curation_concerns/work/templates/controller_spec.rb.erb +1 -1
  140. data/lib/generators/curation_concerns/work/templates/feature_spec.rb.erb +1 -1
  141. data/lib/generators/curation_concerns/work/templates/form_spec.rb.erb +1 -1
  142. data/lib/generators/curation_concerns/work/templates/model_spec.rb.erb +1 -1
  143. data/spec/abilities/admin_ability_spec.rb +15 -0
  144. data/spec/actors/curation_concerns/file_set_actor_spec.rb +24 -0
  145. data/spec/actors/curation_concerns/grant_edit_to_depositor_actor_spec.rb +32 -0
  146. data/spec/actors/curation_concerns/initialize_workflow_actor_spec.rb +33 -0
  147. data/spec/actors/curation_concerns/interpret_visibility_actor_spec.rb +1 -0
  148. data/spec/actors/curation_concerns/work_actor_spec.rb +10 -5
  149. data/spec/controllers/curation_concerns/acts_as_admin_controller_spec.rb +22 -0
  150. data/spec/controllers/curation_concerns/admin/workflow_roles_controller_spec.rb +26 -0
  151. data/spec/controllers/curation_concerns/admin_controller_spec.rb +64 -0
  152. data/spec/controllers/curation_concerns/generic_works_controller_json_spec.rb +11 -3
  153. data/spec/controllers/curation_concerns/generic_works_controller_spec.rb +4 -4
  154. data/spec/controllers/curation_concerns/workflow_actions_controller_spec.rb +36 -0
  155. data/spec/conversions/power_converters/polymorphic_type_spec.rb +22 -0
  156. data/spec/conversions/power_converters/sipity_action_name_spec.rb +32 -0
  157. data/spec/conversions/power_converters/sipity_action_spec.rb +43 -0
  158. data/spec/conversions/power_converters/sipity_agent_spec.rb +20 -0
  159. data/spec/conversions/power_converters/sipity_entity_spec.rb +46 -0
  160. data/spec/conversions/power_converters/sipity_role_spec.rb +33 -0
  161. data/spec/conversions/power_converters/sipity_workflow_id_spec.rb +28 -0
  162. data/spec/conversions/power_converters/sipity_workflow_state_spec.rb +22 -0
  163. data/spec/factories/generic_works.rb +19 -6
  164. data/spec/factories/sipity_entities.rb +7 -0
  165. data/spec/factories/workflow_actions.rb +6 -0
  166. data/spec/factories/workflow_states.rb +6 -0
  167. data/spec/factories/workflows.rb +5 -0
  168. data/spec/features/admin_spec.rb +14 -0
  169. data/spec/features/create_child_work_spec.rb +1 -1
  170. data/spec/features/create_work_spec.rb +1 -1
  171. data/spec/features/embargo_spec.rb +1 -1
  172. data/spec/features/lease_spec.rb +1 -1
  173. data/spec/features/work_generator_spec.rb +2 -2
  174. data/spec/features/workflow_roles_spec.rb +48 -0
  175. data/spec/forms/curation_concerns/forms/workflow_action_form_spec.rb +111 -0
  176. data/spec/forms/curation_concerns/forms/workflow_responsibility_form_spec.rb +33 -0
  177. data/spec/helpers/curation_concerns/charts_helper_spec.rb +62 -0
  178. data/spec/helpers/url_helper_spec.rb +11 -11
  179. data/spec/indexers/work_indexer_spec.rb +13 -1
  180. data/spec/javascripts/charts_spec.coffee +10 -0
  181. data/spec/javascripts/fixtures/chart_example.html +1 -0
  182. data/spec/jobs/import_export_job_spec.rb +28 -0
  183. data/spec/models/curation_concerns/group_spec.rb +15 -0
  184. data/spec/models/file_set_spec.rb +6 -2
  185. data/spec/models/generic_work_spec.rb +12 -0
  186. data/spec/models/sipity/agent_spec.rb +9 -0
  187. data/spec/models/sipity/comment_spec.rb +16 -0
  188. data/spec/models/sipity/entity_spec.rb +27 -0
  189. data/spec/models/sipity/entity_specific_responsibility_spec.rb +8 -0
  190. data/spec/models/sipity/notifiable_context_spec.rb +8 -0
  191. data/spec/models/sipity/notification_recipient_spec.rb +10 -0
  192. data/spec/models/sipity/notification_spec.rb +15 -0
  193. data/spec/models/sipity/role_spec.rb +30 -0
  194. data/spec/models/sipity/workflow_action_spec.rb +10 -0
  195. data/spec/models/sipity/workflow_responsibility_spec.rb +7 -0
  196. data/spec/models/sipity/workflow_role_spec.rb +7 -0
  197. data/spec/models/sipity/workflow_spec.rb +15 -0
  198. data/spec/models/sipity/workflow_state_action_permission_spec.rb +7 -0
  199. data/spec/models/sipity/workflow_state_action_spec.rb +7 -0
  200. data/spec/models/sipity/workflow_state_spec.rb +7 -0
  201. data/spec/models/user_spec.rb +12 -1
  202. data/spec/presenters/curation_concerns/work_show_presenter_spec.rb +12 -1
  203. data/spec/presenters/curation_concerns/workflow_presenter_spec.rb +39 -0
  204. data/spec/routing/route_spec.rb +13 -0
  205. data/spec/services/curation_concerns/admin_set_service_spec.rb +13 -0
  206. data/spec/services/curation_concerns/qa_select_service_spec.rb +75 -0
  207. data/spec/services/curation_concerns/workflow/action_taken_service_spec.rb +76 -0
  208. data/spec/services/curation_concerns/workflow/activate_object_spec.rb +20 -0
  209. data/spec/services/curation_concerns/workflow/deactivate_object_spec.rb +20 -0
  210. data/spec/services/curation_concerns/workflow/method_generator_spec.rb +43 -0
  211. data/spec/services/curation_concerns/workflow/notification_configuration_parameter_spec.rb +25 -0
  212. data/spec/services/curation_concerns/workflow/notification_generator_spec.rb +27 -0
  213. data/spec/services/curation_concerns/workflow/notification_service_spec.rb +81 -0
  214. data/spec/services/curation_concerns/workflow/permission_generator_spec.rb +52 -0
  215. data/spec/services/curation_concerns/workflow/permission_query_spec.rb +179 -0
  216. data/spec/services/curation_concerns/workflow/remove_depositor_permissions_spec.rb +21 -0
  217. data/spec/services/curation_concerns/workflow/sipity_actions_generator_spec.rb +30 -0
  218. data/spec/services/curation_concerns/workflow/state_machine_generator_spec.rb +46 -0
  219. data/spec/services/curation_concerns/workflow/status_list_service_spec.rb +46 -0
  220. data/spec/services/curation_concerns/workflow/workflow_importer_spec.rb +50 -0
  221. data/spec/services/curation_concerns/workflow/workflow_permissions_generator_spec.rb +37 -0
  222. data/spec/services/curation_concerns/workflow/workflow_schema_spec.rb +85 -0
  223. data/spec/sources/curation_concerns/resource_statistics_source_spec.rb +137 -0
  224. data/spec/spec_helper.rb +8 -1
  225. data/spec/test_app_templates/lib/generators/test_app_generator.rb +11 -0
  226. data/spec/views/curation_concerns/admin/_admin_menu.html.erb_spec.rb +23 -0
  227. data/spec/views/curation_concerns/admin/_resource_stats.html.erb_spec.rb +33 -0
  228. data/spec/views/curation_concerns/admin/_total_objects_charts.html.erb_spec.rb +28 -0
  229. data/spec/views/curation_concerns/admin/index.html.erb_spec.rb +26 -0
  230. data/spec/views/curation_concerns/admin/widgets/_pie.html.erb_spec.rb +17 -0
  231. data/spec/views/curation_concerns/base/file_manager.html.erb_spec.rb +1 -1
  232. data/spec/views/curation_concerns/base/show.html.erb_spec.rb +33 -0
  233. data/spec/views/curation_concerns/permissions/confirm.html.erb_spec.rb +1 -1
  234. data/spec/views/shared/_my_actions.html.erb_spec.rb +1 -0
  235. data/tasks/workflow.rake +8 -0
  236. metadata +335 -21
@@ -0,0 +1,432 @@
1
+ module CurationConcerns
2
+ module Workflow
3
+ # Welcome intrepid developer. You have stumbled into some complex data
4
+ # interactions. There are a lot of data collaborators regarding these tests.
5
+ # I would love this to be more in isolation, but that is not in the cards as
6
+ # there are at least 16 database tables interacting to ultimately answer the
7
+ # following question:
8
+ #
9
+ # * What actions can a given user take on an entity?
10
+ #
11
+ # Could there be more efficient queries? Yes. However, the composition of
12
+ # queries has proven to be a very powerful means of understanding and
13
+ # exploring the problem.
14
+ #
15
+ # @note There is an indication of public or private api. The intent of this
16
+ # is to differentiate what are methods that are the primary entry points
17
+ # as understood as of the commit that has the @api tag. However, these are
18
+ # public methods because they have been tested in isolation and are used
19
+ # to help compose the `@api public` methods.
20
+ module PermissionQuery
21
+ module_function
22
+
23
+ # @api public
24
+ #
25
+ # For the given :user and :entity return only workflow actions that meet all of the following:
26
+ #
27
+ # * available for the :entity's workflow state
28
+ # * permitted to be taken by one or more roles in which the user is assigned
29
+ # either at the workflow level or the entity level.
30
+ #
31
+ # * Actions to which the user Only actions permitted to the user
32
+ #
33
+ # @param user [User]
34
+ # @param entity [#to_sipity_entity] an object that can be converted into a Sipity::Entity
35
+ # @return [ActiveRecord::Relation<Sipity::WorkflowAction>]
36
+ def scope_permitted_workflow_actions_available_for_current_state(user:, entity:)
37
+ workflow_actions_scope = scope_workflow_actions_available_for_current_state(entity: entity)
38
+ workflow_state_actions_scope = scope_permitted_entity_workflow_state_actions(user: user, entity: entity)
39
+ workflow_actions_scope.where(
40
+ workflow_actions_scope.arel_table[:id].in(
41
+ workflow_state_actions_scope.arel_table.project(
42
+ workflow_state_actions_scope.arel_table[:workflow_action_id]
43
+ ).where(workflow_state_actions_scope.constraints.reduce)
44
+ )
45
+ )
46
+ end
47
+
48
+ # @api public
49
+ #
50
+ # Agents associated with the given :entity and how they are associated
51
+ # with the given.
52
+ #
53
+ # @param entity [Object] that can be converted into a Sipity::Entity
54
+ # @param role [Object] that can be converted into a Sipity::Role
55
+ # @return [ActiveRecord::Relation<Sipity::Agent>] augmented with
56
+ def scope_agents_associated_with_entity_and_role(entity:, role:)
57
+ entity = PowerConverter.convert_to_sipity_entity(entity)
58
+ role = PowerConverter.convert_to_sipity_role(role)
59
+ workflow_roles = Sipity::WorkflowRole.arel_table
60
+ workflow_responsibilities = Sipity::WorkflowResponsibility.arel_table
61
+ entity_responsibilities = Sipity::EntitySpecificResponsibility.arel_table
62
+
63
+ agents = Sipity::Agent.arel_table
64
+
65
+ agents_select_manager = agents.project(
66
+ :*,
67
+ Arel.sql("'#{Sipity::Agent::ENTITY_LEVEL_AGENT_RELATIONSHIP}'").as('agent_processing_relationship')
68
+ ).where(
69
+ agents[:id].in(
70
+ entity_responsibilities.project(entity_responsibilities[:agent_id]).join(workflow_roles).on(
71
+ workflow_roles[:id].eq(entity_responsibilities[:workflow_role_id])
72
+ ).where(
73
+ entity_responsibilities[:entity_id].eq(entity.id).and(
74
+ workflow_roles[:role_id].eq(role.id)
75
+ )
76
+ )
77
+ )
78
+ ).union(
79
+ agents.project(
80
+ :*,
81
+ Arel.sql("'#{Sipity::Agent::WORKFLOW_LEVEL_AGENT_RELATIONSHIP}'").as('agent_processing_relationship')
82
+ ).where(
83
+ agents[:id].in(
84
+ workflow_responsibilities.project(workflow_responsibilities[:agent_id]).join(workflow_roles).on(
85
+ workflow_roles[:id].eq(workflow_responsibilities[:workflow_role_id])
86
+ ).where(
87
+ workflow_roles[:workflow_id].eq(entity.workflow_id).and(
88
+ workflow_roles[:role_id].eq(role.id)
89
+ )
90
+ )
91
+ )
92
+ )
93
+ )
94
+ # I would love to use the following:
95
+ # `Agent.find_by_sql(agents_select_manager.to_sql)`
96
+ #
97
+ # However AREL is adding an opening and closing parenthesis to the query
98
+ # statement. So I needed to massage that output, as follows:
99
+ #
100
+ # ```ruby
101
+ # Agent.find_by_sql(
102
+ # agents_select_manager.to_sql.sub(/\A\s*\(\s*(.*)\s*\)\s*\Z/,'\1')
103
+ # )
104
+ # ```
105
+ #
106
+ # Instead I'm taking an example from:
107
+ # https://github.com/danshultz/mastering_active_record_sample_code/blob/a656c60ca7a2e27b5cd1aadbdf3bdc1814c37000/app/models/beer.rb#L77-L81
108
+ #
109
+ # Note, I'm making a dynamic query with a result the same as the table
110
+ # name of the model that I'm using.
111
+ Sipity::Agent.from(agents.create_table_alias(agents_select_manager, agents.table_name)).all
112
+ end
113
+
114
+ # @api public
115
+ #
116
+ # Roles associated with the given :entity
117
+ # @param entity [Object] that can be converted into a Sipity::Entity
118
+ # @return [ActiveRecord::Relation<Sipity::Role>]
119
+ def scope_roles_associated_with_the_given_entity(entity:)
120
+ entity = PowerConverter.convert_to_sipity_entity(entity)
121
+ return Sipity::Role.none unless entity
122
+ workflow_roles = Sipity::WorkflowRole.arel_table
123
+ Sipity::Role.where(
124
+ Sipity::Role.arel_table[:id].in(
125
+ workflow_roles.project(workflow_roles[:role_id]).where(
126
+ workflow_roles[:workflow_id].eq(entity.workflow_id)
127
+ )
128
+ )
129
+ )
130
+ end
131
+
132
+ # @api public
133
+ #
134
+ # Is the user authorized to take the processing action on the given
135
+ # entity?
136
+ #
137
+ # @param user [User]
138
+ # @param entity an object that can be converted into a Sipity::Entity
139
+ # @param action an object that can be converted into a Sipity::WorkflowAction#name
140
+ # @return [Boolean]
141
+ def authorized_for_processing?(user:, entity:, action:)
142
+ action_name = PowerConverter.convert_to_sipity_action_name(action)
143
+ scope_permitted_workflow_actions_available_for_current_state(user: user, entity: entity)
144
+ .where(Sipity::WorkflowAction.arel_table[:name].eq(action_name)).count > 0
145
+ end
146
+
147
+ # @api public
148
+ #
149
+ # An ActiveRecord::Relation scope that meets the following criteria:
150
+ #
151
+ # * All of the Processing Agents directly associated with the given :user
152
+ #
153
+ # @param user [User]
154
+ # @return [ActiveRecord::Relation<Sipity::Agent>]
155
+ def scope_processing_agents_for(user:)
156
+ return Sipity::Agent.none unless user.present?
157
+ return Sipity::Agent.none unless user.persisted?
158
+ user_agent = PowerConverter.convert_to_sipity_agent(user)
159
+ group_agents = user.groups.map do |g|
160
+ PowerConverter.convert_to_sipity_agent(CurationConcerns::Group.new(g))
161
+ end
162
+ Sipity::Agent.where(id: group_agents + [user_agent])
163
+ end
164
+
165
+ PermissionScope = Struct.new(:entity, :workflow)
166
+ private_constant :PermissionScope
167
+
168
+ # @api public
169
+ #
170
+ # An ActiveRecord::Relation scope that meets the following criteria:
171
+ #
172
+ # * Sipity::Entity in a state in which I have access to based on:
173
+ # - The entity specific responsibility
174
+ # - For which I've been assigned a role
175
+ # - The workflow specific responsibility
176
+ # - For which I've been assigned a role
177
+ #
178
+ # @param [User] user
179
+ #
180
+ # @return [ActiveRecord::Relation<Sipity::Entity>]
181
+ def scope_entities_for_the_user(user:)
182
+ entities = Sipity::Entity.arel_table
183
+ workflow_state_actions = Sipity::WorkflowStateAction.arel_table
184
+ workflow_states = Sipity::WorkflowState.arel_table
185
+ workflow_state_action_permissions = Sipity::WorkflowStateActionPermission.arel_table
186
+ workflow_responsibilities = Sipity::WorkflowResponsibility.arel_table
187
+ entity_responsibilities = Sipity::EntitySpecificResponsibility.arel_table
188
+
189
+ user_agent_scope = scope_processing_agents_for(user: user)
190
+ user_agent_contraints = user_agent_scope.arel_table.project(
191
+ user_agent_scope.arel_table[:id]
192
+ ).where(user_agent_scope.arel.constraints)
193
+
194
+ join_builder = lambda do |responsibility|
195
+ entities.project(
196
+ entities[:id]
197
+ ).join(workflow_state_actions).on(
198
+ workflow_state_actions[:originating_workflow_state_id].eq(entities[:workflow_state_id])
199
+ ).join(workflow_state_action_permissions).on(
200
+ workflow_state_action_permissions[:workflow_state_action_id].eq(workflow_state_actions[:id])
201
+ ).join(workflow_states).on(
202
+ workflow_states[:id].eq(workflow_state_actions[:originating_workflow_state_id])
203
+ ).join(responsibility).on(
204
+ responsibility[:workflow_role_id].eq(workflow_state_action_permissions[:workflow_role_id])
205
+ )
206
+ end
207
+
208
+ where_builder = -> (responsibility) { responsibility[:agent_id].in(user_agent_contraints) }
209
+
210
+ entity_specific_joins = join_builder.call(entity_responsibilities)
211
+ workflow_specific_joins = join_builder.call(workflow_responsibilities)
212
+
213
+ entity_specific_where = where_builder.call(entity_responsibilities).and(
214
+ entities[:id].eq(entity_responsibilities[:entity_id])
215
+ )
216
+ workflow_specific_where = where_builder.call(workflow_responsibilities)
217
+
218
+ Sipity::Entity.where(
219
+ entities[:id].in(entity_specific_joins.where(entity_specific_where))
220
+ .or(entities[:id].in(workflow_specific_joins.where(workflow_specific_where)))
221
+ )
222
+ end
223
+
224
+ # @api public
225
+ #
226
+ # An ActiveRecord::Relation scope that meets the following criteria:
227
+ #
228
+ # * Users that are directly associated with the given entity through on or
229
+ # more of the given roles
230
+ # * Users that are indirectly associated with the given entity by group
231
+ # and role.
232
+ #
233
+ # @param roles [Sipity::Role]
234
+ # @param entity an object that can be converted into a Sipity::Entity
235
+ # @return [ActiveRecord::Relation<User>]
236
+ def scope_users_for_entity_and_roles(entity:, roles:)
237
+ entity = PowerConverter.convert_to_sipity_entity(entity)
238
+ role_ids = Array.wrap(roles).map { |role| PowerConverter.convert_to_sipity_role(role).id }
239
+ user_polymorphic_type = PowerConverter.convert_to_polymorphic_type(::User)
240
+
241
+ workflow_roles = Sipity::WorkflowRole.arel_table
242
+ workflow_responsibilities = Sipity::WorkflowResponsibility.arel_table
243
+ entity_responsibilities = Sipity::EntitySpecificResponsibility.arel_table
244
+ user_table = ::User.arel_table
245
+ agent_table = Sipity::Agent.arel_table
246
+
247
+ workflow_role_id_subquery = workflow_roles.project(workflow_roles[:id]).where(
248
+ workflow_roles[:role_id].in(role_ids)
249
+ )
250
+
251
+ workflow_agent_id_subquery = workflow_responsibilities.project(workflow_responsibilities[:agent_id]).where(
252
+ workflow_responsibilities[:workflow_role_id].in(workflow_role_id_subquery)
253
+ )
254
+
255
+ entity_agent_id_subquery = entity_responsibilities.project(entity_responsibilities[:agent_id]).where(
256
+ entity_responsibilities[:workflow_role_id].in(workflow_role_id_subquery)
257
+ .and(entity_responsibilities[:entity_id].eq(entity.id))
258
+ )
259
+
260
+ sub_query_for_user = agent_table.project(agent_table[:proxy_for_id]).where(
261
+ agent_table[:id].in(workflow_agent_id_subquery)
262
+ .or(agent_table[:id].in(entity_agent_id_subquery))
263
+ ).where(
264
+ agent_table[:proxy_for_type].eq(user_polymorphic_type)
265
+ )
266
+
267
+ ::User.where(user_table[:id].in(sub_query_for_user))
268
+ end
269
+
270
+ def user_emails_for_entity_and_roles(entity:, roles:)
271
+ scope_users_for_entity_and_roles(entity: entity, roles: roles).pluck(:email)
272
+ end
273
+
274
+ # @api public
275
+ #
276
+ # For the given :user and :entity, return an ActiveRecord::Relation that,
277
+ # if resolved, will be all of the assocated workflow roles for both the
278
+ # workflow responsibilities and the entity specific responsibilities.
279
+ #
280
+ # @param user [User]
281
+ # @param entity an object that can be converted into a Sipity::Entity
282
+ # @return [ActiveRecord::Relation<Sipity::WorkflowRole>]
283
+ def scope_processing_workflow_roles_for_user_and_entity(user:, entity:)
284
+ entity = PowerConverter.convert_to_sipity_entity(entity)
285
+ workflow_scope = scope_processing_workflow_roles_for_user_and_workflow(user: user, workflow: entity.workflow)
286
+
287
+ entity_specific_scope = scope_processing_workflow_roles_for_user_and_entity_specific(user: user, entity: entity)
288
+ Sipity::WorkflowRole.where(
289
+ workflow_scope.arel.constraints.reduce.or(entity_specific_scope.arel.constraints.reduce)
290
+ )
291
+ end
292
+
293
+ # @api private
294
+ #
295
+ # For the given :user and :workflow, return an ActiveRecord::Relation that,
296
+ # if resolved, will be all of the assocated workflow roles that are
297
+ # assigned to directly to the workflow.
298
+ #
299
+ # @param user [User]
300
+ # @param workflow [Sipity::Workflow]
301
+ # @return [ActiveRecord::Relation<Sipity::WorkflowRole>]
302
+ def scope_processing_workflow_roles_for_user_and_workflow(user:, workflow:)
303
+ responsibility_table = Sipity::WorkflowResponsibility.arel_table
304
+ workflow_role_table = Sipity::WorkflowRole.arel_table
305
+
306
+ agent_constraints = scope_processing_agents_for(user: user)
307
+ workflow_role_subquery = workflow_role_table[:id].in(
308
+ responsibility_table.project(responsibility_table[:workflow_role_id])
309
+ .where(
310
+ responsibility_table[:agent_id].in(
311
+ agent_constraints.arel_table.project(
312
+ agent_constraints.arel_table[:id]
313
+ ).where(agent_constraints.arel.constraints)
314
+ )
315
+ )
316
+ )
317
+
318
+ Sipity::WorkflowRole.where(
319
+ workflow_role_table[:workflow_id].eq(workflow.id).and(workflow_role_subquery)
320
+ )
321
+ end
322
+
323
+ # @api private
324
+ #
325
+ # For the given :user and :entity, return an ActiveRecord::Relation that,
326
+ # if resolved, will be all of the assocated workflow roles that are
327
+ # assigned to specifically to the entity (and not the parent workflow).
328
+ #
329
+ # @param user [User]
330
+ # @param entity an object that can be converted into a Sipity::Entity
331
+ # @return [ActiveRecord::Relation<Sipity::WorkflowRole>]
332
+ def scope_processing_workflow_roles_for_user_and_entity_specific(user:, entity:)
333
+ entity = PowerConverter.convert_to_sipity_entity(entity)
334
+ agent_scope = scope_processing_agents_for(user: user)
335
+ specific_resp_table = Sipity::EntitySpecificResponsibility.arel_table
336
+ workflow_role_table = Sipity::WorkflowRole.arel_table
337
+
338
+ Sipity::WorkflowRole.where(
339
+ workflow_role_table[:id].in(
340
+ specific_resp_table.project(specific_resp_table[:workflow_role_id])
341
+ .where(
342
+ specific_resp_table[:agent_id].in(
343
+ agent_scope.arel_table.project(
344
+ agent_scope.arel_table[:id]
345
+ ).where(
346
+ agent_scope.arel.constraints.reduce.and(specific_resp_table[:entity_id].eq(entity.id))
347
+ )
348
+ )
349
+ )
350
+ )
351
+ )
352
+ end
353
+
354
+ # @api private
355
+ #
356
+ # For the given :user and :entity, return an ActiveRecord::Relation,
357
+ # that if resolved, will be collection of
358
+ # Sipity::WorkflowStateAction object to which the user has
359
+ # permission to do something.
360
+ #
361
+ # An ActiveRecord::Relation scope that meets the following criteria:
362
+ #
363
+ # * The actions are available for the given entity's current state
364
+ # * The actions are available for the given user based on their role.
365
+ # Either:
366
+ # - Directly via an agent associated with a user
367
+ # - Indirectly via an agent associated with a group
368
+ #
369
+ # @param user [User]
370
+ # @param entity an object that can be converted into a Sipity::Entity
371
+ # @return [ActiveRecord::Relation<Sipity::WorkflowStateAction>]
372
+ def scope_permitted_entity_workflow_state_actions(user:, entity:)
373
+ entity = PowerConverter.convert_to_sipity_entity(entity)
374
+ workflow_state_actions = Sipity::WorkflowStateAction
375
+ permissions = Sipity::WorkflowStateActionPermission
376
+ role_scope = scope_processing_workflow_roles_for_user_and_entity(user: user, entity: entity)
377
+
378
+ workflow_state_actions.where(
379
+ workflow_state_actions.arel_table[:originating_workflow_state_id].eq(entity.workflow_state_id).and(
380
+ workflow_state_actions.arel_table[:id].in(
381
+ permissions.arel_table.project(
382
+ permissions.arel_table[:workflow_state_action_id]
383
+ ).where(
384
+ permissions.arel_table[:workflow_role_id].in(
385
+ role_scope.arel_table.project(role_scope.arel_table[:id]).where(
386
+ role_scope.arel.constraints.reduce
387
+ )
388
+ )
389
+ )
390
+ )
391
+ )
392
+ )
393
+ end
394
+
395
+ # @api public
396
+ #
397
+ # For the given :entity return an ActiveRecord::Relation that when
398
+ # resolved will be only the workflow actions that:
399
+ #
400
+ # * Are available for the entity's workflow_state
401
+ #
402
+ # @param entity an object that can be converted into a Sipity::Entity
403
+ # @return [ActiveRecord::Relation<Sipity::WorkflowAction>]
404
+ def scope_workflow_actions_for_current_state(entity:)
405
+ entity = PowerConverter.convert_to_sipity_entity(entity)
406
+ state_actions_table = Sipity::WorkflowStateAction.arel_table
407
+ Sipity::WorkflowAction.where(
408
+ Sipity::WorkflowAction.arel_table[:id].in(
409
+ state_actions_table.project(state_actions_table[:workflow_action_id])
410
+ .where(state_actions_table[:originating_workflow_state_id].eq(entity.workflow_state_id))
411
+ )
412
+ )
413
+ end
414
+
415
+ # @api private
416
+ #
417
+ # For the given :entity, return an ActiveRecord::Relation, that
418
+ # if resolved, that lists all of the actions available for the entity and
419
+ # its current state.
420
+ #
421
+ # * All actions that are associated with actions that do not have prerequsites
422
+ # * All actions that have prerequisites and all of those prerequisites are complete
423
+ #
424
+ # @param entity an object that can be converted into a Sipity::Entity
425
+ # @return [ActiveRecord::Relation<Sipity::WorkflowAction>]
426
+ def scope_workflow_actions_available_for_current_state(entity:)
427
+ workflow_actions_for_current_state = scope_workflow_actions_for_current_state(entity: entity)
428
+ Sipity::WorkflowAction.where(workflow_actions_for_current_state.constraints.reduce)
429
+ end
430
+ end
431
+ end
432
+ end
@@ -0,0 +1,11 @@
1
+ module CurationConcerns
2
+ module Workflow
3
+ # This is a built in function for workflow, so that a workflow action can be created that
4
+ # removes the creators ability to alter it.
5
+ class RemoveDepositorPermissions
6
+ def self.call(entity:, **)
7
+ entity.proxy_for.edit_users = []
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,49 @@
1
+ module CurationConcerns
2
+ module Workflow
3
+ # Responsible for creating database entries for the given workflow's actions
4
+ class SipityActionsGenerator
5
+ # @api public
6
+ #
7
+ # Responsible for creating database entries for the given workflow's actions
8
+ #
9
+ # @param workflow [Sipity::Workflow]
10
+ # @param actions_configuration [Hash] as defined in CurationConcerns::Workflow::WorkflowSchema
11
+ # @return [Sipity::Workflow]
12
+ def self.call(**keywords, &block)
13
+ new(**keywords, &block).call
14
+ end
15
+
16
+ def initialize(workflow:, actions_configuration:)
17
+ self.workflow = workflow
18
+ self.actions_configuration = actions_configuration
19
+ end
20
+
21
+ private
22
+
23
+ attr_accessor :workflow
24
+
25
+ attr_reader :actions_configuration
26
+
27
+ def actions_configuration=(input)
28
+ @actions_configuration = Array.wrap(input)
29
+ end
30
+
31
+ public
32
+
33
+ def call
34
+ generate_state_diagram!
35
+ workflow
36
+ end
37
+
38
+ private
39
+
40
+ def generate_state_diagram!
41
+ actions_configuration.each do |configuration|
42
+ Array.wrap(configuration.fetch(:name)).each do |name|
43
+ StateMachineGenerator.generate_from_schema(workflow: workflow, name: name, **configuration.except(:name))
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,102 @@
1
+ module CurationConcerns
2
+ module Workflow
3
+ # Imports a single action for a workflow, including all of the adjacent states,
4
+ # permissions and notifications
5
+ class StateMachineGenerator
6
+ def self.generate_from_schema(workflow:, name:, **keywords)
7
+ new(
8
+ workflow: workflow, action_name: name, config: keywords,
9
+ email_generator_method_name: :schema_based_email_generator_method
10
+ ).call
11
+ end
12
+
13
+ def initialize(workflow:, action_name:, config:, email_generator_method_name: :schema_based_email_generator_method)
14
+ self.workflow = workflow
15
+ self.action_name = action_name
16
+ self.config = config
17
+ self.email_generator_method_name = email_generator_method_name
18
+ end
19
+
20
+ private
21
+
22
+ attr_accessor :workflow, :action_name, :config, :email_generator_method_name
23
+
24
+ def create_workflow_action!
25
+ @action = Sipity::WorkflowAction.find_or_create_by!(workflow: workflow, name: action_name.to_s)
26
+ end
27
+
28
+ public
29
+
30
+ attr_reader :action
31
+
32
+ def call
33
+ create_workflow_action!
34
+
35
+ if config.key?(:attributes)
36
+ build_attributes(config.fetch(:attributes).stringify_keys)
37
+ end
38
+
39
+ # Strategy State
40
+ config.fetch(:from_states, []).each do |entry|
41
+ build_from_state(entry.fetch(:names), entry.fetch(:roles))
42
+ end
43
+
44
+ # Transitions
45
+ if config.key?(:transition_to)
46
+ build_transitions(config.fetch(:transition_to).to_s)
47
+ end
48
+
49
+ send(email_generator_method_name, workflow: workflow, config: config)
50
+
51
+ # Methods
52
+ if config.key?(:methods)
53
+ build_methods(Array.wrap(config.fetch(:methods)))
54
+ end
55
+ end
56
+
57
+ def build_attributes(action_attributes)
58
+ action_attributes.delete('presentation_sequence') # TODO: remove this line when we want to support presentation_sequence
59
+ existing_action_attributes = action.attributes.slice(*action_attributes.keys)
60
+ unless action_attributes == existing_action_attributes
61
+ action.update_attributes!(action_attributes)
62
+ end
63
+ end
64
+
65
+ def build_transitions(name)
66
+ transition_to_state = Sipity::WorkflowState.find_or_create_by!(workflow: workflow, name: name)
67
+ if action.resulting_workflow_state != transition_to_state
68
+ action.resulting_workflow_state = transition_to_state
69
+ action.save!
70
+ end
71
+ end
72
+
73
+ # @params state_names[Array]
74
+ # @params roles [Array]
75
+ def build_from_state(state_names, state_roles)
76
+ Array.wrap(state_names).each do |state_name|
77
+ workflow_state = Sipity::WorkflowState.find_or_create_by!(workflow: workflow, name: state_name.to_s)
78
+ PermissionGenerator.call(
79
+ actors: [],
80
+ roles: state_roles,
81
+ workflow_state: workflow_state,
82
+ action_names: action_name,
83
+ workflow: workflow
84
+ )
85
+ end
86
+ end
87
+
88
+ def build_methods(method_list)
89
+ MethodGenerator.call(action: action, list: method_list)
90
+ end
91
+
92
+ def schema_based_email_generator_method(workflow:, config:)
93
+ Array.wrap(config.fetch(:notifications, [])).each do |configuration|
94
+ notification_configuration = NotificationConfigurationParameter.build_from_workflow_action_configuration(
95
+ workflow_action: action_name, config: configuration
96
+ )
97
+ NotificationGenerator.call(workflow: workflow, notification_configuration: notification_configuration)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,52 @@
1
+ module CurationConcerns
2
+ module Workflow
3
+ class StatusListService
4
+ # @param user [User]
5
+ def initialize(user)
6
+ @user = user
7
+ end
8
+
9
+ attr_reader :user
10
+
11
+ # TODO: We will want to paginate this
12
+ # @return [Array<StatusRow>] a list of results that the given user can take action on.
13
+ def each
14
+ return enum_for(:each) unless block_given?
15
+ solr_documents.each do |doc|
16
+ yield doc
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ # @return [Hash<String,SolrDocument>] a hash of id to solr document
23
+ def solr_documents
24
+ search_solr.map { |result| SolrDocument.new(result) }
25
+ end
26
+
27
+ def search_solr
28
+ actionable_roles = roles_for_user
29
+ return [] if actionable_roles.empty?
30
+ WorkRelation.new.search_with_conditions(
31
+ { actionable_workflow_roles_ssim: actionable_roles },
32
+ fl: 'id title_tesim has_model_ssim, workflow_state_name_ssim',
33
+ rows: 1000)
34
+ end
35
+
36
+ # @return [Array<String>] the list of workflow-role combinations this user has
37
+ def roles_for_user
38
+ Sipity::Workflow.all.flat_map do |wf|
39
+ workflow_roles_for_user_and_workflow(wf).map do |wf_role|
40
+ "#{wf.name}-#{wf_role.role.name}"
41
+ end
42
+ end
43
+ end
44
+
45
+ # @param workflow [Sipity::Workflow]
46
+ # @return [ActiveRecord::Relation<Sipity::WorkflowRole>]
47
+ def workflow_roles_for_user_and_workflow(workflow)
48
+ CurationConcerns::Workflow::PermissionQuery.scope_processing_workflow_roles_for_user_and_workflow(user: user, workflow: workflow)
49
+ end
50
+ end
51
+ end
52
+ end