3637: refactor code for better maintenance.
[arvados.git] / apps / workbench / app / controllers / projects_controller.rb
1 class ProjectsController < ApplicationController
2   def model_class
3     Group
4   end
5
6   def find_object_by_uuid
7     if current_user and params[:uuid] == current_user.uuid
8       @object = current_user.dup
9       @object.uuid = current_user.uuid
10       class << @object
11         def name
12           'Home'
13         end
14         def description
15           ''
16         end
17         def attribute_editable? attr, *args
18           case attr
19           when 'description', 'name'
20             false
21           else
22             super
23           end
24         end
25       end
26     else
27       super
28     end
29   end
30
31   def index_pane_list
32     %w(Projects)
33   end
34
35   def show_pane_list
36     if @user_is_manager
37       %w(Data_collections Jobs_and_pipelines Pipeline_templates Subprojects Other_objects Sharing Advanced)
38     else
39       %w(Data_collections Jobs_and_pipelines Pipeline_templates Subprojects Other_objects Advanced)
40     end
41   end
42
43   def remove_item
44     params[:item_uuids] = [params[:item_uuid]]
45     remove_items
46     render template: 'projects/remove_items'
47   end
48
49   def remove_items
50     @removed_uuids = []
51     links = []
52     params[:item_uuids].collect { |uuid| ArvadosBase.find uuid }.each do |item|
53       if (item.class == Link and
54           item.link_class == 'name' and
55           item.tail_uuid == @object.uuid)
56         # Given uuid is a name link, linking an object to this
57         # project. First follow the link to find the item we're removing,
58         # then delete the link.
59         links << item
60         item = ArvadosBase.find item.head_uuid
61       else
62         # Given uuid is an object. Delete all names.
63         links += Link.where(tail_uuid: @object.uuid,
64                             head_uuid: item.uuid,
65                             link_class: 'name')
66       end
67       links.each do |link|
68         @removed_uuids << link.uuid
69         link.destroy
70       end
71       if item.owner_uuid == @object.uuid
72         # Object is owned by this project. Remove it from the project by
73         # changing owner to the current user.
74         item.update_attributes owner_uuid: current_user.uuid
75         @removed_uuids << item.uuid
76       end
77     end
78   end
79
80   def copy_items
81     move_or_copy_project_items :copy
82   end
83
84   def move_items
85     move_or_copy_project_items :move
86   end
87
88   def move_or_copy_project_items action
89     uuids_to_add = session[:selected_move_or_copy_items]
90     move_or_copy_items action, uuids_to_add, params['target']
91     session[:selected_move_or_copy_items] = nil
92     redirect_to @object
93   end
94
95   def destroy
96     while (objects = Link.filter([['owner_uuid','=',@object.uuid],
97                                   ['tail_uuid','=',@object.uuid]])).any?
98       objects.each do |object|
99         object.destroy
100       end
101     end
102     while (objects = @object.contents(include_linked: false)).any?
103       objects.each do |object|
104         object.update_attributes! owner_uuid: current_user.uuid
105       end
106     end
107     if ArvadosBase::resource_class_for_uuid(@object.owner_uuid) == Group
108       params[:return_to] ||= group_path(@object.owner_uuid)
109     else
110       params[:return_to] ||= projects_path
111     end
112     super
113   end
114
115   def find_objects_for_index
116     @objects = all_projects
117     super
118   end
119
120   def load_contents_objects kinds=[]
121     kind_filters = @filters.select do |attr,op,val|
122       op == 'is_a' and val.is_a? Array and val.count > 1
123     end
124     if /^created_at\b/ =~ @order[0] and kind_filters.count == 1
125       # If filtering on multiple types and sorting by date: Get the
126       # first page of each type, sort the entire set, truncate to one
127       # page, and use the last item on this page as a filter for
128       # retrieving the next page. Ideally the API would do this for
129       # us, but it doesn't (yet).
130       nextpage_operator = /\bdesc$/i =~ @order[0] ? '<' : '>'
131       @objects = []
132       @name_link_for = {}
133       kind_filters.each do |attr,op,val|
134         (val.is_a?(Array) ? val : [val]).each do |type|
135           objects = @object.contents(order: @order,
136                                      limit: @limit,
137                                      include_linked: true,
138                                      filters: (@filters - kind_filters + [['uuid', 'is_a', type]]),
139                                      offset: @offset)
140           objects.each do |object|
141             @name_link_for[object.andand.uuid] = objects.links_for(object, 'name').first
142           end
143           @objects += objects
144         end
145       end
146       @objects = @objects.to_a.sort_by(&:created_at)
147       @objects.reverse! if nextpage_operator == '<'
148       @objects = @objects[0..@limit-1]
149       @next_page_filters = @filters.reject do |attr,op,val|
150         attr == 'created_at' and op == nextpage_operator
151       end
152       if @objects.any?
153         @next_page_filters += [['created_at',
154                                 nextpage_operator,
155                                 @objects.last.created_at]]
156         @next_page_href = url_for(partial: :contents_rows,
157                                   filters: @next_page_filters.to_json)
158       else
159         @next_page_href = nil
160       end
161     else
162       @objects = @object.contents(order: @order,
163                                   limit: @limit,
164                                   include_linked: true,
165                                   filters: @filters,
166                                   offset: @offset)
167       @next_page_href = next_page_href(partial: :contents_rows)
168     end
169
170     preload_links_for_objects(@objects.to_a)
171   end
172
173   def show
174     if !@object
175       return render_not_found("object not found")
176     end
177
178     @user_is_manager = false
179     @share_links = []
180     if @object.uuid != current_user.uuid
181       begin
182         @share_links = Link.permissions_for(@object)
183         @user_is_manager = true
184       rescue ArvadosApiClient::AccessForbiddenException,
185         ArvadosApiClient::NotFoundException
186       end
187     end
188
189     if params[:partial]
190       load_contents_objects
191       respond_to do |f|
192         f.json {
193           render json: {
194             content: render_to_string(partial: 'show_contents_rows.html',
195                                       formats: [:html]),
196             next_page_href: @next_page_href
197           }
198         }
199       end
200     else
201       @objects = []
202       super
203     end
204   end
205
206   def create
207     @new_resource_attrs = (params['project'] || {}).merge(group_class: 'project')
208     @new_resource_attrs[:name] ||= 'New project'
209     super
210   end
211
212   def update
213     @updates = params['project']
214     super
215   end
216
217   helper_method :get_objects_and_names
218   def get_objects_and_names(objects=nil)
219     objects = @objects if objects.nil?
220     objects_and_names = []
221     objects.each do |object|
222       if objects.respond_to? :links_for and
223           !(name_links = objects.links_for(object, 'name')).empty?
224         name_links.each do |name_link|
225           objects_and_names << [object, name_link]
226         end
227       elsif @name_link_for.andand[object.uuid]
228         objects_and_names << [object, @name_link_for[object.uuid]]
229       elsif object.respond_to? :name
230         objects_and_names << [object, object]
231       elsif not Collection.attribute_info.include?(:name)
232         objects_and_names << [object,
233                                Link.new(owner_uuid: @object.uuid,
234                                         tail_uuid: @object.uuid,
235                                         head_uuid: object.uuid,
236                                         link_class: "name",
237                                         name: "")]
238       end
239     end
240     objects_and_names
241   end
242
243   def share_with
244     if not params[:uuids].andand.any?
245       @errors = ["No user/group UUIDs specified to share with."]
246       return render_error(status: 422)
247     end
248     results = {"success" => [], "errors" => []}
249     params[:uuids].each do |shared_uuid|
250       begin
251         Link.create(tail_uuid: shared_uuid, link_class: "permission",
252                     name: "can_read", head_uuid: @object.uuid)
253       rescue ArvadosApiClient::ApiError => error
254         error_list = error.api_response.andand[:errors]
255         if error_list.andand.any?
256           results["errors"] += error_list.map { |e| "#{shared_uuid}: #{e}" }
257         else
258           error_code = error.api_status || "Bad status"
259           results["errors"] << "#{shared_uuid}: #{error_code} response"
260         end
261       else
262         results["success"] << shared_uuid
263       end
264     end
265     if results["errors"].empty?
266       results.delete("errors")
267       status = 200
268     else
269       status = 422
270     end
271     respond_to do |f|
272       f.json { render(json: results, status: status) }
273     end
274   end
275 end