a45c740a5d923e0790221ccd057bd880fe02dd20
[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     kind_filters = @filters.select do |attr,op,val|
133       op == 'is_a' and val.is_a? Array and val.count > 1
134     end
135     if /^created_at\b/ =~ @order[0] and kind_filters.count == 1
136       # If filtering on multiple types and sorting by date: Get the
137       # first page of each type, sort the entire set, truncate to one
138       # page, and use the last item on this page as a filter for
139       # retrieving the next page. Ideally the API would do this for
140       # us, but it doesn't (yet).
141       nextpage_operator = /\bdesc$/i =~ @order[0] ? '<' : '>'
142       @objects = []
143       @name_link_for = {}
144       kind_filters.each do |attr,op,val|
145         (val.is_a?(Array) ? val : [val]).each do |type|
146           objects = @object.contents(order: @order,
147                                      limit: @limit,
148                                      include_linked: true,
149                                      filters: (@filters - kind_filters + [['uuid', 'is_a', type]]),
150                                      offset: @offset)
151           objects.each do |object|
152             @name_link_for[object.andand.uuid] = objects.links_for(object, 'name').first
153           end
154           @objects += objects
155         end
156       end
157       @objects = @objects.to_a.sort_by(&:created_at)
158       @objects.reverse! if nextpage_operator == '<'
159       @objects = @objects[0..@limit-1]
160       @next_page_filters = @filters.reject do |attr,op,val|
161         attr == 'created_at' and op == nextpage_operator
162       end
163       if @objects.any?
164         @next_page_filters += [['created_at',
165                                 nextpage_operator,
166                                 @objects.last.created_at]]
167         @next_page_href = url_for(partial: :contents_rows,
168                                   filters: @next_page_filters.to_json)
169       else
170         @next_page_href = nil
171       end
172     else
173       @objects = @object.contents(order: @order,
174                                   limit: @limit,
175                                   include_linked: true,
176                                   filters: @filters,
177                                   offset: @offset)
178       @next_page_href = next_page_href(partial: :contents_rows)
179
180       uuids = @objects.map { |ob| ob.uuid }
181       @object_tags = {}
182       Link.limit(uuids.length*20).filter([["head_uuid", "in", uuids], ["link_class", "=", "tag"]]).each do |t|
183         @object_tags[t.head_uuid] ||= []
184         @object_tags[t.head_uuid] << t
185       end
186     end
187   end
188
189   def show
190     if !@object
191       return render_not_found("object not found")
192     end
193
194     @user_is_manager = false
195     @share_links = []
196     if @object.uuid != current_user.uuid
197       begin
198         @share_links = Link.permissions_for(@object)
199         @user_is_manager = true
200       rescue ArvadosApiClient::AccessForbiddenException,
201         ArvadosApiClient::NotFoundException
202       end
203     end
204
205     if params[:partial]
206       load_contents_objects
207       respond_to do |f|
208         f.json {
209           render json: {
210             content: render_to_string(partial: 'show_contents_rows.html',
211                                       formats: [:html]),
212             next_page_href: @next_page_href
213           }
214         }
215       end
216     else
217       @objects = []
218       super
219     end
220   end
221
222   def create
223     @new_resource_attrs = (params['project'] || {}).merge(group_class: 'project')
224     @new_resource_attrs[:name] ||= 'New project'
225     super
226   end
227
228   def update
229     @updates = params['project']
230     super
231   end
232
233   helper_method :get_objects_and_names
234   def get_objects_and_names(objects=nil)
235     objects = @objects if objects.nil?
236     objects_and_names = []
237     objects.each do |object|
238       if objects.respond_to? :links_for and
239           !(name_links = objects.links_for(object, 'name')).empty?
240         name_links.each do |name_link|
241           objects_and_names << [object, name_link]
242         end
243       elsif @name_link_for.andand[object.uuid]
244         objects_and_names << [object, @name_link_for[object.uuid]]
245       elsif object.respond_to? :name
246         objects_and_names << [object, object]
247       else
248         objects_and_names << [object,
249                                Link.new(owner_uuid: @object.uuid,
250                                         tail_uuid: @object.uuid,
251                                         head_uuid: object.uuid,
252                                         link_class: "name",
253                                         name: "")]
254       end
255     end
256     objects_and_names
257   end
258
259   def share_with
260     if not params[:uuids].andand.any?
261       @errors = ["No user/group UUIDs specified to share with."]
262       return render_error(status: 422)
263     end
264     results = {"success" => [], "errors" => []}
265     params[:uuids].each do |shared_uuid|
266       begin
267         Link.create(tail_uuid: shared_uuid, link_class: "permission",
268                     name: "can_read", head_uuid: @object.uuid)
269       rescue ArvadosApiClient::ApiError => error
270         error_list = error.api_response.andand[:errors]
271         if error_list.andand.any?
272           results["errors"] += error_list.map { |e| "#{shared_uuid}: #{e}" }
273         else
274           error_code = error.api_status || "Bad status"
275           results["errors"] << "#{shared_uuid}: #{error_code} response"
276         end
277       else
278         results["success"] << shared_uuid
279       end
280     end
281     if results["errors"].empty?
282       results.delete("errors")
283       status = 200
284     else
285       status = 422
286     end
287     respond_to do |f|
288       f.json { render(json: results, status: status) }
289     end
290   end
291 end