3531: Refactor project tab infinite-scroll. Sort jobs and pipelines
[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 move_items
81     target_uuid = params['target']
82     uuids_to_add = session[:selected_move_items]
83
84     uuids_to_add.
85       collect { |x| ArvadosBase::resource_class_for_uuid(x) }.
86       uniq.
87       each do |resource_class|
88       resource_class.filter([['uuid','in',uuids_to_add]]).each do |dst|
89         if resource_class == Collection
90           dst = Link.new(owner_uuid: target_uuid,
91                          tail_uuid: target_uuid,
92                          head_uuid: dst.uuid,
93                          link_class: 'name',
94                          name: target_uuid)
95         else
96           dst.owner_uuid = target_uuid
97           dst.tail_uuid = target_uuid if dst.class == Link
98         end
99         dst.save!
100       end
101     end
102     session[:selected_move_items] = nil
103     redirect_to @object
104   end
105
106   def destroy
107     while (objects = Link.filter([['owner_uuid','=',@object.uuid],
108                                   ['tail_uuid','=',@object.uuid]])).any?
109       objects.each do |object|
110         object.destroy
111       end
112     end
113     while (objects = @object.contents(include_linked: false)).any?
114       objects.each do |object|
115         object.update_attributes! owner_uuid: current_user.uuid
116       end
117     end
118     if ArvadosBase::resource_class_for_uuid(@object.owner_uuid) == Group
119       params[:return_to] ||= group_path(@object.owner_uuid)
120     else
121       params[:return_to] ||= projects_path
122     end
123     super
124   end
125
126   def find_objects_for_index
127     @objects = all_projects
128     super
129   end
130
131   def load_contents_objects kinds=[]
132     @limit = 5
133     kind_filters = @filters.select do |attr,op,val|
134       op == 'is_a' and val.is_a? Array and val.count > 1
135     end
136     if /^created_at/ =~ @order[0] and kind_filters.count == 1
137       # If filtering on multiple types and sorting by date: Get the
138       # first page of each type, sort the entire set, truncate to one
139       # page, and use the last item on this page as a filter for
140       # retrieving the next page. Ideally the API would do this for
141       # us, but it doesn't (yet).
142       @objects = []
143       kind_filters.each do |attr,op,val|
144         (val.is_a?(Array) ? val : [val]).each do |type|
145           @objects += @object.contents(order: @order,
146                                        limit: @limit,
147                                        include_linked: true,
148                                        filters: (@filters - kind_filters + [['uuid', 'is_a', type]]),
149                                        offset: @offset)
150         end
151       end
152       @objects = @objects.to_a.sort_by(&:created_at).reverse[0..@limit-1]
153       @next_page_filters = @filters.reject do |attr,op,val|
154         attr == 'created_at' and op == '<'
155       end
156       if @objects.any?
157         @next_page_filters += [['created_at', '<', @objects.last.created_at]]
158         @next_page_href = url_for(partial: :contents_rows,
159                                   filters: @next_page_filters.to_json)
160       else
161         @next_page_href = nil
162       end
163     else
164       @objects = @object.contents(order: @order,
165                                   limit: @limit,
166                                   include_linked: true,
167                                   filters: @filters,
168                                   offset: @offset)
169       @next_page_href = next_page_href(partial: :contents_rows)
170     end
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 object.respond_to? :name
228         objects_and_names << [object, object]
229       else
230         objects_and_names << [object,
231                                Link.new(owner_uuid: @object.uuid,
232                                         tail_uuid: @object.uuid,
233                                         head_uuid: object.uuid,
234                                         link_class: "name",
235                                         name: "")]
236       end
237     end
238     objects_and_names
239   end
240
241   def share_with
242     if not params[:uuids].andand.any?
243       @errors = ["No user/group UUIDs specified to share with."]
244       return render_error(status: 422)
245     end
246     results = {"success" => [], "errors" => []}
247     params[:uuids].each do |shared_uuid|
248       begin
249         Link.create(tail_uuid: shared_uuid, link_class: "permission",
250                     name: "can_read", head_uuid: @object.uuid)
251       rescue ArvadosApiClient::ApiError => error
252         error_list = error.api_response.andand[:errors]
253         if error_list.andand.any?
254           results["errors"] += error_list.map { |e| "#{shared_uuid}: #{e}" }
255         else
256           error_code = error.api_status || "Bad status"
257           results["errors"] << "#{shared_uuid}: #{error_code} response"
258         end
259       else
260         results["success"] << shared_uuid
261       end
262     end
263     if results["errors"].empty?
264       results.delete("errors")
265       status = 200
266     else
267       status = 422
268     end
269     respond_to do |f|
270       f.json { render(json: results, status: status) }
271     end
272   end
273 end