Merge branch 'master' into 3586-job-priority
[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 destroy
81     while (objects = Link.filter([['owner_uuid','=',@object.uuid],
82                                   ['tail_uuid','=',@object.uuid]])).any?
83       objects.each do |object|
84         object.destroy
85       end
86     end
87     while (objects = @object.contents(include_linked: false)).any?
88       objects.each do |object|
89         object.update_attributes! owner_uuid: current_user.uuid
90       end
91     end
92     if ArvadosBase::resource_class_for_uuid(@object.owner_uuid) == Group
93       params[:return_to] ||= group_path(@object.owner_uuid)
94     else
95       params[:return_to] ||= projects_path
96     end
97     super
98   end
99
100   def find_objects_for_index
101     @objects = all_projects
102     super
103   end
104
105   def load_contents_objects kinds=[]
106     kind_filters = @filters.select do |attr,op,val|
107       op == 'is_a' and val.is_a? Array and val.count > 1
108     end
109     if /^created_at\b/ =~ @order[0] and kind_filters.count == 1
110       # If filtering on multiple types and sorting by date: Get the
111       # first page of each type, sort the entire set, truncate to one
112       # page, and use the last item on this page as a filter for
113       # retrieving the next page. Ideally the API would do this for
114       # us, but it doesn't (yet).
115       nextpage_operator = /\bdesc$/i =~ @order[0] ? '<' : '>'
116       @objects = []
117       @name_link_for = {}
118       kind_filters.each do |attr,op,val|
119         (val.is_a?(Array) ? val : [val]).each do |type|
120           objects = @object.contents(order: @order,
121                                      limit: @limit,
122                                      include_linked: true,
123                                      filters: (@filters - kind_filters + [['uuid', 'is_a', type]]),
124                                      offset: @offset)
125           objects.each do |object|
126             @name_link_for[object.andand.uuid] = objects.links_for(object, 'name').first
127           end
128           @objects += objects
129         end
130       end
131       @objects = @objects.to_a.sort_by(&:created_at)
132       @objects.reverse! if nextpage_operator == '<'
133       @objects = @objects[0..@limit-1]
134       @next_page_filters = @filters.reject do |attr,op,val|
135         attr == 'created_at' and op == nextpage_operator
136       end
137       if @objects.any?
138         @next_page_filters += [['created_at',
139                                 nextpage_operator,
140                                 @objects.last.created_at]]
141         @next_page_href = url_for(partial: :contents_rows,
142                                   filters: @next_page_filters.to_json)
143       else
144         @next_page_href = nil
145       end
146     else
147       @objects = @object.contents(order: @order,
148                                   limit: @limit,
149                                   include_linked: true,
150                                   filters: @filters,
151                                   offset: @offset)
152       @next_page_href = next_page_href(partial: :contents_rows)
153     end
154
155     preload_links_for_objects(@objects.to_a)
156   end
157
158   def show
159     if !@object
160       return render_not_found("object not found")
161     end
162
163     @user_is_manager = false
164     @share_links = []
165     if @object.uuid != current_user.uuid
166       begin
167         @share_links = Link.permissions_for(@object)
168         @user_is_manager = true
169       rescue ArvadosApiClient::AccessForbiddenException,
170         ArvadosApiClient::NotFoundException
171       end
172     end
173
174     if params[:partial]
175       load_contents_objects
176       respond_to do |f|
177         f.json {
178           render json: {
179             content: render_to_string(partial: 'show_contents_rows.html',
180                                       formats: [:html]),
181             next_page_href: @next_page_href
182           }
183         }
184       end
185     else
186       @objects = []
187       super
188     end
189   end
190
191   def create
192     @new_resource_attrs = (params['project'] || {}).merge(group_class: 'project')
193     @new_resource_attrs[:name] ||= 'New project'
194     super
195   end
196
197   def update
198     @updates = params['project']
199     super
200   end
201
202   helper_method :get_objects_and_names
203   def get_objects_and_names(objects=nil)
204     objects = @objects if objects.nil?
205     objects_and_names = []
206     objects.each do |object|
207       if objects.respond_to? :links_for and
208           !(name_links = objects.links_for(object, 'name')).empty?
209         name_links.each do |name_link|
210           objects_and_names << [object, name_link]
211         end
212       elsif @name_link_for.andand[object.uuid]
213         objects_and_names << [object, @name_link_for[object.uuid]]
214       elsif object.respond_to? :name
215         objects_and_names << [object, object]
216       elsif not Collection.attribute_info.include?(:name)
217         objects_and_names << [object,
218                                Link.new(owner_uuid: @object.uuid,
219                                         tail_uuid: @object.uuid,
220                                         head_uuid: object.uuid,
221                                         link_class: "name",
222                                         name: "")]
223       end
224     end
225     objects_and_names
226   end
227
228   def share_with
229     if not params[:uuids].andand.any?
230       @errors = ["No user/group UUIDs specified to share with."]
231       return render_error(status: 422)
232     end
233     results = {"success" => [], "errors" => []}
234     params[:uuids].each do |shared_uuid|
235       begin
236         Link.create(tail_uuid: shared_uuid, link_class: "permission",
237                     name: "can_read", head_uuid: @object.uuid)
238       rescue ArvadosApiClient::ApiError => error
239         error_list = error.api_response.andand[:errors]
240         if error_list.andand.any?
241           results["errors"] += error_list.map { |e| "#{shared_uuid}: #{e}" }
242         else
243           error_code = error.api_status || "Bad status"
244           results["errors"] << "#{shared_uuid}: #{error_code} response"
245         end
246       else
247         results["success"] << shared_uuid
248       end
249     end
250     if results["errors"].empty?
251       results.delete("errors")
252       status = 200
253     else
254       status = 422
255     end
256     respond_to do |f|
257       f.json { render(json: results, status: status) }
258     end
259   end
260 end