46b8d159e8d84fafe2288c804a35d6ef9f9fe8c1
[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
30     @user_is_manager = false
31     @share_links = []
32     if @object.uuid != current_user.uuid
33       begin
34         @share_links = Link.permissions_for(@object)
35         @user_is_manager = true
36       rescue ArvadosApiClient::AccessForbiddenException,
37         ArvadosApiClient::NotFoundException
38       end
39     end
40   end
41
42   def index_pane_list
43     %w(Projects)
44   end
45
46   # Returning an array of hashes instead of an array of strings will allow
47   # us to tell the interface to get counts for each pane (using :filters).
48   # It also seems to me that something like these could be used to configure the contents of the panes.
49   def show_pane_list
50     pane_list = [
51       {
52         :name => 'Data_collections',
53         :filters => [%w(uuid is_a arvados#collection)]
54       },
55       {
56         :name => 'Jobs_and_pipelines',
57         :filters => [%w(uuid is_a) + [%w(arvados#job arvados#pipelineInstance)]]
58       },
59       {
60         :name => 'Pipeline_templates',
61         :filters => [%w(uuid is_a arvados#pipelineTemplate)]
62       },
63       {
64         :name => 'Subprojects',
65         :filters => [%w(uuid is_a arvados#group)]
66       },
67       { :name => 'Other_objects',
68         :filters => [%w(uuid is_a) + [%w(arvados#human arvados#specimen arvados#trait)]]
69       }
70     ]
71     # Note that adding :filters to 'Sharing' won't help show the count for it because @user_is_manager is only set in #show
72     # Therefore if a count were desired there we'd want to set @user_is_manager in a before_filter or somesuch.
73     pane_list << { :name => 'Sharing',
74                    :count => @share_links.count } if @user_is_manager
75     pane_list << { :name => 'Advanced' }
76   end
77
78   # Called via AJAX and returns Javascript that populates tab counts into tab titles.
79   # References #show_pane_list action which should return an array of hashes each with :name 
80   # and then optionally a :filters to run or a straight up :count
81   #
82   # This action could easily be moved to the ApplicationController to genericize the tab_counts behaviour,
83   # but one or more new routes would have to be created, the js.erb would also have to be moved
84   def tab_counts
85     @tab_counts = {}
86     show_pane_list.each do |pane|
87       if pane.is_a?(Hash)
88         if pane[:count]
89           @tab_counts[pane[:name]] = pane[:count]
90         elsif pane[:filters]
91           @tab_counts[pane[:name]] = @object.contents(filters: pane[:filters]).items_available
92         end
93       end
94     end
95   end
96
97   def remove_item
98     params[:item_uuids] = [params[:item_uuid]]
99     remove_items
100     render template: 'projects/remove_items'
101   end
102
103   def remove_items
104     @removed_uuids = []
105     links = []
106     params[:item_uuids].collect { |uuid| ArvadosBase.find uuid }.each do |item|
107       if (item.class == Link and
108           item.link_class == 'name' and
109           item.tail_uuid == @object.uuid)
110         # Given uuid is a name link, linking an object to this
111         # project. First follow the link to find the item we're removing,
112         # then delete the link.
113         links << item
114         item = ArvadosBase.find item.head_uuid
115       else
116         # Given uuid is an object. Delete all names.
117         links += Link.where(tail_uuid: @object.uuid,
118                             head_uuid: item.uuid,
119                             link_class: 'name')
120       end
121       links.each do |link|
122         @removed_uuids << link.uuid
123         link.destroy
124       end
125       if item.owner_uuid == @object.uuid
126         # Object is owned by this project. Remove it from the project by
127         # changing owner to the current user.
128         item.update_attributes owner_uuid: current_user.uuid
129         @removed_uuids << item.uuid
130       end
131     end
132   end
133
134   def destroy
135     while (objects = Link.filter([['owner_uuid','=',@object.uuid],
136                                   ['tail_uuid','=',@object.uuid]])).any?
137       objects.each do |object|
138         object.destroy
139       end
140     end
141     while (objects = @object.contents(include_linked: false)).any?
142       objects.each do |object|
143         object.update_attributes! owner_uuid: current_user.uuid
144       end
145     end
146     if ArvadosBase::resource_class_for_uuid(@object.owner_uuid) == Group
147       params[:return_to] ||= group_path(@object.owner_uuid)
148     else
149       params[:return_to] ||= projects_path
150     end
151     super
152   end
153
154   def find_objects_for_index
155     @objects = all_projects
156     super
157   end
158
159   def load_contents_objects kinds=[]
160     kind_filters = @filters.select do |attr,op,val|
161       op == 'is_a' and val.is_a? Array and val.count > 1
162     end
163     if /^created_at\b/ =~ @order[0] and kind_filters.count == 1
164       # If filtering on multiple types and sorting by date: Get the
165       # first page of each type, sort the entire set, truncate to one
166       # page, and use the last item on this page as a filter for
167       # retrieving the next page. Ideally the API would do this for
168       # us, but it doesn't (yet).
169       nextpage_operator = /\bdesc$/i =~ @order[0] ? '<' : '>'
170       @objects = []
171       @name_link_for = {}
172       kind_filters.each do |attr,op,val|
173         (val.is_a?(Array) ? val : [val]).each do |type|
174           objects = @object.contents(order: @order,
175                                      limit: @limit,
176                                      include_linked: true,
177                                      filters: (@filters - kind_filters + [['uuid', 'is_a', type]]),
178                                      offset: @offset)
179           objects.each do |object|
180             @name_link_for[object.andand.uuid] = objects.links_for(object, 'name').first
181           end
182           @objects += objects
183         end
184       end
185       @objects = @objects.to_a.sort_by(&:created_at)
186       @objects.reverse! if nextpage_operator == '<'
187       @objects = @objects[0..@limit-1]
188       @next_page_filters = @filters.reject do |attr,op,val|
189         attr == 'created_at' and op == nextpage_operator
190       end
191       if @objects.any?
192         @next_page_filters += [['created_at',
193                                 nextpage_operator,
194                                 @objects.last.created_at]]
195         @next_page_href = url_for(partial: :contents_rows,
196                                   filters: @next_page_filters.to_json)
197       else
198         @next_page_href = nil
199       end
200     else
201       @objects = @object.contents(order: @order,
202                                   limit: @limit,
203                                   include_linked: true,
204                                   filters: @filters,
205                                   offset: @offset)
206       @next_page_href = next_page_href(partial: :contents_rows)
207     end
208
209     preload_links_for_objects(@objects.to_a)
210   end
211
212   def show
213     if !@object
214       return render_not_found("object not found")
215     end
216
217     if params[:partial]
218       load_contents_objects
219       respond_to do |f|
220         f.json {
221           render json: {
222             content: render_to_string(partial: 'show_contents_rows.html',
223                                       formats: [:html]),
224             next_page_href: @next_page_href
225           }
226         }
227       end
228     else
229       @objects = []
230       super
231     end
232   end
233
234   def create
235     @new_resource_attrs = (params['project'] || {}).merge(group_class: 'project')
236     @new_resource_attrs[:name] ||= 'New project'
237     super
238   end
239
240   def update
241     @updates = params['project']
242     super
243   end
244
245   helper_method :get_objects_and_names
246   def get_objects_and_names(objects=nil)
247     objects = @objects if objects.nil?
248     objects_and_names = []
249     objects.each do |object|
250       if objects.respond_to? :links_for and
251           !(name_links = objects.links_for(object, 'name')).empty?
252         name_links.each do |name_link|
253           objects_and_names << [object, name_link]
254         end
255       elsif @name_link_for.andand[object.uuid]
256         objects_and_names << [object, @name_link_for[object.uuid]]
257       elsif object.respond_to? :name
258         objects_and_names << [object, object]
259       else
260         objects_and_names << [object,
261                                Link.new(owner_uuid: @object.uuid,
262                                         tail_uuid: @object.uuid,
263                                         head_uuid: object.uuid,
264                                         link_class: "name",
265                                         name: "")]
266
267       end
268     end
269     objects_and_names
270   end
271
272   def share_with
273     if not params[:uuids].andand.any?
274       @errors = ["No user/group UUIDs specified to share with."]
275       return render_error(status: 422)
276     end
277     results = {"success" => [], "errors" => []}
278     params[:uuids].each do |shared_uuid|
279       begin
280         Link.create(tail_uuid: shared_uuid, link_class: "permission",
281                     name: "can_read", head_uuid: @object.uuid)
282       rescue ArvadosApiClient::ApiError => error
283         error_list = error.api_response.andand[:errors]
284         if error_list.andand.any?
285           results["errors"] += error_list.map { |e| "#{shared_uuid}: #{e}" }
286         else
287           error_code = error.api_status || "Bad status"
288           results["errors"] << "#{shared_uuid}: #{error_code} response"
289         end
290       else
291         results["success"] << shared_uuid
292       end
293     end
294     if results["errors"].empty?
295       results.delete("errors")
296       status = 200
297     else
298       status = 422
299     end
300     respond_to do |f|
301       f.json { render(json: results, status: status) }
302     end
303   end
304 end