3e7bfcc71dea4f5c5094ba7181eb38e1f9370d6f
[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/ =~ @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       @objects = []
142       kind_filters.each do |attr,op,val|
143         (val.is_a?(Array) ? val : [val]).each do |type|
144           @objects += @object.contents(order: @order,
145                                        limit: @limit,
146                                        include_linked: true,
147                                        filters: (@filters - kind_filters + [['uuid', 'is_a', type]]),
148                                        offset: @offset)
149         end
150       end
151       @objects = @objects.to_a.sort_by(&:created_at).reverse[0..@limit-1]
152       @next_page_filters = @filters.reject do |attr,op,val|
153         attr == 'created_at' and op == '<'
154       end
155       if @objects.any?
156         @next_page_filters += [['created_at', '<', @objects.last.created_at]]
157         @next_page_href = url_for(partial: :contents_rows,
158                                   filters: @next_page_filters.to_json)
159       else
160         @next_page_href = nil
161       end
162     else
163       @objects = @object.contents(order: @order,
164                                   limit: @limit,
165                                   include_linked: true,
166                                   filters: @filters,
167                                   offset: @offset)
168       @next_page_href = next_page_href(partial: :contents_rows)
169     end
170   end
171
172   def show
173     if !@object
174       return render_not_found("object not found")
175     end
176
177     @user_is_manager = false
178     @share_links = []
179     if @object.uuid != current_user.uuid
180       begin
181         @share_links = Link.permissions_for(@object)
182         @user_is_manager = true
183       rescue ArvadosApiClient::AccessForbiddenException,
184         ArvadosApiClient::NotFoundException
185       end
186     end
187
188     if params[:partial]
189       load_contents_objects
190       respond_to do |f|
191         f.json {
192           render json: {
193             content: render_to_string(partial: 'show_contents_rows.html',
194                                       formats: [:html]),
195             next_page_href: @next_page_href
196           }
197         }
198       end
199     else
200       @objects = []
201       super
202     end
203   end
204
205   def create
206     @new_resource_attrs = (params['project'] || {}).merge(group_class: 'project')
207     @new_resource_attrs[:name] ||= 'New project'
208     super
209   end
210
211   def update
212     @updates = params['project']
213     super
214   end
215
216   helper_method :get_objects_and_names
217   def get_objects_and_names(objects=nil)
218     objects = @objects if objects.nil?
219     objects_and_names = []
220     objects.each do |object|
221       if objects.respond_to? :links_for and
222           !(name_links = objects.links_for(object, 'name')).empty?
223         name_links.each do |name_link|
224           objects_and_names << [object, name_link]
225         end
226       elsif object.respond_to? :name
227         objects_and_names << [object, object]
228       else
229         objects_and_names << [object,
230                                Link.new(owner_uuid: @object.uuid,
231                                         tail_uuid: @object.uuid,
232                                         head_uuid: object.uuid,
233                                         link_class: "name",
234                                         name: "")]
235       end
236     end
237     objects_and_names
238   end
239
240   def share_with
241     if not params[:uuids].andand.any?
242       @errors = ["No user/group UUIDs specified to share with."]
243       return render_error(status: 422)
244     end
245     results = {"success" => [], "errors" => []}
246     params[:uuids].each do |shared_uuid|
247       begin
248         Link.create(tail_uuid: shared_uuid, link_class: "permission",
249                     name: "can_read", head_uuid: @object.uuid)
250       rescue ArvadosApiClient::ApiError => error
251         error_list = error.api_response.andand[:errors]
252         if error_list.andand.any?
253           results["errors"] += error_list.map { |e| "#{shared_uuid}: #{e}" }
254         else
255           error_code = error.api_status || "Bad status"
256           results["errors"] << "#{shared_uuid}: #{error_code} response"
257         end
258       else
259         results["success"] << shared_uuid
260       end
261     end
262     if results["errors"].empty?
263       results.delete("errors")
264       status = 200
265     else
266       status = 422
267     end
268     respond_to do |f|
269       f.json { render(json: results, status: status) }
270     end
271   end
272 end