From ab85ee516948301e8b2e7d3ddb0bec229d3f15a1 Mon Sep 17 00:00:00 2001 From: z9hang Date: Mon, 26 May 2014 16:25:53 +0800 Subject: [PATCH 01/22] =?UTF-8?q?admin=E4=B8=AA=E4=BA=BA=E4=B8=BB=E9=A1=B5?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=8A=9F=E8=83=BD=E4=BF=AE=E6=AD=A3=20admin?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=94=A8=E6=88=B7=E7=9A=84=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E4=BB=8E/users/search=E4=B8=AD=E5=88=86?= =?UTF-8?q?=E7=A6=BB=E5=88=B0/admin/search=20admin=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=95=8C=E9=9D=A2=E6=B7=BB=E5=8A=A0=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=95=8C=E9=9D=A2=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin_controller.rb | 40 ++++++++++++++- app/controllers/users_controller.rb | 12 ++--- app/views/admin/search.html.erb | 68 ++++++++++++++++++++++++++ app/views/admin/users.html.erb | 2 +- app/views/users/search.html.erb | 76 ++--------------------------- config/routes.rb | 1 + 6 files changed, 120 insertions(+), 79 deletions(-) create mode 100644 app/views/admin/search.html.erb diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index ab3d7b12..2a3ce15d 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -149,7 +149,6 @@ class AdminController < ApplicationController respond_to do |format| format.html { @groups = Group.all.sort - render :layout => @user_base_tag } format.api end @@ -196,4 +195,43 @@ class AdminController < ApplicationController [:text_rmagick_available, Object.const_defined?(:Magick)] ] end + #管理功能用户列表的搜索 + def search + sort_init 'login', 'asc' + sort_update %w(login firstname lastname mail admin created_on last_login_on) + + case params[:format] + when 'xml', 'json' + @offset, @limit = api_offset_and_limit({:limit => 15}) + else + @limit = 15#per_page_option + end + + @status = params[:status] || 1 + has = { + "show_changesets" => true + } + scope = User.logged.status(@status) + scope = scope.like(params[:name]) if params[:name].present? + @user_count = scope.count + @user_pages = Paginator.new @user_count, @limit, params['page'] + @user_base_tag = params[:id] ? 'base_users':'base' + @offset ||= @user_pages.reverse_offset + unless @offset == 0 + @users = scope.offset(@offset).limit(@limit).all.reverse + else + limit = @user_count % @limit + if limit == 0 + limit = @limit + end + @users = scope.offset(@offset).limit(limit).all.reverse + end + + respond_to do |format| + format.html { + @groups = Group.all.sort + } + format.api + end + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index da9aef30..82ea7e03 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -70,8 +70,8 @@ class UsersController < ApplicationController cond = Project.visible_condition(User.current) + " AND projects.project_type <> 1" @memberships = @user.memberships.all(:conditions => cond) end - events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 20) - @events_by_day = events.group_by(&:event_date) + #events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 20) + #@events_by_day = events.group_by(&:event_date) @state = 0 @@ -289,7 +289,7 @@ class UsersController < ApplicationController when '0' @offset ||= @user_pages.reverse_offset unless @offset == 0 - @users_statuses = scope.offset(@offset).limit(@limit).all.reverse + @users_statuses = scope.offset(@offset).limit(@limit).all.reverse else limit = @user_count % @limit if limit == 0 @@ -313,7 +313,7 @@ class UsersController < ApplicationController end @s_type = 1 #sort {|x,y| y.user_status.changesets_count <=> x.user_status.changesets_count} - #@users = @users[@offset, @limit] + #@users = @users[@offset, @limit] when '2' @offset ||= @user_pages.reverse_offset unless @offset == 0 @@ -326,9 +326,9 @@ class UsersController < ApplicationController @users_statuses = scope.reorder('watchers_count').offset(@offset).limit(limit).all.reverse end @s_type = 2 - #@users = @users[@offset, @limit] + #@users = @users[@offset, @limit] end - + else @offset ||= @user_pages.reverse_offset unless @offset == 0 diff --git a/app/views/admin/search.html.erb b/app/views/admin/search.html.erb new file mode 100644 index 00000000..17d91e78 --- /dev/null +++ b/app/views/admin/search.html.erb @@ -0,0 +1,68 @@ +<% if User.current.admin? %> +
+ <%= link_to l(:label_user_new), new_user_path, :class => 'icon icon-add' %> +
+ +

<%= l(:label_user_plural)%>

+ + <%= form_tag(:controller => 'admin', :action => 'search', :method => :get) do %> +
+ + <%= l(:label_filter_plural) %> + + + <%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> + + <% if @groups.present? %> + + <%= select_tag 'group_id', content_tag('option') + options_from_collection_for_select(@groups, :id, :name, params[:group_id].to_i), :onchange => "this.form.submit(); return false;" %> + <% end %> + + + <%= text_field_tag 'name', params[:name], :size => 30 %> + <%= submit_tag l(:label_search), :class => "small", :name => nil %> +
+ <% end %> +   + +
+ + + + <%= sort_header_tag('login', :caption => l(:field_login)) %> + <%= sort_header_tag('firstname', :caption => l(:field_firstname)) %> + <%= sort_header_tag('lastname', :caption => l(:field_lastname)) %> + <%= sort_header_tag('mail', :caption => l(:field_mail)) %> + + <%= sort_header_tag('admin', :caption => l(:field_admin), :default_order => 'desc') %> + <%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %> + <%= sort_header_tag('last_login_on', :caption => l(:field_last_login_on), :default_order => 'desc') %> + + + + + <% for user in @users -%> + "> + + + + + + + + + + <% end -%> + +
<%= avatar(user, :size => "14") %><%= link_to h(user.login), edit_user_path(user) %><%= h(user.firstname) %><%= h(user.lastname) %><%= checked_image user.admin? %><%= format_time(user.created_on) %> <%= change_status_link(user) %> + <%= delete_link user_path(user, :back_url => users_path(params)) unless User.current == user %>
+
+ + + <% html_title(l(:label_user_plural)) -%> + +<% end %> \ No newline at end of file diff --git a/app/views/admin/users.html.erb b/app/views/admin/users.html.erb index 77931646..4dceb5fd 100644 --- a/app/views/admin/users.html.erb +++ b/app/views/admin/users.html.erb @@ -5,7 +5,7 @@

<%= l(:label_user_plural)%>

- <%= form_tag(:controller => 'users', :action => 'search', :method => :get) do %> + <%= form_tag(:controller => 'admin', :action => 'search', :method => :get) do %>
<%= l(:label_filter_plural) %> diff --git a/app/views/users/search.html.erb b/app/views/users/search.html.erb index b2143ec6..307803b1 100644 --- a/app/views/users/search.html.erb +++ b/app/views/users/search.html.erb @@ -1,71 +1,4 @@ -<% if User.current.admin? %> -
- <%= link_to l(:label_user_new), new_user_path, :class => 'icon icon-add' %> -
-

<%= l(:label_user_plural)%>

- -<%= form_tag(:controller => 'users', :action => 'search', :method => :get) do %> -
- - <%= l(:label_filter_plural) %> - - - <%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> - - <% if @groups.present? %> - - <%= select_tag 'group_id', content_tag('option') + options_from_collection_for_select(@groups, :id, :name, params[:group_id].to_i), :onchange => "this.form.submit(); return false;" %> - <% end %> - - - <%= text_field_tag 'name', params[:name], :size => 30 %> - <%= submit_tag l(:label_search), :class => "small", :name => nil %> -
-<% end %> -  - -
- - - - <%= sort_header_tag('login', :caption => l(:field_login)) %> - <%= sort_header_tag('firstname', :caption => l(:field_firstname)) %> - <%= sort_header_tag('lastname', :caption => l(:field_lastname)) %> - <%= sort_header_tag('mail', :caption => l(:field_mail)) %> - - <%= sort_header_tag('admin', :caption => l(:field_admin), :default_order => 'desc') %> - <%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %> - <%= sort_header_tag('last_login_on', :caption => l(:field_last_login_on), :default_order => 'desc') %> - - - - - <% for user in @users -%> - "> - - - - - - - - - - <% end -%> - -
<%= avatar(user, :size => "14") %><%= link_to h(user.login), edit_user_path(user) %><%= h(user.firstname) %><%= h(user.lastname) %><%= checked_image user.admin? %><%= format_time(user.created_on) %> <%= change_status_link(user) %> - <%= delete_link user_path(user, :back_url => users_path(params)) unless User.current == user %>
-
- - -<% html_title(l(:label_user_plural)) -%> - -<% else %>
<%= form_tag(:controller => 'users', :action => 'search', :method => :get) do %> @@ -87,8 +20,9 @@ <%=link_to l(:field_homepage), home_path %> > <%=link_to l(:label_software_user), :controller => 'users', :action => 'index' %> - <% end %> -
+ +<% end %> +
<% if @users.size > 0 %> @@ -125,7 +59,7 @@ <% html_title(l(:label_user_plural)) -%> -<% end -%> + diff --git a/config/routes.rb b/config/routes.rb index 43b760ed..074fbe16 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -521,6 +521,7 @@ RedmineApp::Application.routes.draw do match 'admin', :controller => 'admin', :action => 'index', :via => :get match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get match 'admin/users', :controller => 'admin', :action => 'users', :via => :get + match 'admin/search', :controller => 'admin', :action => 'search', :via => [:get, :post] match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get match 'admin/info', :controller => 'admin', :action => 'info', :via => :get match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get From 5d4eec010ebc643b7d2208a21e95bfbaa8d3151f Mon Sep 17 00:00:00 2001 From: z9hang Date: Mon, 26 May 2014 16:49:41 +0800 Subject: [PATCH 02/22] =?UTF-8?q?=E9=9A=90=E8=97=8F=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E7=94=A8=E6=88=B7=E7=9A=84=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E5=90=8E=E8=B7=B3=E5=88=B0=E7=9A=84=E6=A0=87=E7=AD=BE=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=98=BE=E7=A4=BA=E7=9A=84=E7=94=A8=E6=88=B7=E9=82=AE?= =?UTF-8?q?=E7=AE=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/tags/_show_users.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/tags/_show_users.html.erb b/app/views/tags/_show_users.html.erb index bee210b2..29a3f76b 100644 --- a/app/views/tags/_show_users.html.erb +++ b/app/views/tags/_show_users.html.erb @@ -6,7 +6,7 @@ <%= l(:label_tags_user_name) %><%= link_to ("#{user.firstname+user.lastname}"), :controller => "users",:action => "show",:id => user.id%>
- <%= l(:label_tags_user_mail) %><%= mail_to(h(user.mail)) %> +

<% end %> From ddf5590c5470467e647f72f044b55adccf208ba2 Mon Sep 17 00:00:00 2001 From: z9hang Date: Mon, 26 May 2014 17:04:55 +0800 Subject: [PATCH 03/22] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E4=B8=BB=E9=A1=B5=E6=9F=A5=E7=9C=8B=E5=85=B3=E6=B3=A8=E7=9A=84?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=97=B6=E6=8A=A5user=5Fid=E6=8C=87=E4=BB=A3?= =?UTF-8?q?=E4=B8=8D=E6=98=8E=E7=9A=84sql=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/users_controller.rb | 2 +- app/views/users/list.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 82ea7e03..cdecb97f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -565,7 +565,7 @@ class UsersController < ApplicationController end def watch_projects - @watch_projects = Project.joins(:watchers).where("project_type <>? and watchable_type = ? and user_id = ?", '1','Project', @user.id) + @watch_projects = Project.joins(:watchers).where("project_type <>? and watchable_type = ? and `watchers`.user_id = ?", '1','Project', @user.id) @state = 1 respond_to do |format| format.html { diff --git a/app/views/users/list.html.erb b/app/views/users/list.html.erb index 53948fdc..a40b7c90 100644 --- a/app/views/users/list.html.erb +++ b/app/views/users/list.html.erb @@ -75,7 +75,7 @@ <% html_title(l(:label_user_plural)) -%> From 68b9a7efc5c663da197ee61196fecc56cfc08d29 Mon Sep 17 00:00:00 2001 From: nwb Date: Tue, 27 May 2014 14:21:59 +0800 Subject: [PATCH 04/22] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=89=B9=E5=87=86?= =?UTF-8?q?=E6=88=90=E5=91=98=E7=94=B3=E8=AF=B7=E6=9C=AA=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E6=97=B6=E7=9A=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/members_controller.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index d8e583bf..30c6c7e8 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -76,8 +76,7 @@ class MembersController < ApplicationController # ProjectInfo.create(:name => "test", :user_id => 123) end ## end - AppliedProject.deleteappiled(user_id, @project.id) - end + end else members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id]) user_grades << UserGrade.new(:user_id => params[:membership][:user_id], :project_id => @project.id) @@ -95,6 +94,13 @@ class MembersController < ApplicationController end end + if members.present? && members.all? {|m| m.valid? } + members.each do |member| + AppliedProject.deleteappiled(member.user_id, @project.id) + end + + end + respond_to do |format| format.html { redirect_to_settings_in_projects } format.js { @members = members;@applied_members = applied_members; } From bf12692de42d0a5320750570d7d4ff6c89786a65 Mon Sep 17 00:00:00 2001 From: z9hang Date: Tue, 27 May 2014 17:04:29 +0800 Subject: [PATCH 05/22] =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=90=8D=3D=3D?= =?UTF-8?q?=E3=80=8B=E8=B4=A6=E5=8F=B7/=E9=82=AE=E7=AE=B1=20=E8=80=81?= =?UTF-8?q?=E5=B8=88=E5=9C=A8=E8=AF=BE=E7=A8=8B=E5=8A=A8=E6=80=81=E4=B8=AD?= =?UTF-8?q?=E7=9C=8B=E5=88=B0=E5=AD=A6=E7=94=9F=E7=9C=9F=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/projects_controller.rb | 5 +++++ app/helpers/application_helper.rb | 9 +++++++-- app/views/projects/show.html.erb | 14 ++++++++++++-- config/locales/en.yml | 2 +- config/locales/zh.yml | 2 +- .../redpenny-master/stylesheets/application.css | 2 +- 6 files changed, 27 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index a6dd7cf3..1cf14a83 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -716,6 +716,11 @@ class ProjectsController < ApplicationController @document = @project.documents.build # @base_courses_tag = @project.project_type + #判断能否显示真名(当前用户为课程的教师时显示真名) + if @project.project_type == Project::ProjectType_course + @teachers= searchTeacherAndAssistant(@project) + @canShowRealName = isCourseTeacher(User.current.id) + end respond_to do |format| format.html{render :layout => 'base_courses' if @base_courses_tag==1} format.api diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 76da3b68..7dc4b555 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -58,9 +58,14 @@ module ApplicationHelper end # Displays a link to user's account page if active - def link_to_user(user, options={}) + def link_to_user(user, canShowRealName = false, options={}) if user.is_a?(User) - name = h(user.name(options[:format])) + if canShowRealName + name = h(user.realname(options[:format])) + else + name = h(user.name(options[:format])) + end + if user.active? || (User.current.admin? && user.logged?) link_to name, {:controller=> 'users', :action => 'show', id: user.id, host: Setting.user_domain}, :class => user.css_classes else diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index eb66efc7..dfe8a10b 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -18,7 +18,7 @@ <%= h(e.project) if @project.nil? || @project.id != e.project.id %> - <%= link_to_user(e.event_author) if e.respond_to?(:event_author) %> + <%= link_to_user(e.event_author,@canShowRealName) if e.respond_to?(:event_author) %> <%= l(:label_new_activity) %> <%= link_to "#{eventToLanguageCourse(e.event_type, @project)}: "<< format_activity_title(e.event_title), (e.event_type.eql?("attachment")&&e.container.kind_of?(Project)) ? project_files_path(e.container) : e.event_url %> @@ -46,6 +46,7 @@ <% if format_date(day) == format_date(@date_to - @days) %> +

Test

@@ -80,7 +81,16 @@
- +
<%= link_to (h @user.try(:name)), user_path(@user) if @user %> <%= l(:label_user_create_project) %> <%= link_to @project.name %> ! + <% + #判断是否显示真名 + if @canShowRealName + %> + <%= link_to (h @user.try(:realname)), user_path(@user) if @user %> + <% else %> + <%= link_to (h @user.try(:name)), user_path(@user) if @user %> + <% end %> + <%= l(:label_user_create_project) %> <%= link_to @project.name %> !
<%= l :label_update_time %>: <%= format_time(@project.created_on) %> diff --git a/config/locales/en.yml b/config/locales/en.yml index fd4368be..22422c42 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -266,7 +266,7 @@ en: field_is_public: Public field_parent: Subproject of field_is_in_roadmap: Issues displayed in roadmap - field_login: Login + field_login: Account/Email field_mail_notification: Email notifications field_admin: Administrator field_last_login_on: Last connection diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 96b37e76..39749b69 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -284,7 +284,7 @@ zh: field_is_public: 公开 field_parent: 上级项目 field_is_in_roadmap: 在路线图中显示 - field_login: 登录名 + field_login: 账户/邮箱 field_mail_notification: 邮件通知 field_admin: 管理员 field_last_login_on: 最后登录 diff --git a/public/themes/redpenny-master/stylesheets/application.css b/public/themes/redpenny-master/stylesheets/application.css index a94ee45b..7de766ce 100644 --- a/public/themes/redpenny-master/stylesheets/application.css +++ b/public/themes/redpenny-master/stylesheets/application.css @@ -1001,7 +1001,7 @@ ol li ol li ol li padding:25px 28px; background:#fff; text-align:left; - width:410px; + width:420px; margin:10px auto; font-family:lucida grande,verdana; font-size:12px; From 27a30d44be0726630ff2911a34eeebc7e74f4dac Mon Sep 17 00:00:00 2001 From: sw <939547590@qq.com> Date: Tue, 27 May 2014 20:01:21 +0800 Subject: [PATCH 06/22] =?UTF-8?q?1.=E4=BF=AE=E6=94=B9=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E7=94=A8=E6=88=B7=E6=98=AF=E4=B8=8D=E6=98=AF?= =?UTF-8?q?=E4=B8=BA=E8=AF=BE=E7=A8=8B=E8=80=81=E5=B8=88=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E6=B3=95=202.=E5=A2=9E=E5=8A=A0=E4=BD=9C=E4=B8=9A=E7=BB=BC?= =?UTF-8?q?=E8=AF=84=E5=8A=9F=E8=83=BD=203.=E5=85=B6=E4=BB=96=E8=AF=BE?= =?UTF-8?q?=E7=A8=8B=E7=9A=84=E5=AD=A6=E7=94=9F=E6=98=AF=E4=B8=8D=E8=83=BD?= =?UTF-8?q?=E4=BA=92=E8=AF=84=E7=9A=84=EF=BC=8C=E5=8F=AA=E6=9C=89=E6=9C=AC?= =?UTF-8?q?=E9=97=A8=E8=AF=BE=E7=A8=8B=E7=9A=84=E5=AD=A6=E7=94=9F=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E4=BA=92=E8=AF=84=204.=E4=BF=AE=E6=94=B9=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=AF=84=E8=AE=BA=E6=96=B9=E6=B3=95=205.=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=BD=93=E4=BD=9C=E4=B8=9A=E6=97=A0=E4=BA=BA=E8=AF=84?= =?UTF-8?q?=E8=AE=BA=E6=97=B6=EF=BC=8C=E4=BD=9C=E4=B8=9A=E6=9C=80=E7=BB=88?= =?UTF-8?q?=E5=BE=97=E5=88=86=E5=8F=AA=E6=98=BE=E7=A4=BA=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=88=86=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/homework_attach_controller.rb | 8 +-- app/helpers/bids_helper.rb | 21 ++++++++ app/helpers/courses_helper.rb | 24 ++++++--- app/models/homework_attach.rb | 4 +- app/views/bids/_homework_list.html.erb | 20 +++++--- app/views/homework_attach/_addjour.html.erb | 1 + .../_comprehensive_evaluation.html.erb | 50 ++++++++++++++++++ app/views/homework_attach/_showjour.html.erb | 27 +++++++++- app/views/homework_attach/addjours.js.erb | 13 +++-- app/views/homework_attach/show.html.erb | 51 +++++++++---------- ...coloum_to_homework_journals_for_message.rb | 5 ++ 11 files changed, 172 insertions(+), 52 deletions(-) create mode 100644 app/views/homework_attach/_comprehensive_evaluation.html.erb create mode 100644 db/migrate/20140527060344_add_coloum_to_homework_journals_for_message.rb diff --git a/app/controllers/homework_attach_controller.rb b/app/controllers/homework_attach_controller.rb index 8eae8b85..64bfe5c4 100644 --- a/app/controllers/homework_attach_controller.rb +++ b/app/controllers/homework_attach_controller.rb @@ -102,11 +102,12 @@ class HomeworkAttachController < ApplicationController percent_m.to_s + "%" end @limit = 10 - @jours = @homework.journals_for_messages.order("created_on DESC") + @jours = @homework.journals_for_messages.where("is_comprehensive_evaluation is null").order("created_on DESC") @feedback_count = @jours.count @feedback_pages = Paginator.new @feedback_count, @limit, params['page'] @offset ||= @feedback_pages.offset @jour = @jours[@offset, @limit] + @comprehensive_evaluation = @homework.journals_for_messages.where("is_comprehensive_evaluation is not null").order("created_on DESC") end #删除留言 @@ -127,13 +128,14 @@ class HomeworkAttachController < ApplicationController #添加留言 def addjours @homework = HomeworkAttach.find(params[:jour_id]) - @homework.addjours User.current.id, params[:new_form][:user_message],0 - @jours = @homework.journals_for_messages.order("created_on DESC") + @add_jour = @homework.addjours User.current.id, params[:new_form][:user_message],0,params[:is_comprehensive_evaluation] + @jours = @homework.journals_for_messages.where("is_comprehensive_evaluation is null").order("created_on DESC") @limit = 10 @feedback_count = @jours.count @feedback_pages = Paginator.new @feedback_count, @limit, params['page'] @offset ||= @feedback_pages.offset @jour = @jours[@offset, @limit] + @comprehensive_evaluation = @homework.journals_for_messages.where("is_comprehensive_evaluation is not null").order("created_on DESC") respond_to do |format| format.js end diff --git a/app/helpers/bids_helper.rb b/app/helpers/bids_helper.rb index 721b3036..8b03d74c 100644 --- a/app/helpers/bids_helper.rb +++ b/app/helpers/bids_helper.rb @@ -145,6 +145,27 @@ module BidsHelper people.include?(User.current) end + # 当前用户是否加入了此课程(包括教师) + def is_cur_course_user? bid + people = [] + #people << bid.author + course = bid.courses.first + course.members.each do |member| + people << member.user + end + people.include?(User.current) + end + #当前用户是不是指定课程的学生 + def is_cur_course_student? course + people = [] + course.members.each do |member| + if [5,10].include? member.roles.first.id + people << member.user + end + end + people.include?(User.current) + end + # def select_option_helper option # tmp = Hash.new # option.each do |project| diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb index 45ebaf24..959d36cb 100644 --- a/app/helpers/courses_helper.rb +++ b/app/helpers/courses_helper.rb @@ -137,14 +137,24 @@ module CoursesHelper Course.find_by_extra(try(extra)) end #判断制定用户是不是当前课程的老师 - def is_course_teacher user,project - is_teacher = false - searchTeacherAndAssistant(project).each do |teacher| - if user == teacher - is_teacher = true - break + def is_course_teacher user,course + people = [] + course.members.each do |member| + role_id = member.roles.first.id + if TeacherRoles.include? role_id + people << member.user end end - is_teacher + people.include?(user) + end + #当前用户是不是指定课程的学生 + def is_cur_course_student? course + people = [] + course.members.each do |member| + if StudentRoles.include? member.roles.first.id + people << member.user + end + end + people.include?(User.current) end end diff --git a/app/models/homework_attach.rb b/app/models/homework_attach.rb index ac285e60..ad59d107 100644 --- a/app/models/homework_attach.rb +++ b/app/models/homework_attach.rb @@ -13,8 +13,8 @@ class HomeworkAttach < ActiveRecord::Base "user_id" acts_as_attachable - def addjours user_id,message,status = 0 - jfm = self.journals_for_messages.build(:user_id => user_id,:notes =>message,:status => status) + def addjours user_id,message,status = 0,is_comprehensive_evaluation = 0 + jfm = self.journals_for_messages.build(:user_id => user_id,:notes =>message,:status => status,:is_comprehensive_evaluation => is_comprehensive_evaluation) jfm.save jfm end diff --git a/app/views/bids/_homework_list.html.erb b/app/views/bids/_homework_list.html.erb index f7db8a37..48cc16c6 100644 --- a/app/views/bids/_homework_list.html.erb +++ b/app/views/bids/_homework_list.html.erb @@ -1,13 +1,15 @@ -<% is_teacher = is_course_teacher User.current,@bid.courses.first.project %> +<% is_student = is_cur_course_student? @bid.courses.first %> +<% is_teacher = is_course_teacher User.current,@bid.courses.first %> + <%= form_tag(:controller => 'bids', :action => "show_project", :method => :get) do %>
+ +
<%= l(:label_task_plural)%>(<%= @homework_list.count%>) <%= link_to "作业打包下载", zipdown_assort_path(obj_class: @bid.class, obj_id: @bid), remote: false, class: "button_submit button_submit_font_white", style: "margin: 5px 10px;line-height: 20px;height: 20px;display: inline-block;" if( - User.current.admin? || - !(User.current.roles_for_project(@bid.courses.first).map(&:id) & ([7,9])).empty? ) || + User.current.admin? || + !(User.current.roles_for_project(@bid.courses.first).map(&:id) & ([7,9])).empty? ) || (Rails.env.development?) %> @@ -25,8 +27,8 @@ <% @homework_list.each do |homework|%> <% if homework.attachments.any?%> - - + + ') + html.should_not include("
:value_for_inst_d
") + end + end + + it "doesn't die if the source file is not a real filename" do + exception.stub(:backtrace).and_return([ + ":10:in `spawn_rack_application'" + ]) + response.should include("Source unavailable") + end + end +end diff --git a/lib/better_errors/spec/better_errors/middleware_spec.rb b/lib/better_errors/spec/better_errors/middleware_spec.rb new file mode 100644 index 00000000..2c638bfa --- /dev/null +++ b/lib/better_errors/spec/better_errors/middleware_spec.rb @@ -0,0 +1,146 @@ +require "spec_helper" + +module BetterErrors + describe Middleware do + let(:app) { Middleware.new(->env { ":)" }) } + let(:exception) { RuntimeError.new("oh no :(") } + + it "passes non-error responses through" do + app.call({}).should == ":)" + end + + it "calls the internal methods" do + app.should_receive :internal_call + app.call("PATH_INFO" => "/__better_errors/1/preform_awesomness") + end + + it "calls the internal methods on any subfolder path" do + app.should_receive :internal_call + app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors/1/preform_awesomness") + end + + it "shows the error page" do + app.should_receive :show_error_page + app.call("PATH_INFO" => "/__better_errors/") + end + + it "shows the error page on any subfolder path" do + app.should_receive :show_error_page + app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors/") + end + + it "doesn't show the error page to a non-local address" do + app.should_not_receive :better_errors_call + app.call("REMOTE_ADDR" => "1.2.3.4") + end + + it "shows to a whitelisted IP" do + BetterErrors::Middleware.allow_ip! '77.55.33.11' + app.should_receive :better_errors_call + app.call("REMOTE_ADDR" => "77.55.33.11") + end + + it "doesn't blow up when given a blank REMOTE_ADDR" do + expect { app.call("REMOTE_ADDR" => " ") }.to_not raise_error + end + + it "doesn't blow up when given an IP address with a zone index" do + expect { app.call("REMOTE_ADDR" => "0:0:0:0:0:0:0:1%0" ) }.to_not raise_error + end + + context "when requesting the /__better_errors manually" do + let(:app) { Middleware.new(->env { ":)" }) } + + it "shows that no errors have been recorded" do + status, headers, body = app.call("PATH_INFO" => "/__better_errors") + body.join.should match /No errors have been recorded yet./ + end + + it "shows that no errors have been recorded on any subfolder path" do + status, headers, body = app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors") + body.join.should match /No errors have been recorded yet./ + end + end + + context "when handling an error" do + let(:app) { Middleware.new(->env { raise exception }) } + + it "returns status 500" do + status, headers, body = app.call({}) + + status.should == 500 + end + + context "original_exception" do + class OriginalExceptionException < Exception + attr_reader :original_exception + + def initialize(message, original_exception = nil) + super(message) + @original_exception = original_exception + end + end + + it "shows Original Exception if it responds_to and has an original_exception" do + app = Middleware.new(->env { + raise OriginalExceptionException.new("Other Exception", Exception.new("Original Exception")) + }) + + status, _, body = app.call({}) + + status.should == 500 + body.join.should_not match(/Other Exception/) + body.join.should match(/Original Exception/) + end + + it "won't crash if the exception responds_to but doesn't have an original_exception" do + app = Middleware.new(->env { + raise OriginalExceptionException.new("Other Exception") + }) + + status, _, body = app.call({}) + + status.should == 500 + body.join.should match(/Other Exception/) + end + end + + it "returns ExceptionWrapper's status_code" do + ad_ew = double("ActionDispatch::ExceptionWrapper") + ad_ew.stub('new').with({}, exception ){ double("ExceptionWrapper", status_code: 404) } + stub_const('ActionDispatch::ExceptionWrapper', ad_ew) + + status, headers, body = app.call({}) + + status.should == 404 + end + + it "returns UTF-8 error pages" do + status, headers, body = app.call({}) + + headers["Content-Type"].should match /charset=utf-8/ + end + + it "returns text pages by default" do + status, headers, body = app.call({}) + + headers["Content-Type"].should match /text\/plain/ + end + + it "returns HTML pages by default" do + # Chrome's 'Accept' header looks similar this. + status, headers, body = app.call("HTTP_ACCEPT" => "text/html,application/xhtml+xml;q=0.9,*/*") + + headers["Content-Type"].should match /text\/html/ + end + + it "logs the exception" do + logger = Object.new + logger.should_receive :fatal + BetterErrors.stub(:logger).and_return(logger) + + app.call({}) + end + end + end +end diff --git a/lib/better_errors/spec/better_errors/raised_exception_spec.rb b/lib/better_errors/spec/better_errors/raised_exception_spec.rb new file mode 100644 index 00000000..605ab409 --- /dev/null +++ b/lib/better_errors/spec/better_errors/raised_exception_spec.rb @@ -0,0 +1,52 @@ +require "spec_helper" + +module BetterErrors + describe RaisedException do + let(:exception) { RuntimeError.new("whoops") } + subject { RaisedException.new(exception) } + + its(:exception) { should == exception } + its(:message) { should == "whoops" } + its(:type) { should == RuntimeError } + + context "when the exception wraps another exception" do + let(:original_exception) { RuntimeError.new("something went wrong!") } + let(:exception) { double(:original_exception => original_exception) } + + its(:exception) { should == original_exception } + its(:message) { should == "something went wrong!" } + end + + context "when the exception is a syntax error" do + let(:exception) { SyntaxError.new("foo.rb:123: you made a typo!") } + + its(:message) { should == "you made a typo!" } + its(:type) { should == SyntaxError } + + it "has the right filename and line number in the backtrace" do + subject.backtrace.first.filename.should == "foo.rb" + subject.backtrace.first.line.should == 123 + end + end + + context "when the exception is a HAML syntax error" do + before do + stub_const("Haml::SyntaxError", Class.new(SyntaxError)) + end + + let(:exception) { + Haml::SyntaxError.new("you made a typo!").tap do |ex| + ex.set_backtrace(["foo.rb:123", "haml/internals/blah.rb:123456"]) + end + } + + its(:message) { should == "you made a typo!" } + its(:type) { should == Haml::SyntaxError } + + it "has the right filename and line number in the backtrace" do + subject.backtrace.first.filename.should == "foo.rb" + subject.backtrace.first.line.should == 123 + end + end + end +end diff --git a/lib/better_errors/spec/better_errors/repl/basic_spec.rb b/lib/better_errors/spec/better_errors/repl/basic_spec.rb new file mode 100644 index 00000000..c533f632 --- /dev/null +++ b/lib/better_errors/spec/better_errors/repl/basic_spec.rb @@ -0,0 +1,18 @@ +require "spec_helper" +require "better_errors/repl/basic" +require "better_errors/repl/shared_examples" + +module BetterErrors + module REPL + describe Basic do + let(:fresh_binding) { + local_a = 123 + binding + } + + let(:repl) { Basic.new fresh_binding } + + it_behaves_like "a REPL provider" + end + end +end diff --git a/lib/better_errors/spec/better_errors/repl/pry_spec.rb b/lib/better_errors/spec/better_errors/repl/pry_spec.rb new file mode 100644 index 00000000..1aa502c5 --- /dev/null +++ b/lib/better_errors/spec/better_errors/repl/pry_spec.rb @@ -0,0 +1,40 @@ +require "spec_helper" +require "pry" +require "better_errors/repl/pry" +require "better_errors/repl/shared_examples" + +module BetterErrors + module REPL + describe Pry do + let(:fresh_binding) { + local_a = 123 + binding + } + + let(:repl) { Pry.new fresh_binding } + + it "does line continuation" do + output, prompt, filled = repl.send_input "" + output.should == "=> nil\n" + prompt.should == ">>" + filled.should == "" + + output, prompt, filled = repl.send_input "def f(x)" + output.should == "" + prompt.should == ".." + filled.should == " " + + output, prompt, filled = repl.send_input "end" + if RUBY_VERSION >= "2.1.0" + output.should == "=> :f\n" + else + output.should == "=> nil\n" + end + prompt.should == ">>" + filled.should == "" + end + + it_behaves_like "a REPL provider" + end + end +end diff --git a/lib/better_errors/spec/better_errors/repl/shared_examples.rb b/lib/better_errors/spec/better_errors/repl/shared_examples.rb new file mode 100644 index 00000000..0154851d --- /dev/null +++ b/lib/better_errors/spec/better_errors/repl/shared_examples.rb @@ -0,0 +1,18 @@ +shared_examples_for "a REPL provider" do + it "evaluates ruby code in a given context" do + repl.send_input("local_a = 456") + fresh_binding.eval("local_a").should == 456 + end + + it "returns a tuple of output and the new prompt" do + output, prompt = repl.send_input("1 + 2") + output.should == "=> 3\n" + prompt.should == ">>" + end + + it "doesn't barf if the code throws an exception" do + output, prompt = repl.send_input("raise Exception") + output.should include "Exception: Exception" + prompt.should == ">>" + end +end diff --git a/lib/better_errors/spec/better_errors/stack_frame_spec.rb b/lib/better_errors/spec/better_errors/stack_frame_spec.rb new file mode 100644 index 00000000..420111ee --- /dev/null +++ b/lib/better_errors/spec/better_errors/stack_frame_spec.rb @@ -0,0 +1,157 @@ +require "spec_helper" + +module BetterErrors + describe StackFrame do + context "#application?" do + it "is true for application filenames" do + BetterErrors.stub(:application_root).and_return("/abc/xyz") + frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index") + + frame.application?.should be_true + end + + it "is false for everything else" do + BetterErrors.stub(:application_root).and_return("/abc/xyz") + frame = StackFrame.new("/abc/nope", 123, "foo") + + frame.application?.should be_false + end + + it "doesn't care if no application_root is set" do + frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index") + + frame.application?.should be_false + end + end + + context "#gem?" do + it "is true for gem filenames" do + Gem.stub(:path).and_return(["/abc/xyz"]) + frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo") + + frame.gem?.should be_true + end + + it "is false for everything else" do + Gem.stub(:path).and_return(["/abc/xyz"]) + frame = StackFrame.new("/abc/nope", 123, "foo") + + frame.gem?.should be_false + end + end + + context "#application_path" do + it "chops off the application root" do + BetterErrors.stub(:application_root).and_return("/abc/xyz") + frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index") + + frame.application_path.should == "app/controllers/crap_controller.rb" + end + end + + context "#gem_path" do + it "chops of the gem path and stick (gem) there" do + Gem.stub(:path).and_return(["/abc/xyz"]) + frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo") + + frame.gem_path.should == "whatever (1.2.3) lib/whatever.rb" + end + + it "prioritizes gem path over application path" do + BetterErrors.stub(:application_root).and_return("/abc/xyz") + Gem.stub(:path).and_return(["/abc/xyz/vendor"]) + frame = StackFrame.new("/abc/xyz/vendor/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo") + + frame.gem_path.should == "whatever (1.2.3) lib/whatever.rb" + end + end + + context "#pretty_path" do + it "returns #application_path for application paths" do + BetterErrors.stub(:application_root).and_return("/abc/xyz") + frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index") + frame.pretty_path.should == frame.application_path + end + + it "returns #gem_path for gem paths" do + Gem.stub(:path).and_return(["/abc/xyz"]) + frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo") + + frame.pretty_path.should == frame.gem_path + end + end + + it "special cases SyntaxErrors" do + begin + eval(%{ raise SyntaxError, "you wrote bad ruby!" }, nil, "my_file.rb", 123) + rescue SyntaxError => syntax_error + end + frames = StackFrame.from_exception(syntax_error) + frames.first.filename.should == "my_file.rb" + frames.first.line.should == 123 + end + + it "doesn't blow up if no method name is given" do + error = StandardError.allocate + + error.stub(:backtrace).and_return(["foo.rb:123"]) + frames = StackFrame.from_exception(error) + frames.first.filename.should == "foo.rb" + frames.first.line.should == 123 + + error.stub(:backtrace).and_return(["foo.rb:123: this is an error message"]) + frames = StackFrame.from_exception(error) + frames.first.filename.should == "foo.rb" + frames.first.line.should == 123 + end + + it "ignores a backtrace line if its format doesn't make any sense at all" do + error = StandardError.allocate + error.stub(:backtrace).and_return(["foo.rb:123:in `foo'", "C:in `find'", "bar.rb:123:in `bar'"]) + frames = StackFrame.from_exception(error) + frames.count.should == 2 + end + + it "doesn't blow up if a filename contains a colon" do + error = StandardError.allocate + error.stub(:backtrace).and_return(["crap:filename.rb:123"]) + frames = StackFrame.from_exception(error) + frames.first.filename.should == "crap:filename.rb" + end + + it "doesn't blow up with a BasicObject as frame binding" do + obj = BasicObject.new + def obj.my_binding + ::Kernel.binding + end + frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index", obj.my_binding) + frame.class_name.should == 'BasicObject' + end + + it "sets method names properly" do + obj = "string" + def obj.my_method + begin + raise "foo" + rescue => err + err + end + end + + frame = StackFrame.from_exception(obj.my_method).first + if BetterErrors.binding_of_caller_available? + frame.method_name.should == "#my_method" + frame.class_name.should == "String" + else + frame.method_name.should == "my_method" + frame.class_name.should == nil + end + end + + if RUBY_ENGINE == "java" + it "doesn't blow up on a native Java exception" do + expect { StackFrame.from_exception(java.lang.Exception.new) }.to_not raise_error + end + end + end +end diff --git a/lib/better_errors/spec/better_errors/support/my_source.rb b/lib/better_errors/spec/better_errors/support/my_source.rb new file mode 100644 index 00000000..6dfea0f6 --- /dev/null +++ b/lib/better_errors/spec/better_errors/support/my_source.rb @@ -0,0 +1,20 @@ +one +two +three +four +five +six +seven +eight +nine +ten +eleven +twelve +thirteen +fourteen +fifteen +sixteen +seventeen +eighteen +nineteen +twenty diff --git a/lib/better_errors/spec/better_errors_spec.rb b/lib/better_errors/spec/better_errors_spec.rb new file mode 100644 index 00000000..e9b26b67 --- /dev/null +++ b/lib/better_errors/spec/better_errors_spec.rb @@ -0,0 +1,73 @@ +require "spec_helper" + +describe BetterErrors do + context ".editor" do + it "defaults to textmate" do + subject.editor["foo.rb", 123].should == "txmt://open?url=file://foo.rb&line=123" + end + + it "url escapes the filename" do + subject.editor["&.rb", 0].should == "txmt://open?url=file://%26.rb&line=0" + end + + [:emacs, :emacsclient].each do |editor| + it "uses emacs:// scheme when set to #{editor.inspect}" do + subject.editor = editor + subject.editor[].should start_with "emacs://" + end + end + + [:macvim, :mvim].each do |editor| + it "uses mvim:// scheme when set to #{editor.inspect}" do + subject.editor = editor + subject.editor[].should start_with "mvim://" + end + end + + [:sublime, :subl, :st].each do |editor| + it "uses subl:// scheme when set to #{editor.inspect}" do + subject.editor = editor + subject.editor[].should start_with "subl://" + end + end + + [:textmate, :txmt, :tm].each do |editor| + it "uses txmt:// scheme when set to #{editor.inspect}" do + subject.editor = editor + subject.editor[].should start_with "txmt://" + end + end + + ["emacsclient", "/usr/local/bin/emacsclient"].each do |editor| + it "uses emacs:// scheme when EDITOR=#{editor}" do + ENV["EDITOR"] = editor + subject.editor = subject.default_editor + subject.editor[].should start_with "emacs://" + end + end + + ["mvim -f", "/usr/local/bin/mvim -f"].each do |editor| + it "uses mvim:// scheme when EDITOR=#{editor}" do + ENV["EDITOR"] = editor + subject.editor = subject.default_editor + subject.editor[].should start_with "mvim://" + end + end + + ["subl -w", "/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl"].each do |editor| + it "uses mvim:// scheme when EDITOR=#{editor}" do + ENV["EDITOR"] = editor + subject.editor = subject.default_editor + subject.editor[].should start_with "subl://" + end + end + + ["mate -w", "/usr/bin/mate -w"].each do |editor| + it "uses txmt:// scheme when EDITOR=#{editor}" do + ENV["EDITOR"] = editor + subject.editor = subject.default_editor + subject.editor[].should start_with "txmt://" + end + end + end +end diff --git a/lib/better_errors/spec/spec_helper.rb b/lib/better_errors/spec/spec_helper.rb new file mode 100644 index 00000000..40d63261 --- /dev/null +++ b/lib/better_errors/spec/spec_helper.rb @@ -0,0 +1,5 @@ +$: << File.expand_path("../../lib", __FILE__) + +ENV["EDITOR"] = nil + +require "better_errors" diff --git a/lib/better_errors/spec/without_binding_of_caller.rb b/lib/better_errors/spec/without_binding_of_caller.rb new file mode 100644 index 00000000..035e2e90 --- /dev/null +++ b/lib/better_errors/spec/without_binding_of_caller.rb @@ -0,0 +1,9 @@ +module Kernel + alias_method :require_with_binding_of_caller, :require + + def require(feature) + raise LoadError if feature == "binding_of_caller" + + require_with_binding_of_caller(feature) + end +end diff --git a/lib/rack-mini-profiler/.travis.yml b/lib/rack-mini-profiler/.travis.yml new file mode 100644 index 00000000..61ab0d68 --- /dev/null +++ b/lib/rack-mini-profiler/.travis.yml @@ -0,0 +1,9 @@ +language: ruby +rvm: + - 1.9.3 + - 2.0.0 + - 2.1.1 +bundler_args: "" +services: + - redis + - memcached diff --git a/lib/rack-mini-profiler/CHANGELOG b/lib/rack-mini-profiler/CHANGELOG new file mode 100644 index 00000000..7c08a89e --- /dev/null +++ b/lib/rack-mini-profiler/CHANGELOG @@ -0,0 +1,181 @@ +28-June-2012 - Sam + + * Started change log + * Corrected profiler so it properly captures POST requests (was supressing non 200s) + * Amended Rack.MiniProfiler.config[:user_provider] to use ip addres for identity + * Fixed bug where unviewed missing ids never got cleared + * Supress all '/assets/' in the rails tie (makes debugging easier) + * record_sql was mega buggy + * added MemcacheStore + +9-July-2012 - Sam + + * Cleaned up mechanism for profiling in production, all you need to do now + is call Rack::MiniProfiler.authorize_request to get profiling working in + production + * Added option to display full backtraces pp=full-backtrace + * Cleaned up railties, got rid of the post authorize callback + * Version 0.1.3 + +12-July-2012 - Sam + + * Fixed incorrect profiling steps (was not indenting or measuring start time right + * Implemented native PG and MySql2 interceptors, this gives way more accurate times + * Refactored context so its a proper class and not a hash + * Added some more client probing built in to rails + * More tests + +18-July-2012 - Sam + + * Added First Paint time for chrome + * Bug fix to ensure non Rails installs have mini profiler + * Version 0.1.7 + +30-July-2012 - Sam + + * Made compliant with ancient versions of Rack (including Rack used by Rails2) + * Fixed broken share link + * Fixed crashes on startup (in MemoryStore and FileStore) + * Version 0.1.8 + * Unicode fix + * Version 0.1.9 + +7-August-2012 - Sam + + * Added option to disable profiler for the current session (pp=disable / pp=enable) + * yajl compatability contributed by Sven Riedel + +10-August-2012 - Sam + + * Added basic prepared statement profiling for postgres + +20-August-2012 - Sam + + * 1.12.pre + * Cap X-MiniProfiler-Ids at 10, otherwise the header can get killed + +3-September-2012 - Sam + + * 1.13.pre + * pg gem prepared statements were not being logged correctly + * added setting config.backtrace_ignores = [] - an array of regexes that match on caller lines that get ignored + * added setting config.backtrace_includes = [] - an array of regexes that get included in the trace by default + * cleaned up the way client settings are stored + * made pp=full-backtrace "sticky" + * added pp=normal-backtrace to clear the "sticky" state + * change "pp=sample" to work with "caller" no need for stack trace gem + +4-September-2012 - Sam + + * 1.15.pre + * fixed annoying bug where client settings were not sticking + * fixed long standing issue with Rack::ConditionalGet stopping MiniProfiler from working properly + +5-September-2012 - Sam + + * 1.16 + * fixed long standing problem specs (issue with memory store) + * fixed issue where profiler would be dumped when you got a 404 in production (and any time rails is bypassed) + * implemented stacktrace properly + +9-September-2012 - Sam + + * 1.17 + * pp=sample was bust unless stacktrace was installed + +10-September-2012 - Sam + + * 1.19 + * fix compat issue with 1.8.7 + +12-September-2012 - Sam + + * 1.20 + * Added pp=profile-gc , it allows you to profile the GC in Ruby 1.9.3 + +17-September-2012 + * 1.21 + * New MemchacedStore + * Rails 4 support + +17-September-2012 + * Allow rack-mini-profiler to be sourced from github + * Extracted the pp=profile-gc-time out, the object space profiler needs to disable gc + +20-September-2012 + * 1.22 + * Fix permission issue in the gem + +8-April-2013 + * 1.24 + * Flame Graph Support see: http://samsaffron.com/archive/2013/03/19/flame-graphs-in-ruby-miniprofiler + * Fix file retention leak in file_store + * New toggle_shortcut and start_hidden options + * Fix for AngularJS support and MooTools + * More robust gc profiling + * Mongoid support + * Fix for html5 implicit body tags + * script tag initialized via data-attributes + * new - Rack::MiniProfiler.counter counter_name {} + * Allow usage of existing jQuery if its already loaded + * Fix pp=enable + * 1.8.7 support ... grrr + * Net:HTTP profiling + * pre authorize to run in all non development? and production? modes + +8-April-2013 + * 1.25 + * Missed flamegraph.html from build + +11-April-2013 + * 1.26 + * (minor) allow Rack::MiniProfilerRails.initialize!(Rails.application), for post config intialization + +26-June-2013 + * 1.27 + * Disable global ajax handlers on MP requests @JP + * Add Rack::MiniProfiler.config.backtrace_threshold_ms + * jQuery 2.0 support + +18-July-2013 + * 1.28 + * diagnostics in abstract storage was raising not implemented killing + ?pp=env and others + * SOLR xml unescaped by mistake + +20-August-2013 + * 1.29 + * Bugfix: SOLR patching had an incorrect monkey patch + * Implemented exception tracing using TracePoint see pp=trace-exceptions + +30-August-2013 + + * 1.30 + * Feature: Added Rack::MiniProfiler.counter_method(klass,name) for injecting counters + * Bug: Counters were not shifting the table correctly + +3-September-2013 + + * Ripped out flamegraph so it can be isolated into a gem + * Flamegraph now has much increased fidelity + * Ripped out pp=sample it just was never really used + +17-September-2013 - Ross Wilson + * Instead of supressing all "/assets/" requests we now check the configured + config.assets.prefix path since developers can rename the path to serve Asset Pipeline + files from + +12-December-2013 - Sam Saffron + * Version 0.9.0.pre (bumped up to reflect the stability of the project) + * Improved reports for pp=profile-gc + * pp=flamegraph&flamegraph_sample_rate=1 , allow you to specify sampling rates + +13-March-2014 - Sam Saffron + * Version 0.9.1 + * Added back Ruby 1.8 support (thanks Malet) + * Corrected Rails 3.0 support (thanks Zlatko) + * Corrected fix possible XSS (admin only) + * Amend Railstie so MiniProfiler can be launched with action view or action controller (Thanks Akira) + * Corrected Sql patching to avoid setting instance vars on nil which is frozen (thanks Andy, huoxito) + + diff --git a/lib/rack-mini-profiler/Gemfile b/lib/rack-mini-profiler/Gemfile new file mode 100644 index 00000000..d65e2a66 --- /dev/null +++ b/lib/rack-mini-profiler/Gemfile @@ -0,0 +1,3 @@ +source 'http://rubygems.org' + +gemspec diff --git a/lib/rack-mini-profiler/README.md b/lib/rack-mini-profiler/README.md new file mode 100644 index 00000000..550dc1e0 --- /dev/null +++ b/lib/rack-mini-profiler/README.md @@ -0,0 +1,271 @@ +# rack-mini-profiler + +[![Code Climate](https://codeclimate.com/github/MiniProfiler/rack-mini-profiler.png)](https://codeclimate.com/github/MiniProfiler/rack-mini-profiler) [![Build Status](https://travis-ci.org/MiniProfiler/rack-mini-profiler.png)](https://travis-ci.org/MiniProfiler/rack-mini-profiler) + +Middleware that displays speed badge for every html page. Designed to work both in production and in development. + +#### Features + +* database profiling. Currently supports Mysql2, Postgres, and Mongoid3 (with fallback support to ActiveRecord) + +#### Learn more + +* [Visit our community](http://community.miniprofiler.com) +* [Watch the RailsCast](http://railscasts.com/episodes/368-miniprofiler) +* [Read about Flame graphs in rack-mini-profiler](http://samsaffron.com/archive/2013/03/19/flame-graphs-in-ruby-miniprofiler) +* [Read the announcement posts from 2012](http://samsaffron.com/archive/2012/07/12/miniprofiler-ruby-edition) + +## rack-mini-profiler needs your help + +We have decided to restructure our repository so there is a central UI repo and the various language implementation have their own. + +**WE NEED HELP.** + +- Setting up a build that reuses https://github.com/MiniProfiler/ui +- Migrating the internal data structures [per the spec](https://github.com/MiniProfiler/ui) +- Cleaning up the [horrendous class structure that is using strings as keys and crazy non-objects](https://github.com/MiniProfiler/rack-mini-profiler/blob/master/lib/mini_profiler/sql_timer_struct.rb#L36-L44) + +If you feel like taking on any of this start an issue and update us on your progress. + +## Installation + +Install/add to Gemfile + +```ruby +gem 'rack-mini-profiler' +``` + +NOTE: Be sure to require rack_mini_profiler below the `pg` and `mysql` gems in your Gemfile. rack_mini_profiler will identify these gems if they are loaded to insert instrumentation. If included too early no SQL will show up. + +#### Rails + +All you have to do is include the Gem and you're good to go in development. See notes below for use in production. + +#### Rails and manual initialization + +In case you need to make sure rack_mini_profiler initialized after all other gems. +Or you want to execute some code before rack_mini_profiler required. + +```ruby +gem 'rack-mini-profiler', require: false +``` +Note the `require: false` part - if omitted, it will cause the Railtie for the mino-profiler to +be loaded outright, and an attempt to re-initialize it manually will raise an exception. + +Then put initialize code in file like `config/initializers/rack_profiler.rb` + +```ruby +if Rails.env == 'development' + require 'rack-mini-profiler' + + # initialization is skipped so trigger it + Rack::MiniProfilerRails.initialize!(Rails.application) +end +``` + +#### Rack Builder + +```ruby +require 'rack-mini-profiler' +builder = Rack::Builder.new do + use Rack::MiniProfiler + + map('/') { run get } +end +``` + +#### Sinatra + +```ruby +require 'rack-mini-profiler' +class MyApp < Sinatra::Base + use Rack::MiniProfiler +end +``` + +### Flamegraphs + +To generate [flamegraphs](http://samsaffron.com/archive/2013/03/19/flame-graphs-in-ruby-miniprofiler): + +* add the **flamegraph** gem to your Gemfile +* visit a page in your app with `?pp=flamegraph` + +Flamegraph generation is supported in MRI 2.0 and 2.1 only. + + +## Access control in production + +rack-mini-profiler is designed with production profiling in mind. To enable that just run `Rack::MiniProfiler.authorize_request` once you know a request is allowed to profile. + +```ruby +# A hook in your ApplicationController +def authorize + if current_user.is_admin? + Rack::MiniProfiler.authorize_request + end +end +``` + +## Configuration + +Various aspects of rack-mini-profiler's behavior can be configured when your app boots. +For example in a Rails app, this should be done in an initializer: +**config/initializers/mini_profiler.rb** + +### Storage + +rack-mini-profiler stores its results so they can be shared later and aren't lost at the end of the request. + +There are 4 storage options: `MemoryStore`, `RedisStore`, `MemcacheStore`, and `FileStore`. + +`FileStore` is the default in Rails environments and will write files to `tmp/miniprofiler/*`. `MemoryStore` is the default otherwise. + +```ruby +# set MemoryStore +Rack::MiniProfiler.config.storage = Rack::MiniProfiler::MemoryStore + +# set RedisStore +if Rails.env.production? + uri = URI.parse(ENV["REDIS_SERVER_URL"]) + Rack::MiniProfiler.config.storage_options = { :host => uri.host, :port => uri.port, :password => uri.password } + Rack::MiniProfiler.config.storage = Rack::MiniProfiler::RedisStore +end +``` + +MemoryStore stores results in a processes heap - something that does not work well in a multi process environment. +FileStore stores results in the file system - something that may not work well in a multi machine environment. +RedisStore/MemcacheStore work in multi process and multi machine environments (RedisStore only saves results for up to 24 hours so it won't continue to fill up Redis). + +Additionally you may implement an AbstractStore for your own provider. + +### User result segregation + +MiniProfiler will attempt to keep all user results isolated, out-of-the-box the user provider uses the ip address: + +```ruby +Rack::MiniProfiler.config.user_provider = Proc.new{|env| Rack::Request.new(env).ip} +``` + +You can override (something that is very important in a multi-machine production setup): + +```ruby +Rack::MiniProfiler.config.user_provider = Proc.new{ |env| CurrentUser.get(env) } +``` + +The string this function returns should be unique for each user on the system (for anonymous you may need to fall back to ip address) + +### Configuration Options + +You can set configuration options using the configuration accessor on `Rack::MiniProfiler`. +For example: + +```ruby +Rack::MiniProfiler.config.position = 'right' +Rack::MiniProfiler.config.start_hidden = true +``` +The available configuration options are: + +* pre_authorize_cb - A lambda callback you can set to determine whether or not mini_profiler should be visible on a given request. Default in a Rails environment is only on in development mode. If in a Rack app, the default is always on. +* position - Can either be 'right' or 'left'. Default is 'left'. +* skip_schema_queries - Whether or not you want to log the queries about the schema of your tables. Default is 'false', 'true' in rails development. +* auto_inject (default true) - when false the miniprofiler script is not injected in the page +* backtrace_filter - a regex you can use to filter out unwanted lines from the backtraces +* toggle_shortcut (default Alt+P) - a jquery.hotkeys.js-style keyboard shortcut, used to toggle the mini_profiler's visibility. See http://code.google.com/p/js-hotkeys/ for more info. +* start_hidden (default false) - Whether or not you want the mini_profiler to be visible when loading a page +* backtrace_threshold_ms (default zero) - Minimum SQL query elapsed time before a backtrace is recorded. Backtrace recording can take a couple of milliseconds on rubies earlier than 2.0, impacting performance for very small queries. +* flamegraph_sample_rate (default 0.5ms) - How often fast_stack should get stack trace info to generate flamegraphs + +### Custom middleware ordering (required if using `Rack::Deflate` with Rails) + +If you are using `Rack::Deflate` with rails and rack-mini-profiler in its default configuration, +`Rack::MiniProfiler` will be injected (as always) at position 0 in the middleware stack. This +will result in it attempting to inject html into the already-compressed response body. To fix this, +the middleware ordering must be overriden. + +To do this, first add `, require: false` to the gemfile entry for rack-mini-profiler. +This will prevent the railtie from running. Then, customize the initialization +in the initializer like so: + +```ruby +require 'rack-mini-profiler' + +Rack::MiniProfilerRails.initialize!(Rails.application) + +Rails.application.middleware.delete(Rack::MiniProfiler) +Rails.application.middleware.insert_after(Rack::Deflater, Rack::MiniProfiler) +``` + +Deleting the middleware and then reinserting it is a bit inelegant, but +a sufficient and costless solution. It is possible that rack-mini-profiler might +support this scenario more directly if it is found that +there is significant need for this confriguration or that +the above recipe causes problems. + + +## Special query strings + +If you include the query string `pp=help` at the end of your request you will see the various options available. You can use these options to extend or contract the amount of diagnostics rack-mini-profiler gathers. + + +## Rails 2.X support + +To get MiniProfiler working with Rails 2.3.X you need to do the initialization manually as well as monkey patch away an incompatibility between activesupport and json_pure. + +Add the following code to your environment.rb (or just in a specific environment such as development.rb) for initialization and configuration of MiniProfiler. + +```ruby +# configure and initialize MiniProfiler +require 'rack-mini-profiler' +c = ::Rack::MiniProfiler.config +c.pre_authorize_cb = lambda { |env| + Rails.env.development? || Rails.env.production? +} +tmp = Rails.root.to_s + "/tmp/miniprofiler" +FileUtils.mkdir_p(tmp) unless File.exists?(tmp) +c.storage_options = {:path => tmp} +c.storage = ::Rack::MiniProfiler::FileStore +config.middleware.use(::Rack::MiniProfiler) +::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"} +::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{path_without_format_and_extension}"} + +# monkey patch away an activesupport and json_pure incompatability +# http://pivotallabs.com/users/alex/blog/articles/1332-monkey-patch-of-the-day-activesupport-vs-json-pure-vs-ruby-1-8 +if JSON.const_defined?(:Pure) + class JSON::Pure::Generator::State + include ActiveSupport::CoreExtensions::Hash::Except + end +end +``` + +## Running the Specs + +``` +$ rake build +$ rake spec +``` + +Additionally you can also run `autotest` if you like. + +## Licence + +The MIT License (MIT) + +Copyright (c) 2013 Sam Saffron + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/rack-mini-profiler/Rakefile b/lib/rack-mini-profiler/Rakefile new file mode 100644 index 00000000..a908b5b6 --- /dev/null +++ b/lib/rack-mini-profiler/Rakefile @@ -0,0 +1,46 @@ +# Rakefile +require 'rubygems' +require 'bundler' +Bundler.setup(:default, :test) + +task :default => [:spec] + +require 'rspec/core' +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) do |spec| + spec.pattern = FileList['spec/**/*_spec.rb'] +end + +desc "builds a gem" +task :build => :update_asset_version do + `gem build rack-mini-profiler.gemspec 1>&2` +end + +desc "compile less" +task :compile_less => :copy_files do + `lessc lib/html/includes.less > lib/html/includes.css` +end + +desc "update asset version file" +task :update_asset_version => :compile_less do + require 'digest/md5' + h = [] + Dir.glob('lib/html/*.{js,html,css,tmpl}').each do |f| + h << Digest::MD5.hexdigest(::File.read(f)) + end + File.open('lib/mini_profiler/version.rb','w') do |f| + f.write \ +"module Rack + class MiniProfiler + VERSION = '#{Digest::MD5.hexdigest(h.sort.join(''))}'.freeze + end +end" + end +end + + +desc "copy files from other parts of the tree" +task :copy_files do + # TODO grab files from MiniProfiler/UI +end + diff --git a/lib/rack-mini-profiler/autotest/discover.rb b/lib/rack-mini-profiler/autotest/discover.rb new file mode 100644 index 00000000..de32a012 --- /dev/null +++ b/lib/rack-mini-profiler/autotest/discover.rb @@ -0,0 +1,2 @@ +Autotest.add_discovery { "rspec2" } + diff --git a/lib/rack-mini-profiler/lib/html/includes.css b/lib/rack-mini-profiler/lib/html/includes.css new file mode 100644 index 00000000..d96528b8 --- /dev/null +++ b/lib/rack-mini-profiler/lib/html/includes.css @@ -0,0 +1,451 @@ +.profiler-result, +.profiler-queries { + color: #555; + line-height: 1; + font-size: 12px; +} +.profiler-result pre, +.profiler-queries pre, +.profiler-result code, +.profiler-queries code, +.profiler-result label, +.profiler-queries label, +.profiler-result table, +.profiler-queries table, +.profiler-result tbody, +.profiler-queries tbody, +.profiler-result thead, +.profiler-queries thead, +.profiler-result tfoot, +.profiler-queries tfoot, +.profiler-result tr, +.profiler-queries tr, +.profiler-result th, +.profiler-queries th, +.profiler-result td, +.profiler-queries td { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; + background-color: transparent; + overflow: visible; + max-height: none; +} +.profiler-result table, +.profiler-queries table { + border-collapse: collapse; + border-spacing: 0; +} +.profiler-result a, +.profiler-queries a, +.profiler-result a:hover, +.profiler-queries a:hover { + cursor: pointer; + color: #0077cc; +} +.profiler-result a, +.profiler-queries a { + text-decoration: none; +} +.profiler-result a:hover, +.profiler-queries a:hover { + text-decoration: underline; +} +.profiler-result { + font-family: Helvetica, Arial, sans-serif; +} +.profiler-result .profiler-toggle-duration-with-children { + float: right; +} +.profiler-result table.profiler-client-timings { + margin-top: 10px; +} +.profiler-result .profiler-label { + color: #555555; + overflow: hidden; + text-overflow: ellipsis; +} +.profiler-result .profiler-unit { + color: #aaaaaa; +} +.profiler-result .profiler-trivial { + display: none; +} +.profiler-result .profiler-trivial td, +.profiler-result .profiler-trivial td * { + color: #aaaaaa !important; +} +.profiler-result pre, +.profiler-result code, +.profiler-result .profiler-number, +.profiler-result .profiler-unit { + font-family: Consolas, monospace, serif; +} +.profiler-result .profiler-number { + color: #111111; +} +.profiler-result .profiler-info { + text-align: right; +} +.profiler-result .profiler-info .profiler-name { + float: left; +} +.profiler-result .profiler-info .profiler-server-time { + white-space: nowrap; +} +.profiler-result .profiler-timings th { + background-color: #fff; + color: #aaaaaa; + text-align: right; +} +.profiler-result .profiler-timings th, +.profiler-result .profiler-timings td { + white-space: nowrap; +} +.profiler-result .profiler-timings .profiler-duration-with-children { + display: none; +} +.profiler-result .profiler-timings .profiler-duration { + font-family: Consolas, monospace, serif; + color: #111111; + text-align: right; +} +.profiler-result .profiler-timings .profiler-indent { + letter-spacing: 4px; +} +.profiler-result .profiler-timings .profiler-queries-show .profiler-number, +.profiler-result .profiler-timings .profiler-queries-show .profiler-unit { + color: #0077cc; +} +.profiler-result .profiler-timings .profiler-queries-duration { + padding-left: 6px; +} +.profiler-result .profiler-timings .profiler-percent-in-sql { + white-space: nowrap; + text-align: right; +} +.profiler-result .profiler-timings tfoot td { + padding-top: 10px; + text-align: right; +} +.profiler-result .profiler-timings tfoot td a { + font-size: 95%; + display: inline-block; + margin-left: 12px; +} +.profiler-result .profiler-timings tfoot td a:first-child { + float: left; + margin-left: 0px; +} +.profiler-result .profiler-timings tfoot td a.profiler-custom-link { + float: left; +} +.profiler-result .profiler-queries { + font-family: Helvetica, Arial, sans-serif; +} +.profiler-result .profiler-queries .profiler-stack-trace { + margin-bottom: 15px; +} +.profiler-result .profiler-queries pre { + font-family: Consolas, monospace, serif; + white-space: pre-wrap; +} +.profiler-result .profiler-queries th { + background-color: #fff; + border-bottom: 1px solid #555; + font-weight: bold; + padding: 15px; + white-space: nowrap; +} +.profiler-result .profiler-queries td { + padding: 15px; + text-align: left; + background-color: #fff; +} +.profiler-result .profiler-queries td:last-child { + padding-right: 25px; +} +.profiler-result .profiler-queries .profiler-odd td { + background-color: #e5e5e5; +} +.profiler-result .profiler-queries .profiler-since-start, +.profiler-result .profiler-queries .profiler-duration { + text-align: right; +} +.profiler-result .profiler-queries .profiler-info div { + text-align: right; + margin-bottom: 5px; +} +.profiler-result .profiler-queries .profiler-gap-info, +.profiler-result .profiler-queries .profiler-gap-info td { + background-color: #ccc; +} +.profiler-result .profiler-queries .profiler-gap-info .profiler-unit { + color: #777; +} +.profiler-result .profiler-queries .profiler-gap-info .profiler-info { + text-align: right; +} +.profiler-result .profiler-queries .profiler-gap-info.profiler-trivial-gaps { + display: none; +} +.profiler-result .profiler-queries .profiler-trivial-gap-container { + text-align: center; +} +.profiler-result .profiler-queries .str { + color: #800000; +} +.profiler-result .profiler-queries .kwd { + color: #00008b; +} +.profiler-result .profiler-queries .com { + color: #808080; +} +.profiler-result .profiler-queries .typ { + color: #2b91af; +} +.profiler-result .profiler-queries .lit { + color: #800000; +} +.profiler-result .profiler-queries .pun { + color: #000000; +} +.profiler-result .profiler-queries .pln { + color: #000000; +} +.profiler-result .profiler-queries .tag { + color: #800000; +} +.profiler-result .profiler-queries .atn { + color: #ff0000; +} +.profiler-result .profiler-queries .atv { + color: #0000ff; +} +.profiler-result .profiler-queries .dec { + color: #800080; +} +.profiler-result .profiler-warning, +.profiler-result .profiler-warning *, +.profiler-result .profiler-warning .profiler-queries-show, +.profiler-result .profiler-warning .profiler-queries-show .profiler-unit { + color: #f00; +} +.profiler-result .profiler-warning:hover, +.profiler-result .profiler-warning *:hover, +.profiler-result .profiler-warning .profiler-queries-show:hover, +.profiler-result .profiler-warning .profiler-queries-show .profiler-unit:hover { + color: #f00; +} +.profiler-result .profiler-nuclear { + color: #f00; + font-weight: bold; + padding-right: 2px; +} +.profiler-result .profiler-nuclear:hover { + color: #f00; +} +.profiler-results { + z-index: 2147483643; + position: fixed; + top: 0px; +} +.profiler-results.profiler-left { + left: 0px; +} +.profiler-results.profiler-left.profiler-no-controls .profiler-result:last-child .profiler-button, +.profiler-results.profiler-left .profiler-controls { + -webkit-border-bottom-right-radius: 10px; + -moz-border-radius-bottomright: 10px; + border-bottom-right-radius: 10px; +} +.profiler-results.profiler-left .profiler-button, +.profiler-results.profiler-left .profiler-controls { + border-right: 1px solid #888888; +} +.profiler-results.profiler-right { + right: 0px; +} +.profiler-results.profiler-right.profiler-no-controls .profiler-result:last-child .profiler-button, +.profiler-results.profiler-right .profiler-controls { + -webkit-border-bottom-left-radius: 10px; + -moz-border-radius-bottomleft: 10px; + border-bottom-left-radius: 10px; +} +.profiler-results.profiler-right .profiler-button, +.profiler-results.profiler-right .profiler-controls { + border-left: 1px solid #888888; +} +.profiler-results .profiler-button, +.profiler-results .profiler-controls { + display: none; + z-index: 2147483640; + border-bottom: 1px solid #888888; + background-color: #fff; + padding: 4px 7px; + text-align: right; + cursor: pointer; +} +.profiler-results .profiler-button.profiler-button-active, +.profiler-results .profiler-controls.profiler-button-active { + background-color: maroon; +} +.profiler-results .profiler-button.profiler-button-active .profiler-number, +.profiler-results .profiler-controls.profiler-button-active .profiler-number, +.profiler-results .profiler-button.profiler-button-active .profiler-nuclear, +.profiler-results .profiler-controls.profiler-button-active .profiler-nuclear { + color: #fff; + font-weight: bold; +} +.profiler-results .profiler-button.profiler-button-active .profiler-unit, +.profiler-results .profiler-controls.profiler-button-active .profiler-unit { + color: #fff; + font-weight: normal; +} +.profiler-results .profiler-controls { + display: block; + font-size: 12px; + font-family: Consolas, monospace, serif; + cursor: default; + text-align: center; +} +.profiler-results .profiler-controls span { + border-right: 1px solid #aaaaaa; + padding-right: 5px; + margin-right: 5px; + cursor: pointer; +} +.profiler-results .profiler-controls span:last-child { + border-right: none; +} +.profiler-results .profiler-popup { + display: none; + z-index: 2147483641; + position: absolute; + background-color: #fff; + border: 1px solid #aaa; + padding: 5px 10px; + text-align: left; + line-height: 18px; + overflow: auto; + -moz-box-shadow: 0px 1px 15px #555555; + -webkit-box-shadow: 0px 1px 15px #555555; + box-shadow: 0px 1px 15px #555555; +} +.profiler-results .profiler-popup .profiler-info { + margin-bottom: 3px; + padding-bottom: 2px; + border-bottom: 1px solid #ddd; +} +.profiler-results .profiler-popup .profiler-info .profiler-name { + font-size: 110%; + font-weight: bold; +} +.profiler-results .profiler-popup .profiler-info .profiler-name .profiler-overall-duration { + display: none; +} +.profiler-results .profiler-popup .profiler-info .profiler-server-time { + font-size: 95%; +} +.profiler-results .profiler-popup .profiler-timings th, +.profiler-results .profiler-popup .profiler-timings td { + padding-left: 6px; + padding-right: 6px; +} +.profiler-results .profiler-popup .profiler-timings th { + font-size: 95%; + padding-bottom: 3px; +} +.profiler-results .profiler-popup .profiler-timings .profiler-label { + max-width: 275px; +} +.profiler-results .profiler-queries { + display: none; + z-index: 2147483643; + position: absolute; + overflow-y: auto; + overflow-x: auto; + background-color: #fff; +} +.profiler-results .profiler-queries th { + font-size: 17px; +} +.profiler-results.profiler-min .profiler-result { + display: none; +} +.profiler-results.profiler-min .profiler-controls span { + display: none; +} +.profiler-results.profiler-min .profiler-controls .profiler-min-max { + border-right: none; + padding: 0px; + margin: 0px; +} +.profiler-queries-bg { + z-index: 2147483642; + display: none; + background: #000; + opacity: 0.7; + position: absolute; + top: 0px; + left: 0px; + min-width: 100%; +} +.profiler-result-full .profiler-result { + width: 950px; + margin: 30px auto; +} +.profiler-result-full .profiler-result .profiler-button { + display: none; +} +.profiler-result-full .profiler-result .profiler-popup .profiler-info { + font-size: 25px; + border-bottom: 1px solid #aaaaaa; + padding-bottom: 3px; + margin-bottom: 25px; +} +.profiler-result-full .profiler-result .profiler-popup .profiler-info .profiler-overall-duration { + padding-right: 20px; + font-size: 80%; + color: #888; +} +.profiler-result-full .profiler-result .profiler-popup .profiler-timings td, +.profiler-result-full .profiler-result .profiler-popup .profiler-timings th { + padding-left: 8px; + padding-right: 8px; +} +.profiler-result-full .profiler-result .profiler-popup .profiler-timings th { + padding-bottom: 7px; +} +.profiler-result-full .profiler-result .profiler-popup .profiler-timings td { + font-size: 14px; + padding-bottom: 4px; +} +.profiler-result-full .profiler-result .profiler-popup .profiler-timings td:first-child { + padding-left: 10px; +} +.profiler-result-full .profiler-result .profiler-popup .profiler-timings .profiler-label { + max-width: 550px; +} +.profiler-result-full .profiler-result .profiler-queries { + margin: 25px 0; +} +.profiler-result-full .profiler-result .profiler-queries table { + width: 100%; +} +.profiler-result-full .profiler-result .profiler-queries th { + font-size: 16px; + color: #555; + line-height: 20px; +} +.profiler-result-full .profiler-result .profiler-queries td { + padding: 15px 10px; + text-align: left; +} +.profiler-result-full .profiler-result .profiler-queries .profiler-info div { + text-align: right; + margin-bottom: 5px; +} diff --git a/lib/rack-mini-profiler/lib/html/includes.js b/lib/rack-mini-profiler/lib/html/includes.js new file mode 100644 index 00000000..d8b4de08 --- /dev/null +++ b/lib/rack-mini-profiler/lib/html/includes.js @@ -0,0 +1,960 @@ +"use strict"; +var MiniProfiler = (function () { + var $; + + var options, + container, + controls, + fetchedIds = [], + fetchingIds = [], // so we never pull down a profiler twice + ajaxStartTime + ; + + var hasLocalStorage = function () { + try { + return 'localStorage' in window && window['localStorage'] !== null; + } catch (e) { + return false; + } + }; + + var getVersionedKey = function (keyPrefix) { + return keyPrefix + '-' + options.version; + }; + + var save = function (keyPrefix, value) { + if (!hasLocalStorage()) { return; } + + // clear old keys with this prefix, if any + for (var i = 0; i < localStorage.length; i++) { + if ((localStorage.key(i) || '').indexOf(keyPrefix) > -1) { + localStorage.removeItem(localStorage.key(i)); + } + } + + // save under this version + localStorage[getVersionedKey(keyPrefix)] = value; + }; + + var load = function (keyPrefix) { + if (!hasLocalStorage()) { return null; } + + return localStorage[getVersionedKey(keyPrefix)]; + }; + + var fetchTemplates = function (success) { + var key = 'templates', + cached = load(key); + + if (cached) { + $('body').append(cached); + success(); + } + else { + $.get(options.path + 'includes.tmpl?v=' + options.version, function (data) { + if (data) { + save(key, data); + $('body').append(data); + success(); + } + }); + } + }; + + var getClientPerformance = function() { + return window.performance == null ? null : window.performance; + }; + + var fetchResults = function (ids) { + var clientPerformance, clientProbes, i, j, p, id, idx; + + for (i = 0; i < ids.length; i++) { + id = ids[i]; + + clientPerformance = null; + clientProbes = null; + + if (window.mPt) { + clientProbes = mPt.results(); + for (j = 0; j < clientProbes.length; j++) { + clientProbes[j].d = clientProbes[j].d.getTime(); + } + mPt.flush(); + } + + if (id == options.currentId) { + + clientPerformance = getClientPerformance(); + + if (clientPerformance != null) { + // ie is buggy strip out functions + var copy = { navigation: {}, timing: {} }; + + var timing = $.extend({}, clientPerformance.timing); + + for (p in timing) { + if (timing.hasOwnProperty(p) && !$.isFunction(timing[p])) { + copy.timing[p] = timing[p]; + } + } + if (clientPerformance.navigation) { + copy.navigation.redirectCount = clientPerformance.navigation.redirectCount; + } + clientPerformance = copy; + + // hack to add chrome timings + if (window.chrome && window.chrome.loadTimes) { + var chromeTimes = window.chrome.loadTimes(); + if (chromeTimes.firstPaintTime) { + clientPerformance.timing["First Paint Time"] = Math.round(chromeTimes.firstPaintTime * 1000); + } + if (chromeTimes.firstPaintTime) { + clientPerformance.timing["First Paint After Load Time"] = Math.round(chromeTimes.firstPaintAfterLoadTime * 1000); + } + + } + } + } else if (ajaxStartTime != null && clientProbes && clientProbes.length > 0) { + clientPerformance = { timing: { navigationStart: ajaxStartTime.getTime() } }; + ajaxStartTime = null; + } + + if ($.inArray(id, fetchedIds) < 0 && $.inArray(id, fetchingIds) < 0) { + idx = fetchingIds.push(id) - 1; + + $.ajax({ + url: options.path + 'results', + data: { id: id, clientPerformance: clientPerformance, clientProbes: clientProbes, popup: 1 }, + dataType: 'json', + global: false, + type: 'POST', + success: function (json) { + fetchedIds.push(id); + if (json != "hidden") { + buttonShow(json); + } + }, + complete: function () { + fetchingIds.splice(idx, 1); + } + }); + } + } + }; + + var renderTemplate = function (json) { + return $('#profilerTemplate').tmpl(json); + }; + + var buttonShow = function (json) { + var result = renderTemplate(json); + + if (controls) + result.insertBefore(controls); + else + result.appendTo(container); + + var button = result.find('.profiler-button'), + popup = result.find('.profiler-popup'); + + // button will appear in corner with the total profiling duration - click to show details + button.click(function () { buttonClick(button, popup); }); + + // small duration steps and the column with aggregate durations are hidden by default; allow toggling + toggleHidden(popup); + + // lightbox in the queries + popup.find('.profiler-queries-show').click(function () { queriesShow($(this), result); }); + + // limit count + if (container.find('.profiler-result').length > options.maxTracesToShow) + container.find('.profiler-result').first().remove(); + button.show(); + }; + + var toggleHidden = function (popup) { + var trivial = popup.find('.profiler-toggle-trivial'); + var childrenTime = popup.find('.profiler-toggle-duration-with-children'); + var trivialGaps = popup.parent().find('.profiler-toggle-trivial-gaps'); + + var toggleIt = function (node) { + var link = $(node), + klass = "profiler-" + link.attr('class').substr('profiler-toggle-'.length), + isHidden = link.text().indexOf('show') > -1; + + popup.parent().find('.' + klass).toggle(isHidden); + link.text(link.text().replace(isHidden ? 'show' : 'hide', isHidden ? 'hide' : 'show')); + + popupPreventHorizontalScroll(popup); + }; + + childrenTime.add(trivial).add(trivialGaps).click(function () { + toggleIt(this); + }); + + // if option is set or all our timings are trivial, go ahead and show them + if (options.showTrivial || trivial.data('show-on-load')) { + toggleIt(trivial); + } + // if option is set, go ahead and show time with children + if (options.showChildrenTime) { + toggleIt(childrenTime); + } + }; + + var buttonClick = function (button, popup) { + // we're toggling this button/popup + if (popup.is(':visible')) { + popupHide(button, popup); + } + else { + var visiblePopups = container.find('.profiler-popup:visible'), + theirButtons = visiblePopups.siblings('.profiler-button'); + + // hide any other popups + popupHide(theirButtons, visiblePopups); + + // before showing the one we clicked + popupShow(button, popup); + } + }; + + var popupShow = function (button, popup) { + button.addClass('profiler-button-active'); + + popupSetDimensions(button, popup); + + popup.show(); + + popupPreventHorizontalScroll(popup); + }; + + var popupSetDimensions = function (button, popup) { + var top = button.position().top - 1, // position next to the button we clicked + windowHeight = $(window).height(), + maxHeight = windowHeight - top - 40; // make sure the popup doesn't extend below the fold + + popup + .css({ 'top': top, 'max-height': maxHeight }) + .css(options.renderPosition, button.outerWidth() - 3); // move left or right, based on config + }; + + var popupPreventHorizontalScroll = function (popup) { + var childrenHeight = 0; + + popup.children().each(function () { childrenHeight += $(this).height(); }); + + popup.css({ 'padding-right': childrenHeight > popup.height() ? 40 : 10 }); + }; + + var popupHide = function (button, popup) { + button.removeClass('profiler-button-active'); + popup.hide(); + }; + + var queriesShow = function (link, result) { + + var px = 30, + win = $(window), + width = win.width() - 2 * px, + height = win.height() - 2 * px, + queries = result.find('.profiler-queries'); + + // opaque background + $('
').appendTo('body').css({ 'height': $(document).height() }).show(); + + // center the queries and ensure long content is scrolled + queries.css({ 'top': px, 'max-height': height, 'width': width }).css(options.renderPosition, px) + .find('table').css({ 'width': width }); + + // have to show everything before we can get a position for the first query + queries.show(); + + queriesScrollIntoView(link, queries, queries); + + // syntax highlighting + prettyPrint(); + }; + + var queriesScrollIntoView = function (link, queries, whatToScroll) { + var id = link.closest('tr').attr('data-timing-id'), + cells = queries.find('tr[data-timing-id="' + id + '"] td'); + + // ensure they're in view + whatToScroll.scrollTop(whatToScroll.scrollTop() + cells.first().position().top - 100); + + // highlight and then fade back to original bg color; do it ourselves to prevent any conflicts w/ jquery.UI or other implementations of Resig's color plugin + cells.each(function () { + var cell = $(this), + highlightHex = '#FFFFBB', + highlightRgb = getRGB(highlightHex), + originalRgb = getRGB(cell.css('background-color')), + getColorDiff = function (fx, i) { + // adapted from John Resig's color plugin: http://plugins.jquery.com/project/color + return Math.max(Math.min(parseInt((fx.pos * (originalRgb[i] - highlightRgb[i])) + highlightRgb[i], 10), 255), 0); + }; + + // we need to animate some other property to piggy-back on the step function, so I choose you, opacity! + cell.css({ 'opacity': 1, 'background-color': highlightHex }) + .animate({ 'opacity': 1 }, { duration: 2000, step: function (now, fx) { + fx.elem.style.backgroundColor = "rgb(" + [getColorDiff(fx, 0), getColorDiff(fx, 1), getColorDiff(fx, 2)].join(",") + ")"; + } + }); + }); + }; + + // Color Conversion functions from highlightFade + // By Blair Mitchelmore + // http://jquery.offput.ca/highlightFade/ + // Parse strings looking for color tuples [255,255,255] + var getRGB = function (color) { + var result; + + // Check if we're already dealing with an array of colors + if (color && color.constructor == Array && color.length == 3) return color; + + // Look for rgb(num,num,num) + if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])]; + + // Look for rgb(num%,num%,num%) + if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)) return [parseFloat(result[1]) * 2.55, parseFloat(result[2]) * 2.55, parseFloat(result[3]) * 2.55]; + + // Look for #a0b1c2 + if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)) return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]; + + // Look for #fff + if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)) return [parseInt(result[1] + result[1], 16), parseInt(result[2] + result[2], 16), parseInt(result[3] + result[3], 16)]; + + // Look for rgba(0, 0, 0, 0) == transparent in Safari 3 + if (result = /rgba\(0, 0, 0, 0\)/.exec(color)) return colors['transparent']; + + return null; + }; + + var bindDocumentEvents = function () { + $(document).bind('click keyup', function (e) { + + // this happens on every keystroke, and :visible is crazy expensive in IE <9 + // and in this case, the display:none check is sufficient. + var popup = $('.profiler-popup').filter(function () { return $(this).css("display") !== "none"; }); + + if (!popup.length) { + return; + } + + var button = popup.siblings('.profiler-button'), + queries = popup.closest('.profiler-result').find('.profiler-queries'), + bg = $('.profiler-queries-bg'), + isEscPress = e.type == 'keyup' && e.which == 27, + hidePopup = false, + hideQueries = false; + + if (bg.is(':visible')) { + hideQueries = isEscPress || (e.type == 'click' && !$.contains(queries[0], e.target) && !$.contains(popup[0], e.target)); + } + else if (popup.is(':visible')) { + hidePopup = isEscPress || (e.type == 'click' && !$.contains(popup[0], e.target) && !$.contains(button[0], e.target) && button[0] != e.target); + } + + if (hideQueries) { + bg.remove(); + queries.hide(); + } + + if (hidePopup) { + popupHide(button, popup); + } + }); + $(document).bind('keydown', options.toggleShortcut, function(e) { + $('.profiler-results').toggle(); + }); + }; + + var initFullView = function () { + + // first, get jquery tmpl, then render and bind handlers + fetchTemplates(function () { + + // profiler will be defined in the full page's head + renderTemplate(profiler).appendTo(container); + + var popup = $('.profiler-popup'); + + toggleHidden(popup); + + prettyPrint(); + + // since queries are already shown, just highlight and scroll when clicking a "1 sql" link + popup.find('.profiler-queries-show').click(function () { + queriesScrollIntoView($(this), $('.profiler-queries'), $(document)); + }); + }); + }; + + var initControls = function (container) { + if (options.showControls) { + controls = $('
mc
').appendTo(container); + + $('.profiler-controls .profiler-min-max').click(function () { + container.toggleClass('profiler-min'); + }); + + container.hover(function () { + if ($(this).hasClass('profiler-min')) { + $(this).find('.profiler-min-max').show(); + } + }, + function () { + if ($(this).hasClass('profiler-min')) { + $(this).find('.profiler-min-max').hide(); + } + }); + + $('.profiler-controls .profiler-clear').click(function () { + container.find('.profiler-result').remove(); + }); + } + else { + container.addClass('profiler-no-controls'); + } + }; + + var initPopupView = function () { + + if (options.authorized) { + // all fetched profilings will go in here + container = $('
').appendTo('body'); + + // MiniProfiler.RenderIncludes() sets which corner to render in - default is upper left + container.addClass("profiler-" + options.renderPosition); + + //initialize the controls + initControls(container); + + // we'll render results json via a jquery.tmpl - after we get the templates, we'll fetch the initial json to populate it + fetchTemplates(function () { + // get master page profiler results + fetchResults(options.ids); + }); + if (options.startHidden) container.hide(); + } + else { + fetchResults(options.ids); + } + + var jQueryAjaxComplete = function (e, xhr, settings) { + if (xhr) { + // should be an array of strings, e.g. ["008c4813-9bd7-443d-9376-9441ec4d6a8c","16ff377b-8b9c-4c20-a7b5-97cd9fa7eea7"] + var stringIds = xhr.getResponseHeader('X-MiniProfiler-Ids'); + if (stringIds) { + var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds); + fetchResults(ids); + } + } + }; + + // fetch profile results for any ajax calls + // note, this does not use $ cause we want to hook into the main jQuery + if (jQuery && jQuery(document) && jQuery(document).ajaxComplete) { + jQuery(document).ajaxComplete(jQueryAjaxComplete); + } + + if (jQuery && jQuery(document).ajaxStart) + jQuery(document).ajaxStart(function () { ajaxStartTime = new Date(); }); + + // fetch results after ASP Ajax calls + if (typeof (Sys) != 'undefined' && typeof (Sys.WebForms) != 'undefined' && typeof (Sys.WebForms.PageRequestManager) != 'undefined') { + // Get the instance of PageRequestManager. + var PageRequestManager = Sys.WebForms.PageRequestManager.getInstance(); + + PageRequestManager.add_endRequest(function (sender, args) { + if (args) { + var response = args.get_response(); + if (response.get_responseAvailable() && response._xmlHttpRequest != null) { + var stringIds = args.get_response().getResponseHeader('X-MiniProfiler-Ids'); + if (stringIds) { + var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds); + fetchResults(ids); + } + } + } + }); + } + + // more Asp.Net callbacks + if (typeof (WebForm_ExecuteCallback) == "function") { + WebForm_ExecuteCallback = (function (callbackObject) { + // Store original function + var original = WebForm_ExecuteCallback; + + return function (callbackObject) { + original(callbackObject); + + var stringIds = callbackObject.xmlRequest.getResponseHeader('X-MiniProfiler-Ids'); + if (stringIds) { + var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds); + fetchResults(ids); + } + }; + + })(); + } + + // also fetch results after ExtJS requests, in case it is being used + if (typeof (Ext) != 'undefined' && typeof (Ext.Ajax) != 'undefined' && typeof (Ext.Ajax.on) != 'undefined') { + // Ext.Ajax is a singleton, so we just have to attach to its 'requestcomplete' event + Ext.Ajax.on('requestcomplete', function (e, xhr, settings) { + //iframed file uploads don't have headers + if (!xhr || !xhr.getResponseHeader) { + return; + } + + var stringIds = xhr.getResponseHeader('X-MiniProfiler-Ids'); + if (stringIds) { + var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds); + fetchResults(ids); + } + }); + } + + if (typeof (MooTools) != 'undefined' && typeof (Request) != 'undefined') { + Request.prototype.addEvents({ + onComplete: function() { + var stringIds = this.xhr.getResponseHeader('X-MiniProfiler-Ids'); + if (stringIds) { + var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds); + fetchResults(ids); + } + } + }); + } + + // add support for AngularJS, which use the basic XMLHttpRequest object. + if (window.angular && typeof (XMLHttpRequest) != 'undefined') { + var _send = XMLHttpRequest.prototype.send; + + XMLHttpRequest.prototype.send = function sendReplacement(data) { + this._onreadystatechange = this.onreadystatechange; + + this.onreadystatechange = function onReadyStateChangeReplacement() { + if (this.readyState == 4) { + var stringIds = this.getResponseHeader('X-MiniProfiler-Ids'); + if (stringIds) { + var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds); + fetchResults(ids); + } + } + + return this._onreadystatechange.apply(this, arguments); + }; + + return _send.apply(this, arguments); + }; + } + + // some elements want to be hidden on certain doc events + bindDocumentEvents(); + }; + + return { + + init: function () { + var script = document.getElementById('mini-profiler'); + if (!script || !script.getAttribute) return; + + options = (function () { + var version = script.getAttribute('data-version'); + var path = script.getAttribute('data-path'); + + var currentId = script.getAttribute('data-current-id'); + + var ids = script.getAttribute('data-ids'); + if (ids) ids = ids.split(','); + + var position = script.getAttribute('data-position'); + + var toggleShortcut = script.getAttribute('data-toggle-shortcut'); + + if (script.getAttribute('data-max-traces')) { + var maxTraces = parseInt(script.getAttribute('data-max-traces'), 10); + } + + if (script.getAttribute('data-trivial') === 'true') var trivial = true; + if (script.getAttribute('data-children') == 'true') var children = true; + if (script.getAttribute('data-controls') == 'true') var controls = true; + if (script.getAttribute('data-authorized') == 'true') var authorized = true; + if (script.getAttribute('data-start-hidden') == 'true') var startHidden = true; + + return { + ids: ids, + path: path, + version: version, + renderPosition: position, + showTrivial: trivial, + showChildrenTime: children, + maxTracesToShow: maxTraces, + showControls: controls, + currentId: currentId, + authorized: authorized, + toggleShortcut: toggleShortcut, + startHidden: startHidden + }; + })(); + + var doInit = function () { + // when rendering a shared, full page, this div will exist + container = $('.profiler-result-full'); + if (container.length) { + if (window.location.href.indexOf("&trivial=1") > 0) { + options.showTrivial = true; + } + initFullView(); + } + else { + initPopupView(); + } + }; + + // this preserves debugging + var load = function (s, f) { + var sc = document.createElement("script"); + sc.async = "async"; + sc.type = "text/javascript"; + sc.src = s; + var done = false; + sc.onload = sc.onreadystatechange = function (_, abort) { + if (!sc.readyState || /loaded|complete/.test(sc.readyState)) { + if (!abort && !done) { done = true; f(); } + } + }; + document.getElementsByTagName('head')[0].appendChild(sc); + }; + + var wait = 0; + var finish = false; + var deferInit = function() { + if (finish) return; + if (window.performance && window.performance.timing && window.performance.timing.loadEventEnd === 0 && wait < 10000) { + setTimeout(deferInit, 100); + wait += 100; + } else { + finish = true; + init(); + } + }; + + var init = function() { + if (options.authorized) { + var url = options.path + "includes.css?v=" + options.version; + if (document.createStyleSheet) { + document.createStyleSheet(url); + } else { + $('head').append($('')); + } + if (!$.tmpl) { + load(options.path + 'jquery.tmpl.js?v=' + options.version, doInit); + } else { + doInit(); + } + } else { + doInit(); + } + + // jquery.hotkeys.js + // https://github.com/jeresig/jquery.hotkeys/blob/master/jquery.hotkeys.js + + (function(d){function h(g){if("string"===typeof g.data){var h=g.handler,j=g.data.toLowerCase().split(" ");g.handler=function(b){if(!(this!==b.target&&(/textarea|select/i.test(b.target.nodeName)||"text"===b.target.type))){var c="keypress"!==b.type&&d.hotkeys.specialKeys[b.which],e=String.fromCharCode(b.which).toLowerCase(),a="",f={};b.altKey&&"alt"!==c&&(a+="alt+");b.ctrlKey&&"ctrl"!==c&&(a+="ctrl+");b.metaKey&&(!b.ctrlKey&&"meta"!==c)&&(a+="meta+");b.shiftKey&&"shift"!==c&&(a+="shift+");c?f[a+c]= + !0:(f[a+e]=!0,f[a+d.hotkeys.shiftNums[e]]=!0,"shift+"===a&&(f[d.hotkeys.shiftNums[e]]=!0));c=0;for(e=j.length;c","/":"?","\\":"|"}};d.each(["keydown","keyup","keypress"],function(){d.event.special[this]={add:h}})})(MiniProfiler.jQuery); + + }; + + var major, minor; + if (typeof(jQuery) == 'function') { + var jQueryVersion = jQuery.fn.jquery.split('.'); + major = parseInt(jQueryVersion[0], 10); + minor = parseInt(jQueryVersion[1], 10); + } + + + if (major === 2 || (major === 1 && minor >= 7)) { + MiniProfiler.jQuery = $ = jQuery; + $(deferInit); + } else { + load(options.path + "jquery.1.7.1.js?v=" + options.version, function() { + MiniProfiler.jQuery = $ = jQuery.noConflict(true); + $(deferInit); + }); + } + }, + + getClientTimingByName: function (clientTiming, name) { + + for (var i = 0; i < clientTiming.Timings.length; i++) { + if (clientTiming.Timings[i].Name == name) { + return clientTiming.Timings[i]; + } + } + return { Name: name, Duration: "", Start: "" }; + }, + + renderDate: function (jsonDate) { // JavaScriptSerializer sends dates as /Date(1308024322065)/ + if (jsonDate) { + return (typeof jsonDate === 'string') ? new Date(parseInt(jsonDate.replace("/Date(", "").replace(")/", ""), 10)).toUTCString() : jsonDate; + } + }, + + renderIndent: function (depth) { + var result = ''; + for (var i = 0; i < depth; i++) { + result += ' '; + } + return result; + }, + + renderExecuteType: function (typeId) { + // see StackExchange.Profiling.ExecuteType enum + switch (typeId) { + case 0: return 'None'; + case 1: return 'NonQuery'; + case 2: return 'Scalar'; + case 3: return 'Reader'; + } + }, + + shareUrl: function (id) { + return options.path + 'results?id=' + id; + }, + + getClientTimings: function (clientTimings) { + var list = []; + var t; + + if (!clientTimings.Timings) return []; + + for (var i = 0; i < clientTimings.Timings.length; i++) { + t = clientTimings.Timings[i]; + var trivial = t.Name != "Dom Complete" && t.Name != "Response" && t.Name != "First Paint Time"; + trivial = t.Duration < 2 ? trivial : false; + list.push( + { + isTrivial: trivial, + name: t.Name, + duration: t.Duration, + start: t.Start + }); + } + + list.sort(function (a, b) { return a.start - b.start; }); + return list; + }, + + getSqlTimings: function (root) { + var result = [], + addToResults = function (timing) { + if (timing.SqlTimings) { + for (var i = 0, sqlTiming; i < timing.SqlTimings.length; i++) { + sqlTiming = timing.SqlTimings[i]; + + // HACK: add info about the parent Timing to each SqlTiming so UI can render + sqlTiming.ParentTimingName = timing.Name; + result.push(sqlTiming); + } + } + + if (timing.Children) { + for (var i = 0; i < timing.Children.length; i++) { + addToResults(timing.Children[i]); + } + } + }; + + // start adding at the root and recurse down + addToResults(root); + + var removeDuration = function(list, duration) { + + var newList = []; + for (var i = 0; i < list.length; i++) { + + var item = list[i]; + if (duration.start > item.start) { + if (duration.start > item.finish) { + newList.push(item); + continue; + } + newList.push({ start: item.start, finish: duration.start }); + } + + if (duration.finish < item.finish) { + if (duration.finish < item.start) { + newList.push(item); + continue; + } + newList.push({ start: duration.finish, finish: item.finish }); + } + } + + return newList; + }; + + var processTimes = function (elem, parent) { + var duration = { start: elem.StartMilliseconds, finish: (elem.StartMilliseconds + elem.DurationMilliseconds) }; + elem.richTiming = [duration]; + if (parent != null) { + elem.parent = parent; + elem.parent.richTiming = removeDuration(elem.parent.richTiming, duration); + } + + if (elem.Children) { + for (var i = 0; i < elem.Children.length; i++) { + processTimes(elem.Children[i], elem); + } + } + }; + + processTimes(root, null); + + // sort results by time + result.sort(function (a, b) { return a.StartMilliseconds - b.StartMilliseconds; }); + + var determineOverlap = function(gap, node) { + var overlap = 0; + for (var i = 0; i < node.richTiming.length; i++) { + var current = node.richTiming[i]; + if (current.start > gap.finish) { + break; + } + if (current.finish < gap.start) { + continue; + } + + overlap += Math.min(gap.finish, current.finish) - Math.max(gap.start, current.start); + } + return overlap; + }; + + var determineGap = function (gap, node, match) { + var overlap = determineOverlap(gap, node); + if (match == null || overlap > match.duration) { + match = { name: node.Name, duration: overlap }; + } + else if (match.name == node.Name) { + match.duration += overlap; + } + + if (node.Children) { + for (var i = 0; i < node.Children.length; i++) { + match = determineGap(gap, node.Children[i], match); + } + } + return match; + }; + + var time = 0; + var prev = null; + $.each(result, function () { + this.prevGap = { + duration: (this.StartMilliseconds - time).toFixed(2), + start: time, + finish: this.StartMilliseconds + }; + + this.prevGap.topReason = determineGap(this.prevGap, root, null); + + time = this.StartMilliseconds + this.DurationMilliseconds; + prev = this; + }); + + + if (result.length > 0) { + var me = result[result.length - 1]; + me.nextGap = { + duration: (root.DurationMilliseconds - time).toFixed(2), + start: time, + finish: root.DurationMilliseconds + }; + me.nextGap.topReason = determineGap(me.nextGap, root, null); + } + + return result; + }, + + getSqlTimingsCount: function (root) { + var result = 0, + countSql = function (timing) { + if (timing.SqlTimings) { + result += timing.SqlTimings.length; + } + + if (timing.Children) { + for (var i = 0; i < timing.Children.length; i++) { + countSql(timing.Children[i]); + } + } + }; + countSql(root); + return result; + }, + + fetchResultsExposed: function (ids) { + return fetchResults(ids); + }, + + formatDuration: function (duration) { + return (duration || 0).toFixed(1); + } + }; +})(); + +MiniProfiler.init(); + +if (typeof prettyPrint === "undefined") { + + // prettify.js + // http://code.google.com/p/google-code-prettify/ + + window.PR_SHOULD_USE_CONTINUATION=true;window.PR_TAB_WIDTH=8;window.PR_normalizedHtml=window.PR=window.prettyPrintOne=window.prettyPrint=void 0;window._pr_isIE6=function(){var y=navigator&&navigator.userAgent&&navigator.userAgent.match(/\bMSIE ([678])\./);y=y?+y[1]:false;window._pr_isIE6=function(){return y};return y}; + (function(){function y(b){return b.replace(L,"&").replace(M,"<").replace(N,">")}function H(b,f,i){switch(b.nodeType){case 1:var o=b.tagName.toLowerCase();f.push("<",o);var l=b.attributes,n=l.length;if(n){if(i){for(var r=[],j=n;--j>=0;)r[j]=l[j];r.sort(function(q,m){return q.name"); + for(l=b.firstChild;l;l=l.nextSibling)H(l,f,i);if(b.firstChild||!/^(?:br|link|img)$/.test(o))f.push("");break;case 3:case 4:f.push(y(b.nodeValue));break}}function O(b){function f(c){if(c.charAt(0)!=="\\")return c.charCodeAt(0);switch(c.charAt(1)){case "b":return 8;case "t":return 9;case "n":return 10;case "v":return 11;case "f":return 12;case "r":return 13;case "u":case "x":return parseInt(c.substring(2),16)||c.charCodeAt(1);case "0":case "1":case "2":case "3":case "4":case "5":case "6":case "7":return parseInt(c.substring(1), + 8);default:return c.charCodeAt(1)}}function i(c){if(c<32)return(c<16?"\\x0":"\\x")+c.toString(16);c=String.fromCharCode(c);if(c==="\\"||c==="-"||c==="["||c==="]")c="\\"+c;return c}function o(c){var d=c.substring(1,c.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));c=[];for(var a=[],k=d[0]==="^",e=k?1:0,h=d.length;e122)){s<65||g>90||a.push([Math.max(65,g)|32,Math.min(s,90)|32]);s<97||g>122||a.push([Math.max(97,g)&-33,Math.min(s,122)&-33])}}a.sort(function(v,w){return v[0]-w[0]||w[1]-v[1]});d=[];g=[NaN,NaN];for(e=0;eh[0]){h[1]+1>h[0]&&a.push("-"); + a.push(i(h[1]))}}a.push("]");return a.join("")}function l(c){for(var d=c.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g")),a=d.length,k=[],e=0,h=0;e=2&&c==="[")d[e]=o(g);else if(c!=="\\")d[e]=g.replace(/[a-zA-Z]/g,function(s){s=s.charCodeAt(0);return"["+String.fromCharCode(s&-33,s|32)+"]"})}return d.join("")}for(var n=0,r=false,j=false,q=0,m=b.length;q=0;l-=16)o.push(" ".substring(0,l));l=n+1;break;case "\n":f=0;break;default:++f}if(!o)return i;o.push(i.substring(l));return o.join("")}}function I(b, + f,i,o){if(f){b={source:f,c:b};i(b);o.push.apply(o,b.d)}}function B(b,f){var i={},o;(function(){for(var r=b.concat(f),j=[],q={},m=0,t=r.length;m=0;)i[c.charAt(d)]=p;p=p[1];c=""+p;if(!q.hasOwnProperty(c)){j.push(p);q[c]=null}}j.push(/[\0-\uffff]/);o=O(j)})();var l=f.length;function n(r){for(var j=r.c,q=[j,z],m=0,t=r.source.match(o)||[],p={},c=0,d=t.length;c=5&&"lang-"===k.substring(0,5))&&!(e&&typeof e[1]==="string")){h=false;k=P}h||(p[a]=k)}g=m;m+=a.length;if(h){h=e[1];var s=a.indexOf(h),v=s+h.length;if(e[2]){v=a.length-e[2].length;s=v-h.length}k=k.substring(5);I(j+g,a.substring(0,s),n,q);I(j+g+s,h,Q(k,h),q);I(j+g+v,a.substring(v),n,q)}else q.push(j+g,k)}r.d=q}return n}function x(b){var f=[],i=[];if(b.tripleQuotedStrings)f.push([A,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, + null,"'\""]);else b.multiLineStrings?f.push([A,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"]):f.push([A,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"]);b.verbatimStrings&&i.push([A,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null]);if(b.hashComments)if(b.cStyleComments){f.push([C,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"]);i.push([A,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/, + null])}else f.push([C,/^#[^\r\n]*/,null,"#"]);if(b.cStyleComments){i.push([C,/^\/\/[^\r\n]*/,null]);i.push([C,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}b.regexLiterals&&i.push(["lang-regex",RegExp("^"+Z+"(/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/)")]);b=b.keywords.replace(/^\s+|\s+$/g,"");b.length&&i.push([R,RegExp("^(?:"+b.replace(/\s+/g,"|")+")\\b"),null]);f.push([z,/^\s+/,null," \r\n\t\u00a0"]);i.push([J,/^@[a-z_$][a-z_$@0-9]*/i,null],[S,/^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/, + null],[z,/^[a-z_$][a-z_$@0-9]*/i,null],[J,/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],[E,/^.[^\s\w\.$@\'\"\`\/\#]*/,null]);return B(f,i)}function $(b){function f(D){if(D>r){if(j&&j!==q){n.push("");j=null}if(!j&&q){j=q;n.push('')}var T=y(p(i.substring(r,D))).replace(e?d:c,"$1 ");e=k.test(T);n.push(T.replace(a,s));r=D}}var i=b.source,o=b.g,l=b.d,n=[],r=0,j=null,q=null,m=0,t=0,p=Y(window.PR_TAB_WIDTH),c=/([\r\n ]) /g, + d=/(^| ) /gm,a=/\r\n?|\n/g,k=/[ \r\n]$/,e=true,h=window._pr_isIE6();h=h?b.b.tagName==="PRE"?h===6?" \r\n":h===7?" 
\r":" \r":" 
":"
";var g=b.b.className.match(/\blinenums\b(?::(\d+))?/),s;if(g){for(var v=[],w=0;w<10;++w)v[w]=h+'
  • ';var F=g[1]&&g[1].length?g[1]-1:0;n.push('
    1. ");s=function(){var D=v[++F%10];return j?""+D+'':D}}else s=h; + for(;;)if(m");j=null}n.push(o[m+1]);m+=2}else if(t");g&&n.push("
    ");b.a=n.join("")}function u(b,f){for(var i=f.length;--i>=0;){var o=f[i];if(G.hasOwnProperty(o))"console"in window&&console.warn("cannot override language handler %s",o);else G[o]=b}}function Q(b,f){b&&G.hasOwnProperty(b)||(b=/^\s*1&&m.charAt(0)==="<"){if(!ba.test(m))if(ca.test(m)){f.push(m.substring(9,m.length-3));n+=m.length-12}else if(da.test(m)){f.push("\n");++n}else if(m.indexOf(V)>=0&&m.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,' $1="$2$3$4"').match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/)){var t=m.match(W)[2],p=1,c;c=j+1;a:for(;c=0;){var e=p.indexOf(";",k);if(e>=0){var h=p.substring(k+3,e),g=10;if(h&&h.charAt(0)==="x"){h=h.substring(1);g=16}var s=parseInt(h,g);isNaN(s)||(p=p.substring(0,k)+String.fromCharCode(s)+p.substring(e+1))}}a=p.replace(ea,"<").replace(fa,">").replace(ga,"'").replace(ha,'"').replace(ia," ").replace(ja, + "&")}f.push(a);n+=a.length}}o={source:f.join(""),h:r};var v=o.source;b.source=v;b.c=0;b.g=o.h;Q(i,v)(b);$(b)}catch(w){if("console"in window)console.log(w&&w.stack?w.stack:w)}}var A="str",R="kwd",C="com",S="typ",J="lit",E="pun",z="pln",P="src",V="nocode",Z=function(){for(var b=["!","!=","!==","#","%","%=","&","&&","&&=","&=","(","*","*=","+=",",","-=","->","/","/=",":","::",";","<","<<","<<=","<=","=","==","===",">",">=",">>",">>=",">>>",">>>=","?","@","[","^","^=","^^","^^=","{","|","|=","||","||=", + "~","break","case","continue","delete","do","else","finally","instanceof","return","throw","try","typeof"],f="(?:^^|[+-]",i=0;i:&a-z])/g,"\\$1");f+=")\\s*";return f}(),L=/&/g,M=//g,X=/\"/g,ea=/</g,fa=/>/g,ga=/'/g,ha=/"/g,ja=/&/g,ia=/ /g,ka=/[\r\n]/g,K=null,aa=RegExp("[^<]+|

    <% end %> From 31804b3d4598c02a6881a23a8025998f4d33b6ab Mon Sep 17 00:00:00 2001 From: z9hang Date: Wed, 28 May 2014 11:10:31 +0800 Subject: [PATCH 10/22] =?UTF-8?q?=E5=8F=91=E9=82=AE=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/mailer.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/models/mailer.rb b/app/models/mailer.rb index e5ede628..a303d92b 100644 --- a/app/models/mailer.rb +++ b/app/models/mailer.rb @@ -22,7 +22,7 @@ class Mailer < ActionMailer::Base helper :custom_fields include Redmine::I18n - + include CoursesHelper def self.default_url_options { :host => Setting.host_name, :protocol => Setting.protocol } end @@ -44,6 +44,16 @@ class Mailer < ActionMailer::Base when :Bid respond_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") when :Project + if journals_for_message.jour.project_type == 1 + project = journals_for_message.jour + @teachers = searchTeacherAndAssistant journals_for_message.jour + @recipients ||= [] + @teachers.each do |teacher| + @recipients << teacher + end + mail :to => @recipients, + :subject => "您的课程#{journals_for_message.jour.name}中有了新的留言" + end project_feedback_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") when :Contest show_contest_contest_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") From 5d8edb218f416ee10a6229a3b50609314475ddaf Mon Sep 17 00:00:00 2001 From: yanxd Date: Wed, 28 May 2014 11:47:50 +0800 Subject: [PATCH 11/22] =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E6=9F=A5?= =?UTF-8?q?=E7=9C=8Buser=5Fprojects=20=E8=AF=BE=E7=A8=8B=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=B7=B7=E6=B7=86=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index cdecb97f..917d5b33 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -65,7 +65,7 @@ class UsersController < ApplicationController def user_projects if User.current.admin? - @memberships = @user.memberships.all + @memberships = @user.memberships.all(conditions: "projects.project_type = #{Project::ProjectType_project}") else cond = Project.visible_condition(User.current) + " AND projects.project_type <> 1" @memberships = @user.memberships.all(:conditions => cond) From daecf1a53b83aca4dd59043be4a681d8ebb2c47a Mon Sep 17 00:00:00 2001 From: z9hang Date: Wed, 28 May 2014 14:17:43 +0800 Subject: [PATCH 12/22] =?UTF-8?q?=E8=80=81=E5=B8=88=E6=94=B6=E5=88=B0?= =?UTF-8?q?=E8=AF=BE=E7=A8=8B=E7=95=99=E8=A8=80=E5=8C=BA=E7=9A=84=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E7=95=99=E8=A8=80=E5=8F=8A=E5=AF=B9=E8=80=81=E5=B8=88?= =?UTF-8?q?=E7=9A=84=E5=9B=9E=E5=A4=8D=E7=9A=84=E9=82=AE=E4=BB=B6=E6=8F=90?= =?UTF-8?q?=E9=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/journals_for_message_observer.rb | 2 -- app/models/mailer.rb | 34 +++++++++++++-------- config/locales/en.yml | 3 +- config/locales/zh.yml | 2 ++ 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/app/models/journals_for_message_observer.rb b/app/models/journals_for_message_observer.rb index e5fdfb18..093002a2 100644 --- a/app/models/journals_for_message_observer.rb +++ b/app/models/journals_for_message_observer.rb @@ -1,9 +1,7 @@ # Added by young class JournalsForMessageObserver < ActiveRecord::Observer def after_create(journals_for_message) - Thread.new do Mailer.journals_for_message_add(User.current, journals_for_message).deliver - end end end diff --git a/app/models/mailer.rb b/app/models/mailer.rb index a303d92b..e0ad5429 100644 --- a/app/models/mailer.rb +++ b/app/models/mailer.rb @@ -44,16 +44,7 @@ class Mailer < ActionMailer::Base when :Bid respond_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") when :Project - if journals_for_message.jour.project_type == 1 - project = journals_for_message.jour - @teachers = searchTeacherAndAssistant journals_for_message.jour - @recipients ||= [] - @teachers.each do |teacher| - @recipients << teacher - end - mail :to => @recipients, - :subject => "您的课程#{journals_for_message.jour.name}中有了新的留言" - end + return -1 if journals_for_message.jour.project_type == Project::ProjectType_project project_feedback_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") when :Contest show_contest_contest_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}") @@ -63,7 +54,23 @@ class Mailer < ActionMailer::Base logger.error "[Builds a Mail::Message ERROR] journalsForMessage's jour is unkown type, journalsForMessage.id = #{journals_for_message.id}" return -1 end - mail :to => @mail.mail, :subject => @title + #如果是直接留言并且留言对象是Project并且Project类型是课程 + if !journals_for_message.at_user && journals_for_message.jour.class.to_s.to_sym == :Project && journals_for_message.jour.project_type == 1 + project = journals_for_message.jour + #课程的教师 + @teachers = searchTeacherAndAssistant journals_for_message.jour + #收件人邮箱 + @recipients ||= [] + @teachers.each do |teacher| + @recipients << teacher.user.mail + end + mail :to => @recipients, + :subject => "#{l(:label_your_course)}#{journals_for_message.jour.name}#{l(:label_have_message)} " + else + mail :to => @mail.mail, :subject => @title + end + + end # Builds a Mail::Message object used to email recipients of the added issue. @@ -433,7 +440,10 @@ class Mailer < ActionMailer::Base headers[:to].delete(@author.mail) if headers[:to].is_a?(Array) headers[:cc].delete(@author.mail) if headers[:cc].is_a?(Array) end - + if !User.current.nil? + #不给本人发邮件 + headers[:to].delete(User.current.mail) if headers[:to].is_a?(Array) + end if @author && @author.logged? redmine_headers 'Sender' => @author.login end diff --git a/config/locales/en.yml b/config/locales/en.yml index 22422c42..76b820bd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1617,6 +1617,7 @@ en: label_class_hour: period label_activity_time: publish date - + label_your_course: your course + label_have_message : have a new message # ajax异步验证 modal_valid_passing: can be used. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 39749b69..7f20912f 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -1497,6 +1497,8 @@ zh: label_tags_numbers: Tag统计: label_max_number: 至多25个字符。 label_mail_attention: qq邮箱可能收不到此邮件,其他邮箱如果没有收到可能在垃圾邮件中,其中gmail与教育网邮箱的激活邮件有时比较慢,请耐心等待。 + label_your_course: 您的课程《 + label_have_message : 》有新的留言 label_all_revisions: 所有版本: label_upassword_info: 该密码在项目组内可共享 label_how_commit_code: 查看如何提交代码: From 61135a352aefc03babc1371f88b587dab9b262d6 Mon Sep 17 00:00:00 2001 From: nwb Date: Wed, 28 May 2014 14:43:20 +0800 Subject: [PATCH 13/22] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E8=A1=A8=E5=8D=95=E6=94=AF=E6=8C=81=E6=B7=BB=E5=8A=A0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=A0=87=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/files_controller.rb | 31 ++++++++++++++++++++++++++--- app/controllers/users_controller.rb | 2 +- app/helpers/tags_helper.rb | 6 +++++- app/views/attachments/upload.js.erb | 3 +-- app/views/files/_new.html.erb | 2 +- app/views/tags/_tagEx.html.erb | 26 +++++++----------------- config/routes.rb | 5 ++++- db/schema.rb | 17 +++++++++------- 8 files changed, 57 insertions(+), 35 deletions(-) diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index f9236d0b..5b81b729 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -52,9 +52,14 @@ class FilesController < ApplicationController end def create - if params[:tag_name] + if params[:add_tag] + @addTag=true + #render :back tag_saveEx - render :text =>"success" + #render :text =>"success" + respond_to do |format| + format.js + end else @addTag=false container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id])) @@ -64,7 +69,27 @@ class FilesController < ApplicationController if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added') Mailer.attachments_added(attachments[:files]).deliver end - redirect_to project_files_path(@project) + + # 临时用 + sort_init 'created_on', 'desc' + sort_update 'created_on' => "#{Attachment.table_name}.created_on", + 'filename' => "#{Attachment.table_name}.filename", + 'size' => "#{Attachment.table_name}.filesize", + 'downloads' => "#{Attachment.table_name}.downloads" + + @containers = [ Project.includes(:attachments).reorder("#{Attachment.table_name}.created_on DESC").find(@project.id)] #modify by Long Jun + @containers += @project.versions.includes(:attachments).reorder("#{Attachment.table_name}.created_on DESC").all.sort + + @attachtype = 0 + @contenttype = 0 + + respond_to do |format| + format.js + format.html { + redirect_to project_files_path(@project) + } + end + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index cdecb97f..840dabfe 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -689,7 +689,7 @@ class UsersController < ApplicationController end def tag_saveEx - @tags = params[:tag_name][:name] + @tags = params[:tag_name] @obj_id = params[:obj_id] @obj_flag = params[:obj_flag] diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb index ba2470fe..201a582f 100644 --- a/app/helpers/tags_helper.rb +++ b/app/helpers/tags_helper.rb @@ -49,5 +49,9 @@ module TagsHelper end return @result end - + +end + +def tagname_val + ("#tag_name_name").value end \ No newline at end of file diff --git a/app/views/attachments/upload.js.erb b/app/views/attachments/upload.js.erb index 674d73b7..63600619 100644 --- a/app/views/attachments/upload.js.erb +++ b/app/views/attachments/upload.js.erb @@ -12,6 +12,5 @@ fileSpan.find('a.remove-upload') }) .off('click'); var divattach = fileSpan.find('div.div_attachments'); -divattach.html('<%#= j(render :partial => 'tags/tagEx', :locals => {:obj => @attachment, :object_flag => "6"})%>'); - +divattach.html('<%= j(render :partial => 'tags/tagEx', :locals => {:obj => @attachment, :object_flag => "6"})%>'); <% end %> diff --git a/app/views/files/_new.html.erb b/app/views/files/_new.html.erb index a01143e0..7dcf82e1 100644 --- a/app/views/files/_new.html.erb +++ b/app/views/files/_new.html.erb @@ -2,7 +2,7 @@ <% versions = project.versions.sort %> <% attachmenttypes = project.attachmenttypes %> <%= error_messages_for 'attachment' %> -<%= form_tag(project_files_path(project), :multipart => true,:name=>"upload_form", :class => "tabular") do %> +<%= form_tag(project_files_path(project), :multipart => true,:remote => true,:method => :post,:name=>"upload_form", :class => "tabular") do %>

  • <%= image_tag(url_to_avatar(homework.user), :class => "avatar")%>
    <%= image_tag(url_to_avatar(homework.user), :class => "avatar")%> @@ -44,8 +46,12 @@ <% end %> @@ -71,7 +77,7 @@ diff --git a/app/views/homework_attach/_addjour.html.erb b/app/views/homework_attach/_addjour.html.erb index a5e7bed6..6cb35bcc 100644 --- a/app/views/homework_attach/_addjour.html.erb +++ b/app/views/homework_attach/_addjour.html.erb @@ -56,6 +56,7 @@ :url => {:controller => 'homework_attach', :action => 'addjours', :jour_id => homework_attach.id, + :is_comprehensive_evaluation => is_comprehensive_evaluation, :sta => sta}) do |f|%>
    diff --git a/app/views/homework_attach/_comprehensive_evaluation.html.erb b/app/views/homework_attach/_comprehensive_evaluation.html.erb new file mode 100644 index 00000000..df0c03b2 --- /dev/null +++ b/app/views/homework_attach/_comprehensive_evaluation.html.erb @@ -0,0 +1,50 @@ +<% is_teacher = is_course_teacher User.current,homework.bid.courses.first %> +<% if comprehensive_evaluation != nil && comprehensive_evaluation.count > 0 %> + <% stars = homework.rates(:quality).where("rater_id = #{comprehensive_evaluation.first.user.id}").select("stars").first.stars * 2 * 10 %> +
    +
    + 作业综评: + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    <%= comprehensive_evaluation.first.notes%>
    +
    + <% if is_teacher %> +
    评分: + <%= rating_for homework, dimension: :quality, class: 'rateable div_inline' %> + (您可以重新打分,打分结果以最后一次打分为主!) +
    + <% end %> +
    +<% else %> + <% if is_teacher %> +
    +
    + 作业综评: +
    +
    评分: + <%= rating_for homework, dimension: :quality, class: 'rateable div_inline' %> + (您可以重新打分,打分结果以最后一次打分为主!) +
    +
    + <%= render :partial => 'addjour', :locals => {:homework_attach => homework, :sta => 0,:is_comprehensive_evaluation => 1} %> +
    +
    + <% else %> +
    +
    + 作业综评: +
    +
    老师还未进行评价!
    +
    + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/homework_attach/_showjour.html.erb b/app/views/homework_attach/_showjour.html.erb index 939deff3..52976713 100644 --- a/app/views/homework_attach/_showjour.html.erb +++ b/app/views/homework_attach/_showjour.html.erb @@ -38,10 +38,25 @@ <% if jour.size > 0 %>
      <% for journal in jour%> + <% seems = homework.rates(:quality).where("rater_id = #{journal.user.id}").select("stars").first %>
    • <%= image_tag(url_to_avatar(journal.user), :class => "avatar") %> - <%= link_to journal.user, user_path(journal.user)%> + + <%= link_to journal.user, user_path(journal.user)%> + + <% label = l(:label_contest_requirement) %>
      <%= textilizable journal.notes%>
      <%= l(:label_bids_published) %> @@ -70,4 +85,12 @@
    • <% end %>
    -<% end %> \ No newline at end of file +<% end %> + + + + \ No newline at end of file diff --git a/app/views/homework_attach/addjours.js.erb b/app/views/homework_attach/addjours.js.erb index e463237e..ed5887a7 100644 --- a/app/views/homework_attach/addjours.js.erb +++ b/app/views/homework_attach/addjours.js.erb @@ -1,4 +1,9 @@ -$('#message').html('<%= escape_javascript(render(:partial => 'showjour', :locals => {:jour =>@jour, :state => false} )) %>'); -$('#pre_show').html('<%= escape_javascript(render(:partial => 'pre_show', :locals => {:content => nil})) %>'); -$('#new_form_user_message').val(""); -$('#new_form_reference_user_id').val(""); \ No newline at end of file +<% if @add_jour.is_comprehensive_evaluation == 1 %> + $('#comprehensive_evaluation').html('<%= escape_javascript(render(:partial => 'comprehensive_evaluation', + :locals => {:comprehensive_evaluation => @comprehensive_evaluation,:homework => @homework} )) %>'); +<% else %> + $('#message').html('<%= escape_javascript(render(:partial => 'showjour', :locals => {:jour =>@jour, :state => false,:homework => @homework} )) %>'); + $('#pre_show').html('<%= escape_javascript(render(:partial => 'pre_show', :locals => {:content => nil})) %>'); + $('#new_form_user_message').val(""); + $('#new_form_reference_user_id').val(""); +<% end %> \ No newline at end of file diff --git a/app/views/homework_attach/show.html.erb b/app/views/homework_attach/show.html.erb index f2c37d9a..9c39e84f 100644 --- a/app/views/homework_attach/show.html.erb +++ b/app/views/homework_attach/show.html.erb @@ -6,6 +6,9 @@ height: 200px; } +<% is_student = is_cur_course_student? @homework.bid.courses.first %> +<% is_teacher = is_course_teacher User.current,@homework.bid.courses.first %> +

    <%= notice %>

    @@ -93,10 +96,14 @@
    + <% score = @homework.average(:quality).try(:avg).try(:round, 2).to_s %>
    最终得分
    - <%= @homework.average(:quality).try(:avg).try(:round, 2).to_s %> - 分 + <% if score == "" %> + 0分 + <% else %> + <%= score %>分 + <% end %>
    <%= rating_for @homework, :static => true, dimension: :quality, class: 'rateable div_inline' %> @@ -114,42 +121,32 @@
    - +
    + <%= render :partial => 'comprehensive_evaluation', :locals => {:comprehensive_evaluation => @comprehensive_evaluation,:homework => @homework} %>
    -
    ---> +
    作业评论:
    -
    评分: - <%= rating_for @homework, dimension: :quality, class: 'rateable div_inline' %> - (您可以重新打分,打分结果以最后一次打分为主!) -
    - + <% if is_student %> +
    评分: + <%= rating_for @homework, dimension: :quality, class: 'rateable div_inline' %> + (您可以重新打分,打分结果以最后一次打分为主!) +
    + <% end %>
    +<% if !is_teacher %> -
    - <%= render :partial => 'addjour', :locals => {:homework_attach => @homework, :sta => 0} %> +
    + <%= render :partial => 'addjour', :locals => {:homework_attach => @homework, :sta => 0, :is_comprehensive_evaluation => nil} %>
    +<% end %>
    - <%= render :partial => 'showjour', :locals => {:jour => @jour} %> -
    - - -
    diff --git a/db/migrate/20140527060344_add_coloum_to_homework_journals_for_message.rb b/db/migrate/20140527060344_add_coloum_to_homework_journals_for_message.rb new file mode 100644 index 00000000..744a09d3 --- /dev/null +++ b/db/migrate/20140527060344_add_coloum_to_homework_journals_for_message.rb @@ -0,0 +1,5 @@ +class AddColoumToHomeworkJournalsForMessage < ActiveRecord::Migration + def change + add_column :journals_for_messages, :is_comprehensive_evaluation, :integer + end +end From 8227c5222a92e2da99435ecc3fc756f86093b146 Mon Sep 17 00:00:00 2001 From: sw <939547590@qq.com> Date: Wed, 28 May 2014 08:50:38 +0800 Subject: [PATCH 07/22] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=80=81=E5=B8=88?= =?UTF-8?q?=E6=9C=AA=E8=BF=9B=E8=A1=8C=E7=BB=BC=E8=AF=84=E7=9A=84=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=BF=9B=E5=85=A5=E4=BA=92=E8=AF=84=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E7=9A=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../homework_attach/_comprehensive_evaluation.html.erb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/views/homework_attach/_comprehensive_evaluation.html.erb b/app/views/homework_attach/_comprehensive_evaluation.html.erb index df0c03b2..e6ebacff 100644 --- a/app/views/homework_attach/_comprehensive_evaluation.html.erb +++ b/app/views/homework_attach/_comprehensive_evaluation.html.erb @@ -1,13 +1,17 @@ <% is_teacher = is_course_teacher User.current,homework.bid.courses.first %> <% if comprehensive_evaluation != nil && comprehensive_evaluation.count > 0 %> - <% stars = homework.rates(:quality).where("rater_id = #{comprehensive_evaluation.first.user.id}").select("stars").first.stars * 2 * 10 %> + <% stars = homework.rates(:quality).where("rater_id = #{comprehensive_evaluation.first.user.id}").select("stars").first %>
    作业综评:
    -
    + <% if stars != nil %> +
    + <% else %> +
    + <% end %>
    From c7d6a07b72e3373f848fe4425a739673dd68d211 Mon Sep 17 00:00:00 2001 From: yanxd Date: Wed, 28 May 2014 09:57:30 +0800 Subject: [PATCH 08/22] gems --- Gemfile | 16 +- Gemfile.lock | 16 +- lib/better_errors/.travis.yml | 4 + lib/better_errors/.yardopts | 1 + lib/better_errors/CHANGELOG.md | 3 + lib/better_errors/Gemfile | 10 + lib/better_errors/LICENSE.txt | 22 + lib/better_errors/README.md | 103 ++ lib/better_errors/Rakefile | 13 + lib/better_errors/better_errors.gemspec | 27 + lib/better_errors/lib/better_errors.rb | 146 +++ .../lib/better_errors/code_formatter.rb | 63 + .../lib/better_errors/code_formatter/html.rb | 26 + .../lib/better_errors/code_formatter/text.rb | 14 + .../lib/better_errors/error_page.rb | 110 ++ .../lib/better_errors/exception_extension.rb | 17 + .../lib/better_errors/middleware.rb | 141 +++ lib/better_errors/lib/better_errors/rails.rb | 28 + .../lib/better_errors/raised_exception.rb | 66 ++ lib/better_errors/lib/better_errors/repl.rb | 30 + .../lib/better_errors/repl/basic.rb | 20 + .../lib/better_errors/repl/pry.rb | 78 ++ .../lib/better_errors/stack_frame.rb | 111 ++ .../lib/better_errors/templates/main.erb | 1031 +++++++++++++++++ .../lib/better_errors/templates/text.erb | 21 + .../better_errors/templates/variable_info.erb | 70 ++ .../lib/better_errors/version.rb | 3 + .../spec/better_errors/code_formatter_spec.rb | 92 ++ .../spec/better_errors/error_page_spec.rb | 76 ++ .../spec/better_errors/middleware_spec.rb | 146 +++ .../better_errors/raised_exception_spec.rb | 52 + .../spec/better_errors/repl/basic_spec.rb | 18 + .../spec/better_errors/repl/pry_spec.rb | 40 + .../better_errors/repl/shared_examples.rb | 18 + .../spec/better_errors/stack_frame_spec.rb | 157 +++ .../spec/better_errors/support/my_source.rb | 20 + lib/better_errors/spec/better_errors_spec.rb | 73 ++ lib/better_errors/spec/spec_helper.rb | 5 + .../spec/without_binding_of_caller.rb | 9 + lib/rack-mini-profiler/.travis.yml | 9 + lib/rack-mini-profiler/CHANGELOG | 181 +++ lib/rack-mini-profiler/Gemfile | 3 + lib/rack-mini-profiler/README.md | 271 +++++ lib/rack-mini-profiler/Rakefile | 46 + lib/rack-mini-profiler/autotest/discover.rb | 2 + lib/rack-mini-profiler/lib/html/includes.css | 451 +++++++ lib/rack-mini-profiler/lib/html/includes.js | 960 +++++++++++++++ lib/rack-mini-profiler/lib/html/includes.less | 471 ++++++++ lib/rack-mini-profiler/lib/html/includes.tmpl | 222 ++++ .../lib/html/jquery.1.7.1.js | 4 + .../lib/html/jquery.tmpl.js | 486 ++++++++ lib/rack-mini-profiler/lib/html/list.css | 9 + lib/rack-mini-profiler/lib/html/list.js | 38 + lib/rack-mini-profiler/lib/html/list.tmpl | 34 + .../lib/html/profile_handler.js | 1 + lib/rack-mini-profiler/lib/html/share.html | 11 + .../lib/mini_profiler/client_settings.rb | 65 ++ .../lib/mini_profiler/client_timer_struct.rb | 78 ++ .../lib/mini_profiler/config.rb | 65 ++ .../lib/mini_profiler/context.rb | 11 + .../lib/mini_profiler/custom_timer_struct.rb | 22 + .../lib/mini_profiler/gc_profiler.rb | 181 +++ .../lib/mini_profiler/page_timer_struct.rb | 58 + .../lib/mini_profiler/profiler.rb | 567 +++++++++ .../lib/mini_profiler/profiling_methods.rb | 151 +++ .../lib/mini_profiler/request_timer_struct.rb | 115 ++ .../lib/mini_profiler/sql_timer_struct.rb | 58 + .../mini_profiler/storage/abstract_store.rb | 32 + .../lib/mini_profiler/storage/file_store.rb | 133 +++ .../mini_profiler/storage/memcache_store.rb | 53 + .../lib/mini_profiler/storage/memory_store.rb | 86 ++ .../lib/mini_profiler/storage/redis_store.rb | 54 + .../lib/mini_profiler/timer_struct.rb | 33 + .../lib/mini_profiler/version.rb | 5 + .../lib/mini_profiler_rails/railtie.rb | 106 ++ .../lib/patches/net_patches.rb | 14 + .../lib/patches/sql_patches.rb | 284 +++++ .../lib/rack-mini-profiler.rb | 7 + .../rack-mini-profiler.gemspec | 35 + .../spec/components/client_settings_spec.rb | 45 + .../components/client_timer_struct_spec.rb | 162 +++ .../spec/components/config_spec.rb | 14 + .../spec/components/file_store_spec.rb | 46 + .../spec/components/gc_profiler_spec.rb | 69 ++ .../spec/components/memcache_store_spec.rb | 59 + .../spec/components/memory_store_spec.rb | 40 + .../spec/components/page_timer_struct_spec.rb | 33 + .../spec/components/profiler_spec.rb | 131 +++ .../spec/components/redis_store_spec.rb | 79 ++ .../components/request_timer_struct_spec.rb | 148 +++ .../spec/components/sql_timer_struct_spec.rb | 80 ++ .../spec/components/timer_struct_spec.rb | 54 + .../spec/fixtures/simple_client_request.yml | 17 + .../spec/fixtures/weird_client_request.yml | 14 + .../spec/integration/mini_profiler_spec.rb | 261 +++++ lib/rack-mini-profiler/spec/spec_helper.rb | 31 + .../spec/support/expand_each_to_matcher.rb | 8 + lib/rack-mini-profiler/test_old/config.ru | 40 + 98 files changed, 9628 insertions(+), 11 deletions(-) create mode 100644 lib/better_errors/.travis.yml create mode 100644 lib/better_errors/.yardopts create mode 100644 lib/better_errors/CHANGELOG.md create mode 100644 lib/better_errors/Gemfile create mode 100644 lib/better_errors/LICENSE.txt create mode 100644 lib/better_errors/README.md create mode 100644 lib/better_errors/Rakefile create mode 100644 lib/better_errors/better_errors.gemspec create mode 100644 lib/better_errors/lib/better_errors.rb create mode 100644 lib/better_errors/lib/better_errors/code_formatter.rb create mode 100644 lib/better_errors/lib/better_errors/code_formatter/html.rb create mode 100644 lib/better_errors/lib/better_errors/code_formatter/text.rb create mode 100644 lib/better_errors/lib/better_errors/error_page.rb create mode 100644 lib/better_errors/lib/better_errors/exception_extension.rb create mode 100644 lib/better_errors/lib/better_errors/middleware.rb create mode 100644 lib/better_errors/lib/better_errors/rails.rb create mode 100644 lib/better_errors/lib/better_errors/raised_exception.rb create mode 100644 lib/better_errors/lib/better_errors/repl.rb create mode 100644 lib/better_errors/lib/better_errors/repl/basic.rb create mode 100644 lib/better_errors/lib/better_errors/repl/pry.rb create mode 100644 lib/better_errors/lib/better_errors/stack_frame.rb create mode 100644 lib/better_errors/lib/better_errors/templates/main.erb create mode 100644 lib/better_errors/lib/better_errors/templates/text.erb create mode 100644 lib/better_errors/lib/better_errors/templates/variable_info.erb create mode 100644 lib/better_errors/lib/better_errors/version.rb create mode 100644 lib/better_errors/spec/better_errors/code_formatter_spec.rb create mode 100644 lib/better_errors/spec/better_errors/error_page_spec.rb create mode 100644 lib/better_errors/spec/better_errors/middleware_spec.rb create mode 100644 lib/better_errors/spec/better_errors/raised_exception_spec.rb create mode 100644 lib/better_errors/spec/better_errors/repl/basic_spec.rb create mode 100644 lib/better_errors/spec/better_errors/repl/pry_spec.rb create mode 100644 lib/better_errors/spec/better_errors/repl/shared_examples.rb create mode 100644 lib/better_errors/spec/better_errors/stack_frame_spec.rb create mode 100644 lib/better_errors/spec/better_errors/support/my_source.rb create mode 100644 lib/better_errors/spec/better_errors_spec.rb create mode 100644 lib/better_errors/spec/spec_helper.rb create mode 100644 lib/better_errors/spec/without_binding_of_caller.rb create mode 100644 lib/rack-mini-profiler/.travis.yml create mode 100644 lib/rack-mini-profiler/CHANGELOG create mode 100644 lib/rack-mini-profiler/Gemfile create mode 100644 lib/rack-mini-profiler/README.md create mode 100644 lib/rack-mini-profiler/Rakefile create mode 100644 lib/rack-mini-profiler/autotest/discover.rb create mode 100644 lib/rack-mini-profiler/lib/html/includes.css create mode 100644 lib/rack-mini-profiler/lib/html/includes.js create mode 100644 lib/rack-mini-profiler/lib/html/includes.less create mode 100644 lib/rack-mini-profiler/lib/html/includes.tmpl create mode 100644 lib/rack-mini-profiler/lib/html/jquery.1.7.1.js create mode 100644 lib/rack-mini-profiler/lib/html/jquery.tmpl.js create mode 100644 lib/rack-mini-profiler/lib/html/list.css create mode 100644 lib/rack-mini-profiler/lib/html/list.js create mode 100644 lib/rack-mini-profiler/lib/html/list.tmpl create mode 100644 lib/rack-mini-profiler/lib/html/profile_handler.js create mode 100644 lib/rack-mini-profiler/lib/html/share.html create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/client_settings.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/client_timer_struct.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/config.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/context.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/custom_timer_struct.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/gc_profiler.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/page_timer_struct.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/profiler.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/profiling_methods.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/request_timer_struct.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/sql_timer_struct.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/storage/abstract_store.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/storage/file_store.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/storage/memcache_store.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/storage/memory_store.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/storage/redis_store.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/timer_struct.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler/version.rb create mode 100644 lib/rack-mini-profiler/lib/mini_profiler_rails/railtie.rb create mode 100644 lib/rack-mini-profiler/lib/patches/net_patches.rb create mode 100644 lib/rack-mini-profiler/lib/patches/sql_patches.rb create mode 100644 lib/rack-mini-profiler/lib/rack-mini-profiler.rb create mode 100644 lib/rack-mini-profiler/rack-mini-profiler.gemspec create mode 100644 lib/rack-mini-profiler/spec/components/client_settings_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/client_timer_struct_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/config_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/file_store_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/gc_profiler_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/memcache_store_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/memory_store_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/page_timer_struct_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/profiler_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/redis_store_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/request_timer_struct_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/sql_timer_struct_spec.rb create mode 100644 lib/rack-mini-profiler/spec/components/timer_struct_spec.rb create mode 100644 lib/rack-mini-profiler/spec/fixtures/simple_client_request.yml create mode 100644 lib/rack-mini-profiler/spec/fixtures/weird_client_request.yml create mode 100644 lib/rack-mini-profiler/spec/integration/mini_profiler_spec.rb create mode 100644 lib/rack-mini-profiler/spec/spec_helper.rb create mode 100644 lib/rack-mini-profiler/spec/support/expand_each_to_matcher.rb create mode 100644 lib/rack-mini-profiler/test_old/config.ru diff --git a/Gemfile b/Gemfile index bfc0b773..a04059bf 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,7 @@ unless RUBY_PLATFORM =~ /w32/ gem 'rubyzip' gem 'zip-zip' end + gem 'seems_rateable', path: 'lib/seems_rateable' gem "rails", "3.2.13" gem "jquery-rails", "~> 2.0.2" @@ -15,6 +16,11 @@ gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby] gem "builder", "3.0.0" gem 'acts-as-taggable-on' +group :development do + gem 'better_errors', path: 'lib/better_errors' + gem 'rack-mini-profiler', path: 'lib/rack-mini-profiler' +end + # Optional gem for LDAP authentication group :ldap do gem "net-ldap", "~> 0.3.1" @@ -70,16 +76,6 @@ else warn("Please configure your config/database.yml first") end -group :development do - gem "rdoc", ">= 2.4.2" - if nil - gem 'thin' - gem 'rack-mini-profiler' - end -end - - - local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") if File.exists?(local_gemfile) puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v` diff --git a/Gemfile.lock b/Gemfile.lock index 54d89f38..25cc9f48 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,16 @@ +PATH + remote: lib/better_errors + specs: + better_errors (1.1.0) + coderay (>= 1.0.0) + erubis (>= 2.6.6) + +PATH + remote: lib/rack-mini-profiler + specs: + rack-mini-profiler (0.9.1) + rack (>= 1.1.3) + PATH remote: lib/seems_rateable specs: @@ -105,6 +118,7 @@ DEPENDENCIES activerecord-jdbc-adapter (= 1.2.5) activerecord-jdbcmysql-adapter acts-as-taggable-on + better_errors! builder (= 3.0.0) coderay (~> 1.0.6) fastercsv (~> 1.5.0) @@ -112,8 +126,8 @@ DEPENDENCIES jquery-rails (~> 2.0.2) mysql2 (~> 0.3.11) net-ldap (~> 0.3.1) + rack-mini-profiler! rack-openid rails (= 3.2.13) - rdoc (>= 2.4.2) ruby-openid (~> 2.1.4) seems_rateable! diff --git a/lib/better_errors/.travis.yml b/lib/better_errors/.travis.yml new file mode 100644 index 00000000..ce51187c --- /dev/null +++ b/lib/better_errors/.travis.yml @@ -0,0 +1,4 @@ +language: ruby +rvm: + - 2.1.0 + - 2.0.0 diff --git a/lib/better_errors/.yardopts b/lib/better_errors/.yardopts new file mode 100644 index 00000000..73034ccf --- /dev/null +++ b/lib/better_errors/.yardopts @@ -0,0 +1 @@ +--markup markdown --no-private diff --git a/lib/better_errors/CHANGELOG.md b/lib/better_errors/CHANGELOG.md new file mode 100644 index 00000000..00fc2466 --- /dev/null +++ b/lib/better_errors/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +See https://github.com/charliesome/better_errors/releases diff --git a/lib/better_errors/Gemfile b/lib/better_errors/Gemfile new file mode 100644 index 00000000..287f7749 --- /dev/null +++ b/lib/better_errors/Gemfile @@ -0,0 +1,10 @@ +source 'https://rubygems.org' + +gemspec + +gem "rake" +gem "rspec", "2.14.1" +gem "binding_of_caller", platforms: :ruby +gem "pry", "0.9.12" +gem "yard" +gem "kramdown" diff --git a/lib/better_errors/LICENSE.txt b/lib/better_errors/LICENSE.txt new file mode 100644 index 00000000..755ce77a --- /dev/null +++ b/lib/better_errors/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2014 Charlie Somerville + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/better_errors/README.md b/lib/better_errors/README.md new file mode 100644 index 00000000..91488695 --- /dev/null +++ b/lib/better_errors/README.md @@ -0,0 +1,103 @@ +# Better Errors [![Gem Version](http://img.shields.io/gem/v/better_errors.svg)](https://rubygems.org/gems/better_errors) [![Build Status](https://travis-ci.org/charliesome/better_errors.svg)](https://travis-ci.org/charliesome/better_errors) [![Code Climate](http://img.shields.io/codeclimate/github/charliesome/better_errors.svg)](https://codeclimate.com/github/charliesome/better_errors) + +Better Errors replaces the standard Rails error page with a much better and more useful error page. It is also usable outside of Rails in any Rack app as Rack middleware. + +![image](http://i.imgur.com/6zBGAAb.png) + +## Features + +* Full stack trace +* Source code inspection for all stack frames (with highlighting) +* Local and instance variable inspection +* Live REPL on every stack frame + +## Installation + +Add this to your Gemfile: + +```ruby +group :development do + gem "better_errors" +end +``` + +If you would like to use Better Errors' **advanced features** (REPL, local/instance variable inspection, pretty stack frame names), you need to add the [`binding_of_caller`](https://github.com/banister/binding_of_caller) gem by [@banisterfiend](http://twitter.com/banisterfiend) to your Gemfile: + +```ruby +gem "binding_of_caller" +``` + +This is an optional dependency however, and Better Errors will work without it. + +_Note: If you discover that Better Errors isn't working - particularly after upgrading from version 0.5.0 or less - be sure to set `config.consider_all_requests_local = true` in `config/environments/development.rb`._ + +## Security + +**NOTE:** It is *critical* you put better\_errors in the **development** section. **Do NOT run better_errors in production, or on Internet facing hosts.** + +You will notice that the only machine that gets the Better Errors page is localhost, which means you get the default error page if you are developing on a remote host (or a virtually remote host, such as a Vagrant box). Obviously, the REPL is not something you want to expose to the public, but there may also be other pieces of sensitive information available in the backtrace. + +To poke selective holes in this security mechanism, you can add a line like this to your startup (for example, on Rails it would be `config/environments/development.rb`) + +```ruby +BetterErrors::Middleware.allow_ip! ENV['TRUSTED_IP'] if ENV['TRUSTED_IP'] +``` + +Then run Rails like this: + +```shell +TRUSTED_IP=66.68.96.220 rails s +``` + +Note that the `allow_ip!` is actually backed by a `Set`, so you can add more than one IP address or subnet. + +**Tip:** You can find your apparent IP by hitting the old error page's "Show env dump" and looking at "REMOTE_ADDR". + +**VirtualBox:** If you are using VirtualBox and are accessing the guest from your host's browser, you will need to use `allow_ip!` to see the error page. + +## Usage + +If you're using Rails, there's nothing else you need to do. + +If you're not using Rails, you need to insert `BetterErrors::Middleware` into your middleware stack, and optionally set `BetterErrors.application_root` if you'd like Better Errors to abbreviate filenames within your application. + +Here's an example using Sinatra: + +```ruby +require "sinatra" +require "better_errors" + +configure :development do + use BetterErrors::Middleware + BetterErrors.application_root = __dir__ +end + +get "/" do + raise "oops" +end +``` + +### Unicorn, Puma, and other multi-worker servers + +Better Errors works by leaving a lot of context in server process memory. If +you're using a web server that runs multiple "workers" it's likely that a second +request (as happens when you click on a stack frame) will hit a different +worker. That worker won't have the necessary context in memory, and you'll see +a `Session Expired` message. + +If this is the case for you, consider turning the number of workers to one (1) +in `development`. Another option would be to use Webrick, Mongrel, Thin, +or another single-process server as your `rails server`, when you are trying +to troubleshoot an issue in development. + +## Get in touch! + +If you're using better_errors, I'd love to hear from you. Drop me a line and tell me what you think! + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request diff --git a/lib/better_errors/Rakefile b/lib/better_errors/Rakefile new file mode 100644 index 00000000..b6329726 --- /dev/null +++ b/lib/better_errors/Rakefile @@ -0,0 +1,13 @@ +require "bundler/gem_tasks" +require "rspec/core/rake_task" + +namespace :test do + RSpec::Core::RakeTask.new(:with_binding_of_caller) + + without_task = RSpec::Core::RakeTask.new(:without_binding_of_caller) + without_task.ruby_opts = "-I spec -r without_binding_of_caller" + + task :all => [:with_binding_of_caller, :without_binding_of_caller] +end + +task :default => "test:all" diff --git a/lib/better_errors/better_errors.gemspec b/lib/better_errors/better_errors.gemspec new file mode 100644 index 00000000..315c110c --- /dev/null +++ b/lib/better_errors/better_errors.gemspec @@ -0,0 +1,27 @@ +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'better_errors/version' + +Gem::Specification.new do |s| + s.name = "better_errors" + s.version = BetterErrors::VERSION + s.authors = ["Charlie Somerville"] + s.email = ["charlie@charliesomerville.com"] + s.description = %q{Provides a better error page for Rails and other Rack apps. Includes source code inspection, a live REPL and local/instance variable inspection for all stack frames.} + s.summary = %q{Better error page for Rails and other Rack apps} + s.homepage = "https://github.com/charliesome/better_errors" + s.license = "MIT" + + s.files = `git ls-files`.split($/) + s.test_files = s.files.grep(%r{^(test|spec|features)/}) + s.require_paths = ["lib"] + + s.required_ruby_version = ">= 2.0.0" + + s.add_dependency "erubis", ">= 2.6.6" + s.add_dependency "coderay", ">= 1.0.0" + + # optional dependencies: + # s.add_dependency "binding_of_caller" + # s.add_dependency "pry" +end diff --git a/lib/better_errors/lib/better_errors.rb b/lib/better_errors/lib/better_errors.rb new file mode 100644 index 00000000..394060ec --- /dev/null +++ b/lib/better_errors/lib/better_errors.rb @@ -0,0 +1,146 @@ +require "pp" +require "erubis" +require "coderay" +require "uri" + +require "better_errors/code_formatter" +require "better_errors/error_page" +require "better_errors/middleware" +require "better_errors/raised_exception" +require "better_errors/repl" +require "better_errors/stack_frame" +require "better_errors/version" + +module BetterErrors + POSSIBLE_EDITOR_PRESETS = [ + { symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" }, + { symbols: [:macvim, :mvim], sniff: /vim/i, url: proc { |file, line| "mvim://open?url=file://#{file}&line=#{line}" } }, + { symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" }, + { symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" }, + ] + + class << self + # The path to the root of the application. Better Errors uses this property + # to determine if a file in a backtrace should be considered an application + # frame. If you are using Better Errors with Rails, you do not need to set + # this attribute manually. + # + # @return [String] + attr_accessor :application_root + + # The logger to use when logging exception details and backtraces. If you + # are using Better Errors with Rails, you do not need to set this attribute + # manually. If this attribute is `nil`, nothing will be logged. + # + # @return [Logger, nil] + attr_accessor :logger + + # @private + attr_accessor :binding_of_caller_available + + # @private + alias_method :binding_of_caller_available?, :binding_of_caller_available + + # The ignored instance variables. + # @return [Array] + attr_accessor :ignored_instance_variables + end + @ignored_instance_variables = [] + + # Returns a proc, which when called with a filename and line number argument, + # returns a URL to open the filename and line in the selected editor. + # + # Generates TextMate URLs by default. + # + # BetterErrors.editor["/some/file", 123] + # # => txmt://open?url=file:///some/file&line=123 + # + # @return [Proc] + def self.editor + @editor + end + + # Configures how Better Errors generates open-in-editor URLs. + # + # @overload BetterErrors.editor=(sym) + # Uses one of the preset editor configurations. Valid symbols are: + # + # * `:textmate`, `:txmt`, `:tm` + # * `:sublime`, `:subl`, `:st` + # * `:macvim` + # + # @param [Symbol] sym + # + # @overload BetterErrors.editor=(str) + # Uses `str` as the format string for generating open-in-editor URLs. + # + # Use `%{file}` and `%{line}` as placeholders for the actual values. + # + # @example + # BetterErrors.editor = "my-editor://open?url=%{file}&line=%{line}" + # + # @param [String] str + # + # @overload BetterErrors.editor=(proc) + # Uses `proc` to generate open-in-editor URLs. The proc will be called + # with `file` and `line` parameters when a URL needs to be generated. + # + # Your proc should take care to escape `file` appropriately with + # `URI.encode_www_form_component` (please note that `URI.escape` is **not** + # a suitable substitute.) + # + # @example + # BetterErrors.editor = proc { |file, line| + # "my-editor://open?url=#{URI.encode_www_form_component file}&line=#{line}" + # } + # + # @param [Proc] proc + # + def self.editor=(editor) + POSSIBLE_EDITOR_PRESETS.each do |config| + if config[:symbols].include?(editor) + return self.editor = config[:url] + end + end + + if editor.is_a? String + self.editor = proc { |file, line| editor % { file: URI.encode_www_form_component(file), line: line } } + else + if editor.respond_to? :call + @editor = editor + else + raise TypeError, "Expected editor to be a valid editor key, a format string or a callable." + end + end + end + + # Enables experimental Pry support in the inline REPL + # + # If you encounter problems while using Pry, *please* file a bug report at + # https://github.com/charliesome/better_errors/issues + def self.use_pry! + REPL::PROVIDERS.unshift const: :Pry, impl: "better_errors/repl/pry" + end + + # Automatically sniffs a default editor preset based on the EDITOR + # environment variable. + # + # @return [Symbol] + def self.default_editor + POSSIBLE_EDITOR_PRESETS.detect(-> { {} }) { |config| + ENV["EDITOR"] =~ config[:sniff] + }[:url] || :textmate + end + + BetterErrors.editor = default_editor +end + +begin + require "binding_of_caller" + require "better_errors/exception_extension" + BetterErrors.binding_of_caller_available = true +rescue LoadError => e + BetterErrors.binding_of_caller_available = false +end + +require "better_errors/rails" if defined? Rails::Railtie diff --git a/lib/better_errors/lib/better_errors/code_formatter.rb b/lib/better_errors/lib/better_errors/code_formatter.rb new file mode 100644 index 00000000..77827241 --- /dev/null +++ b/lib/better_errors/lib/better_errors/code_formatter.rb @@ -0,0 +1,63 @@ +module BetterErrors + # @private + class CodeFormatter + require "better_errors/code_formatter/html" + require "better_errors/code_formatter/text" + + FILE_TYPES = { + ".rb" => :ruby, + "" => :ruby, + ".html" => :html, + ".erb" => :erb, + ".haml" => :haml + } + + attr_reader :filename, :line, :context + + def initialize(filename, line, context = 5) + @filename = filename + @line = line + @context = context + end + + def output + formatted_code + rescue Errno::ENOENT, Errno::EINVAL + source_unavailable + end + + def formatted_code + formatted_lines.join + end + + def coderay_scanner + ext = File.extname(filename) + FILE_TYPES[ext] || :text + end + + def each_line_of(lines, &blk) + line_range.zip(lines).map { |current_line, str| + yield (current_line == line), current_line, str + } + end + + def highlighted_lines + CodeRay.scan(context_lines.join, coderay_scanner).div(wrap: nil).lines + end + + def context_lines + range = line_range + source_lines[(range.begin - 1)..(range.end - 1)] or raise Errno::EINVAL + end + + def source_lines + @source_lines ||= File.readlines(filename) + end + + def line_range + min = [line - context, 1].max + max = [line + context, source_lines.count].min + min..max + end + end +end diff --git a/lib/better_errors/lib/better_errors/code_formatter/html.rb b/lib/better_errors/lib/better_errors/code_formatter/html.rb new file mode 100644 index 00000000..b3ab0543 --- /dev/null +++ b/lib/better_errors/lib/better_errors/code_formatter/html.rb @@ -0,0 +1,26 @@ +module BetterErrors + # @private + class CodeFormatter::HTML < CodeFormatter + def source_unavailable + "

    Source is not available

    " + end + + def formatted_lines + each_line_of(highlighted_lines) { |highlight, current_line, str| + class_name = highlight ? "highlight" : "" + sprintf '
    %s
    ', class_name, str + } + end + + def formatted_nums + each_line_of(highlighted_lines) { |highlight, current_line, str| + class_name = highlight ? "highlight" : "" + sprintf '%5d', class_name, current_line + } + end + + def formatted_code + %{
    #{formatted_nums.join}
    #{super}
    } + end + end +end diff --git a/lib/better_errors/lib/better_errors/code_formatter/text.rb b/lib/better_errors/lib/better_errors/code_formatter/text.rb new file mode 100644 index 00000000..cd63806d --- /dev/null +++ b/lib/better_errors/lib/better_errors/code_formatter/text.rb @@ -0,0 +1,14 @@ +module BetterErrors + # @private + class CodeFormatter::Text < CodeFormatter + def source_unavailable + "# Source is not available" + end + + def formatted_lines + each_line_of(context_lines) { |highlight, current_line, str| + sprintf '%s %3d %s', (highlight ? '>' : ' '), current_line, str + } + end + end +end diff --git a/lib/better_errors/lib/better_errors/error_page.rb b/lib/better_errors/lib/better_errors/error_page.rb new file mode 100644 index 00000000..849020f1 --- /dev/null +++ b/lib/better_errors/lib/better_errors/error_page.rb @@ -0,0 +1,110 @@ +require "cgi" +require "json" +require "securerandom" + +module BetterErrors + # @private + class ErrorPage + def self.template_path(template_name) + File.expand_path("../templates/#{template_name}.erb", __FILE__) + end + + def self.template(template_name) + Erubis::EscapedEruby.new(File.read(template_path(template_name))) + end + + attr_reader :exception, :env, :repls + + def initialize(exception, env) + @exception = RaisedException.new(exception) + @env = env + @start_time = Time.now.to_f + @repls = [] + end + + def id + @id ||= SecureRandom.hex(8) + end + + def render(template_name = "main") + self.class.template(template_name).result binding + end + + def do_variables(opts) + index = opts["index"].to_i + @frame = backtrace_frames[index] + @var_start_time = Time.now.to_f + { html: render("variable_info") } + end + + def do_eval(opts) + index = opts["index"].to_i + code = opts["source"] + + unless binding = backtrace_frames[index].frame_binding + return { error: "REPL unavailable in this stack frame" } + end + + result, prompt, prefilled_input = + (@repls[index] ||= REPL.provider.new(binding)).send_input(code) + + { result: result, + prompt: prompt, + prefilled_input: prefilled_input, + highlighted_input: CodeRay.scan(code, :ruby).div(wrap: nil) } + end + + def backtrace_frames + exception.backtrace + end + + def application_frames + backtrace_frames.select(&:application?) + end + + def first_frame + application_frames.first || backtrace_frames.first + end + + private + def editor_url(frame) + BetterErrors.editor[frame.filename, frame.line] + end + + def rack_session + env['rack.session'] + end + + def rails_params + env['action_dispatch.request.parameters'] + end + + def uri_prefix + env["SCRIPT_NAME"] || "" + end + + def request_path + env["PATH_INFO"] + end + + def html_formatted_code_block(frame) + CodeFormatter::HTML.new(frame.filename, frame.line).output + end + + def text_formatted_code_block(frame) + CodeFormatter::Text.new(frame.filename, frame.line).output + end + + def text_heading(char, str) + str + "\n" + char*str.size + end + + def inspect_value(obj) + CGI.escapeHTML(obj.inspect) + rescue NoMethodError + "(object doesn't support inspect)" + rescue Exception => e + "(exception was raised in inspect)" + end + end +end diff --git a/lib/better_errors/lib/better_errors/exception_extension.rb b/lib/better_errors/lib/better_errors/exception_extension.rb new file mode 100644 index 00000000..e97b052f --- /dev/null +++ b/lib/better_errors/lib/better_errors/exception_extension.rb @@ -0,0 +1,17 @@ +module BetterErrors + module ExceptionExtension + prepend_features Exception + + def set_backtrace(*) + if caller_locations.none? { |loc| loc.path == __FILE__ } + @__better_errors_bindings_stack = binding.callers.drop(1) + end + + super + end + + def __better_errors_bindings_stack + @__better_errors_bindings_stack || [] + end + end +end diff --git a/lib/better_errors/lib/better_errors/middleware.rb b/lib/better_errors/lib/better_errors/middleware.rb new file mode 100644 index 00000000..ce9aaad4 --- /dev/null +++ b/lib/better_errors/lib/better_errors/middleware.rb @@ -0,0 +1,141 @@ +require "json" +require "ipaddr" +require "set" + +module BetterErrors + # Better Errors' error handling middleware. Including this in your middleware + # stack will show a Better Errors error page for exceptions raised below this + # middleware. + # + # If you are using Ruby on Rails, you do not need to manually insert this + # middleware into your middleware stack. + # + # @example Sinatra + # require "better_errors" + # + # if development? + # use BetterErrors::Middleware + # end + # + # @example Rack + # require "better_errors" + # if ENV["RACK_ENV"] == "development" + # use BetterErrors::Middleware + # end + # + class Middleware + # The set of IP addresses that are allowed to access Better Errors. + # + # Set to `{ "127.0.0.1/8", "::1/128" }` by default. + ALLOWED_IPS = Set.new + + # Adds an address to the set of IP addresses allowed to access Better + # Errors. + def self.allow_ip!(addr) + ALLOWED_IPS << IPAddr.new(addr) + end + + allow_ip! "127.0.0.0/8" + allow_ip! "::1/128" rescue nil # windows ruby doesn't have ipv6 support + + # A new instance of BetterErrors::Middleware + # + # @param app The Rack app/middleware to wrap with Better Errors + # @param handler The error handler to use. + def initialize(app, handler = ErrorPage) + @app = app + @handler = handler + end + + # Calls the Better Errors middleware + # + # @param [Hash] env + # @return [Array] + def call(env) + if allow_ip? env + better_errors_call env + else + @app.call env + end + end + + private + + def allow_ip?(env) + # REMOTE_ADDR is not in the rack spec, so some application servers do + # not provide it. + return true unless env["REMOTE_ADDR"] and !env["REMOTE_ADDR"].strip.empty? + ip = IPAddr.new env["REMOTE_ADDR"].split("%").first + ALLOWED_IPS.any? { |subnet| subnet.include? ip } + end + + def better_errors_call(env) + case env["PATH_INFO"] + when %r{/__better_errors/(?.+?)/(?\w+)\z} + internal_call env, $~ + when %r{/__better_errors/?\z} + show_error_page env + else + protected_app_call env + end + end + + def protected_app_call(env) + @app.call env + rescue Exception => ex + @error_page = @handler.new ex, env + log_exception + show_error_page(env, ex) + end + + def show_error_page(env, exception=nil) + type, content = if @error_page + if text?(env) + [ 'plain', @error_page.render('text') ] + else + [ 'html', @error_page.render ] + end + else + [ 'html', no_errors_page ] + end + + status_code = 500 + if defined? ActionDispatch::ExceptionWrapper + status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code + end + + [status_code, { "Content-Type" => "text/#{type}; charset=utf-8" }, [content]] + end + + def text?(env) + env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" || + !env["HTTP_ACCEPT"].to_s.include?('html') + end + + def log_exception + return unless BetterErrors.logger + + message = "\n#{@error_page.exception.type} - #{@error_page.exception.message}:\n" + @error_page.backtrace_frames.each do |frame| + message << " #{frame}\n" + end + + BetterErrors.logger.fatal message + end + + def internal_call(env, opts) + if opts[:id] != @error_page.id + return [200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(error: "Session expired")]] + end + + env["rack.input"].rewind + response = @error_page.send("do_#{opts[:method]}", JSON.parse(env["rack.input"].read)) + [200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(response)]] + end + + def no_errors_page + "

    No errors

    No errors have been recorded yet.


    " + + "Better Errors v#{BetterErrors::VERSION}" + end + end +end diff --git a/lib/better_errors/lib/better_errors/rails.rb b/lib/better_errors/lib/better_errors/rails.rb new file mode 100644 index 00000000..36e386a5 --- /dev/null +++ b/lib/better_errors/lib/better_errors/rails.rb @@ -0,0 +1,28 @@ +module BetterErrors + # @private + class Railtie < Rails::Railtie + initializer "better_errors.configure_rails_initialization" do + if use_better_errors? + insert_middleware + BetterErrors.logger = Rails.logger + BetterErrors.application_root = Rails.root.to_s + end + end + + def insert_middleware + if defined? ActionDispatch::DebugExceptions + app.middleware.insert_after ActionDispatch::DebugExceptions, BetterErrors::Middleware + else + app.middleware.use BetterErrors::Middleware + end + end + + def use_better_errors? + !Rails.env.production? and app.config.consider_all_requests_local + end + + def app + Rails.application + end + end +end diff --git a/lib/better_errors/lib/better_errors/raised_exception.rb b/lib/better_errors/lib/better_errors/raised_exception.rb new file mode 100644 index 00000000..07d0d7b2 --- /dev/null +++ b/lib/better_errors/lib/better_errors/raised_exception.rb @@ -0,0 +1,66 @@ +# @private +module BetterErrors + class RaisedException + attr_reader :exception, :message, :backtrace + + def initialize(exception) + if exception.respond_to?(:original_exception) && exception.original_exception + exception = exception.original_exception + end + + @exception = exception + @message = exception.message + + setup_backtrace + massage_syntax_error + end + + def type + exception.class + end + + private + def has_bindings? + exception.respond_to?(:__better_errors_bindings_stack) && exception.__better_errors_bindings_stack.any? + end + + def setup_backtrace + if has_bindings? + setup_backtrace_from_bindings + else + setup_backtrace_from_backtrace + end + end + + def setup_backtrace_from_bindings + @backtrace = exception.__better_errors_bindings_stack.map { |binding| + file = binding.eval "__FILE__" + line = binding.eval "__LINE__" + name = binding.frame_description + StackFrame.new(file, line, name, binding) + } + end + + def setup_backtrace_from_backtrace + @backtrace = (exception.backtrace || []).map { |frame| + if /\A(?.*?):(?\d+)(:in `(?.*)')?/ =~ frame + StackFrame.new(file, line.to_i, name) + end + }.compact + end + + def massage_syntax_error + case exception.class.to_s + when "Haml::SyntaxError" + if /\A(.+?):(\d+)/ =~ exception.backtrace.first + backtrace.unshift(StackFrame.new($1, $2.to_i, "")) + end + when "SyntaxError" + if /\A(.+?):(\d+): (.*)/m =~ exception.message + backtrace.unshift(StackFrame.new($1, $2.to_i, "")) + @message = $3 + end + end + end + end +end diff --git a/lib/better_errors/lib/better_errors/repl.rb b/lib/better_errors/lib/better_errors/repl.rb new file mode 100644 index 00000000..c0f6463e --- /dev/null +++ b/lib/better_errors/lib/better_errors/repl.rb @@ -0,0 +1,30 @@ +module BetterErrors + # @private + module REPL + PROVIDERS = [ + { impl: "better_errors/repl/basic", + const: :Basic }, + ] + + def self.provider + @provider ||= const_get detect[:const] + end + + def self.provider=(prov) + @provider = prov + end + + def self.detect + PROVIDERS.find { |prov| + test_provider prov + } + end + + def self.test_provider(provider) + require provider[:impl] + true + rescue LoadError + false + end + end +end diff --git a/lib/better_errors/lib/better_errors/repl/basic.rb b/lib/better_errors/lib/better_errors/repl/basic.rb new file mode 100644 index 00000000..7011e448 --- /dev/null +++ b/lib/better_errors/lib/better_errors/repl/basic.rb @@ -0,0 +1,20 @@ +module BetterErrors + module REPL + class Basic + def initialize(binding) + @binding = binding + end + + def send_input(str) + [execute(str), ">>", ""] + end + + private + def execute(str) + "=> #{@binding.eval(str).inspect}\n" + rescue Exception => e + "!! #{e.inspect rescue e.class.to_s rescue "Exception"}\n" + end + end + end +end diff --git a/lib/better_errors/lib/better_errors/repl/pry.rb b/lib/better_errors/lib/better_errors/repl/pry.rb new file mode 100644 index 00000000..412be013 --- /dev/null +++ b/lib/better_errors/lib/better_errors/repl/pry.rb @@ -0,0 +1,78 @@ +require "fiber" +require "pry" + +module BetterErrors + module REPL + class Pry + class Input + def readline + Fiber.yield + end + end + + class Output + def initialize + @buffer = "" + end + + def puts(*args) + args.each do |arg| + @buffer << "#{arg.chomp}\n" + end + end + + def tty? + false + end + + def read_buffer + @buffer + ensure + @buffer = "" + end + end + + def initialize(binding) + @fiber = Fiber.new do + @pry.repl binding + end + @input = Input.new + @output = Output.new + @pry = ::Pry.new input: @input, output: @output + @pry.hooks.clear_all if defined?(@pry.hooks.clear_all) + @fiber.resume + end + + def send_input(str) + local ::Pry.config, color: false, pager: false do + @fiber.resume "#{str}\n" + [@output.read_buffer, *prompt] + end + end + + def prompt + if indent = @pry.instance_variable_get(:@indent) and !indent.indent_level.empty? + ["..", indent.indent_level] + else + [">>", ""] + end + rescue + [">>", ""] + end + + private + def local(obj, attrs) + old_attrs = {} + attrs.each do |k, v| + old_attrs[k] = obj.send k + obj.send "#{k}=", v + end + yield + ensure + old_attrs.each do |k, v| + obj.send "#{k}=", v + end + end + end + end +end diff --git a/lib/better_errors/lib/better_errors/stack_frame.rb b/lib/better_errors/lib/better_errors/stack_frame.rb new file mode 100644 index 00000000..4130425f --- /dev/null +++ b/lib/better_errors/lib/better_errors/stack_frame.rb @@ -0,0 +1,111 @@ +require "set" + +module BetterErrors + # @private + class StackFrame + def self.from_exception(exception) + RaisedException.new(exception).backtrace + end + + attr_reader :filename, :line, :name, :frame_binding + + def initialize(filename, line, name, frame_binding = nil) + @filename = filename + @line = line + @name = name + @frame_binding = frame_binding + + set_pretty_method_name if frame_binding + end + + def application? + if root = BetterErrors.application_root + filename.index(root) == 0 && filename.index("#{root}/vendor") != 0 + end + end + + def application_path + filename[(BetterErrors.application_root.length+1)..-1] + end + + def gem? + Gem.path.any? { |path| filename.index(path) == 0 } + end + + def gem_path + if path = Gem.path.detect { |path| filename.index(path) == 0 } + gem_name_and_version, path = filename.sub("#{path}/gems/", "").split("/", 2) + /(?.+)-(?[\w.]+)/ =~ gem_name_and_version + "#{gem_name} (#{gem_version}) #{path}" + end + end + + def class_name + @class_name + end + + def method_name + @method_name || @name + end + + def context + if gem? + :gem + elsif application? + :application + else + :dunno + end + end + + def pretty_path + case context + when :application; application_path + when :gem; gem_path + else filename + end + end + + def local_variables + return {} unless frame_binding + frame_binding.eval("local_variables").each_with_object({}) do |name, hash| + if defined?(frame_binding.local_variable_get) + hash[name] = frame_binding.local_variable_get(name) + else + hash[name] = frame_binding.eval(name.to_s) + end + end + end + + def instance_variables + return {} unless frame_binding + Hash[visible_instance_variables.map { |x| + [x, frame_binding.eval(x.to_s)] + }] + end + + def visible_instance_variables + frame_binding.eval("instance_variables") - BetterErrors.ignored_instance_variables + end + + def to_s + "#{pretty_path}:#{line}:in `#{name}'" + end + + private + def set_pretty_method_name + name =~ /\A(block (\([^)]+\) )?in )?/ + recv = frame_binding.eval("self") + + return unless method_name = frame_binding.eval("::Kernel.__method__") + + if Module === recv + @class_name = "#{$1}#{recv}" + @method_name = ".#{method_name}" + else + @class_name = "#{$1}#{Kernel.instance_method(:class).bind(recv).call}" + @method_name = "##{method_name}" + end + end + end +end diff --git a/lib/better_errors/lib/better_errors/templates/main.erb b/lib/better_errors/lib/better_errors/templates/main.erb new file mode 100644 index 00000000..7c3ff9fe --- /dev/null +++ b/lib/better_errors/lib/better_errors/templates/main.erb @@ -0,0 +1,1031 @@ + + + + <%= exception.type %> at <%= request_path %> + + + <%# Stylesheets are placed in the for Turbolinks compatibility. %> + + + <%# IE8 compatibility crap %> + + + <%# + If Rails's Turbolinks is used, the Better Errors page is probably + rendered in the host app's layout. Let's empty out the styles of the + host app. + %> + + +
    +
    +

    <%= exception.type %> at <%= request_path %>

    +

    <%= exception.message %>

    +
    +
    + +
    + + + <% backtrace_frames.each_with_index do |frame, index| %> + + <% end %> +
    + + + + + diff --git a/lib/better_errors/lib/better_errors/templates/text.erb b/lib/better_errors/lib/better_errors/templates/text.erb new file mode 100644 index 00000000..fe9068bb --- /dev/null +++ b/lib/better_errors/lib/better_errors/templates/text.erb @@ -0,0 +1,21 @@ +<%== text_heading("=", "%s at %s" % [exception.type, request_path]) %> + +> <%== exception.message %> +<% if backtrace_frames.any? %> + +<%== text_heading("-", "%s, line %i" % [first_frame.pretty_path, first_frame.line]) %> + +``` ruby +<%== text_formatted_code_block(first_frame) %>``` + +App backtrace +------------- + +<%== application_frames.map { |s| " - #{s}" }.join("\n") %> + +Full backtrace +-------------- + +<%== backtrace_frames.map { |s| " - #{s}" }.join("\n") %> + +<% end %> diff --git a/lib/better_errors/lib/better_errors/templates/variable_info.erb b/lib/better_errors/lib/better_errors/templates/variable_info.erb new file mode 100644 index 00000000..bde5daab --- /dev/null +++ b/lib/better_errors/lib/better_errors/templates/variable_info.erb @@ -0,0 +1,70 @@ +
    +
    +

    <%= @frame.name %>

    + +
    +
    + <%== html_formatted_code_block @frame %> +
    + + <% if BetterErrors.binding_of_caller_available? && @frame.frame_binding %> +
    +
    +
    
    +                
    >>
    +
    +
    + <% end %> +
    + +<% if BetterErrors.binding_of_caller_available? && @frame.frame_binding %> +
    + This is a live shell. Type in here. +
    + +
    +<% end %> + +<% unless BetterErrors.binding_of_caller_available? %> +
    + Tip: add gem "binding_of_caller" to your Gemfile to enable the REPL and local/instance variable inspection. +
    +<% end %> + +
    +

    Request info

    +
    +
    - <% if display_id %> + <% if is_student %> <%= link_to "互评>>" , homework_attach_path(homework)%> + <% else %> + <% if is_teacher %> + <%= link_to "综评>>" , homework_attach_path(homework)%> + <% end %> <% end %>
      - <% if display_id %> + <% if is_cur_course_user? @bid %> <%= l(:label_bidding_user_studentcode) %>  : <%= homework.user.user_extensions.student_id%> <% end %>
    + <% if rails_params %> + + <% end %> + <% if rack_session %> + + <% end %> +
    Request parameters
    <%== inspect_value rails_params %>
    Rack session
    <%== inspect_value rack_session %>
    + + + +
    +

    Local Variables

    +
    + + <% @frame.local_variables.each do |name, value| %> + + <% end %> +
    <%= name %>
    <%== inspect_value value %>
    +
    +
    + +
    +

    Instance Variables

    +
    + + <% @frame.instance_variables.each do |name, value| %> + + <% end %> +
    <%= name %>
    <%== inspect_value value %>
    +
    +
    + + diff --git a/lib/better_errors/lib/better_errors/version.rb b/lib/better_errors/lib/better_errors/version.rb new file mode 100644 index 00000000..81769ce8 --- /dev/null +++ b/lib/better_errors/lib/better_errors/version.rb @@ -0,0 +1,3 @@ +module BetterErrors + VERSION = "1.1.0" +end diff --git a/lib/better_errors/spec/better_errors/code_formatter_spec.rb b/lib/better_errors/spec/better_errors/code_formatter_spec.rb new file mode 100644 index 00000000..c084e2f9 --- /dev/null +++ b/lib/better_errors/spec/better_errors/code_formatter_spec.rb @@ -0,0 +1,92 @@ +require "spec_helper" + +module BetterErrors + describe CodeFormatter do + let(:filename) { File.expand_path("../support/my_source.rb", __FILE__) } + + let(:formatter) { CodeFormatter.new(filename, 8) } + + it "picks an appropriate scanner" do + formatter.coderay_scanner.should == :ruby + end + + it "shows 5 lines of context" do + formatter.line_range.should == (3..13) + + formatter.context_lines.should == [ + "three\n", + "four\n", + "five\n", + "six\n", + "seven\n", + "eight\n", + "nine\n", + "ten\n", + "eleven\n", + "twelve\n", + "thirteen\n" + ] + end + + it "works when the line is right on the edge" do + formatter = CodeFormatter.new(filename, 20) + formatter.line_range.should == (15..20) + end + + describe CodeFormatter::HTML do + it "highlights the erroring line" do + formatter = CodeFormatter::HTML.new(filename, 8) + formatter.output.should =~ /highlight.*eight/ + end + + it "works when the line is right on the edge" do + formatter = CodeFormatter::HTML.new(filename, 20) + formatter.output.should_not == formatter.source_unavailable + end + + it "doesn't barf when the lines don't make any sense" do + formatter = CodeFormatter::HTML.new(filename, 999) + formatter.output.should == formatter.source_unavailable + end + + it "doesn't barf when the file doesn't exist" do + formatter = CodeFormatter::HTML.new("fkdguhskd7e l", 1) + formatter.output.should == formatter.source_unavailable + end + end + + describe CodeFormatter::Text do + it "highlights the erroring line" do + formatter = CodeFormatter::Text.new(filename, 8) + formatter.output.should == <<-TEXT.gsub(/^ /, "") + 3 three + 4 four + 5 five + 6 six + 7 seven + > 8 eight + 9 nine + 10 ten + 11 eleven + 12 twelve + 13 thirteen + TEXT + end + + it "works when the line is right on the edge" do + formatter = CodeFormatter::Text.new(filename, 20) + formatter.output.should_not == formatter.source_unavailable + end + + it "doesn't barf when the lines don't make any sense" do + formatter = CodeFormatter::Text.new(filename, 999) + formatter.output.should == formatter.source_unavailable + end + + it "doesn't barf when the file doesn't exist" do + formatter = CodeFormatter::Text.new("fkdguhskd7e l", 1) + formatter.output.should == formatter.source_unavailable + end + end + end +end diff --git a/lib/better_errors/spec/better_errors/error_page_spec.rb b/lib/better_errors/spec/better_errors/error_page_spec.rb new file mode 100644 index 00000000..daea57be --- /dev/null +++ b/lib/better_errors/spec/better_errors/error_page_spec.rb @@ -0,0 +1,76 @@ +require "spec_helper" + +module BetterErrors + describe ErrorPage do + let!(:exception) { raise ZeroDivisionError, "you divided by zero you silly goose!" rescue $! } + + let(:error_page) { ErrorPage.new exception, { "PATH_INFO" => "/some/path" } } + + let(:response) { error_page.render } + + let(:empty_binding) { + local_a = :value_for_local_a + local_b = :value_for_local_b + + @inst_c = :value_for_inst_c + @inst_d = :value_for_inst_d + + binding + } + + it "includes the error message" do + response.should include("you divided by zero you silly goose!") + end + + it "includes the request path" do + response.should include("/some/path") + end + + it "includes the exception class" do + response.should include("ZeroDivisionError") + end + + context "variable inspection" do + let(:exception) { empty_binding.eval("raise") rescue $! } + + if BetterErrors.binding_of_caller_available? + it "shows local variables" do + html = error_page.do_variables("index" => 0)[:html] + html.should include("local_a") + html.should include(":value_for_local_a") + html.should include("local_b") + html.should include(":value_for_local_b") + end + else + it "tells the user to add binding_of_caller to their gemfile to get fancy features" do + html = error_page.do_variables("index" => 0)[:html] + html.should include(%{gem "binding_of_caller"}) + end + end + + it "shows instance variables" do + html = error_page.do_variables("index" => 0)[:html] + html.should include("inst_c") + html.should include(":value_for_inst_c") + html.should include("inst_d") + html.should include(":value_for_inst_d") + end + + it "shows filter instance variables" do + BetterErrors.stub(:ignored_instance_variables).and_return([ :@inst_d ]) + html = error_page.do_variables("index" => 0)[:html] + html.should include("inst_c") + html.should include(":value_for_inst_c") + html.should_not include('
    @inst_d
    diff --git a/app/views/tags/_tagEx.html.erb b/app/views/tags/_tagEx.html.erb index 08800caa..3e7cd9c6 100644 --- a/app/views/tags/_tagEx.html.erb +++ b/app/views/tags/_tagEx.html.erb @@ -1,13 +1,3 @@ -
    <%#begin @@ -60,11 +50,7 @@ <%= text_field "tag_name" ,"name"%> - <%= button_tag "增加", :type=>"button", :onclick=>"tagAddClick(tags_show-"+obj.class.to_s + "-" +obj.id.to_s + ","+ obj.id.to_s + "," + object_flag.to_s + ")" %> - <%#= f.text_field :object_id,:value=> obj.id,:style=>"display:none"%> - <%#= f.text_field :object_flag,:value=> object_flag,:style=>"display:none"%> - - <%= submit_tag "增加", :name=>"add_tag" %> + <%= submit_tag l(:button_add), :name=>"add_tag",:remote=>"false", :format => 'js' %>
    - - + + diff --git a/app/views/account/register.html.erb b/app/views/account/register.html.erb index f24f7d05..cc7a26ad 100644 --- a/app/views/account/register.html.erb +++ b/app/views/account/register.html.erb @@ -279,7 +279,7 @@ <% if @user.auth_source_id.nil? %>

    <%= f.text_field :login, :size => 25, :required => true %> - <%= l(:label_max_number) %>

    + <%= l(:label_max_number) %>

    <%= f.password_field :password, :size => 25, :required => true %> <%= l(:text_caracters_minimum, :count => Setting.password_min_length) %>

    @@ -302,10 +302,16 @@
    <%= text_field_tag 'username', params[:username], :tabindex => '1' %> + <%= text_field_tag 'username', params[:username], :tabindex => '1' , :value => "#{l(:label_login_prompt)}", + :onfocus => "clearInfo('username','#{l(:label_login_prompt)}')", + :onblur => "showInfo('username','#{l(:label_login_prompt)}')", + :style => "resize: none;font-size: 12px;color: #818283;"%> +
    -
    <%= l(:label_gender) %>   - <%= select_tag 'gender', " - ".html_safe %>

    +

    + + + +
    <%= l(:label_gender) %>   + <%= select_tag 'gender', " + ".html_safe %> +
    +

    +

    diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index 0418697c..c9390bcc 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -57,6 +57,10 @@
    diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index dfe8a10b..173fc26a 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -18,7 +18,7 @@
    <%= l(:label_location) %> *
      - 发布人:  <%= link_to (display_id ? homework.user.realname : homework.user ), user_path(homework.user)%> + 发布人:  <%= link_to ( is_teacher ? homework.user.realname : homework.user ), user_path(homework.user)%> 作业评分: @@ -71,7 +71,7 @@
      - <% if display_id %> + <% if display_id || is_teacher %> <%= l(:label_bidding_user_studentcode) %>  : <%= homework.user.user_extensions.student_id%> <% end %> <%= h(e.project) if @project.nil? || @project.id != e.project.id %> - <%= link_to_user(e.event_author,@canShowRealName) if e.respond_to?(:event_author) %> + <%= link_to_user(e.event_author) if e.respond_to?(:event_author) %>(<%= link_to_user(e.event_author,@canShowRealName) if e.respond_to?(:event_author) %>) <%= l(:label_new_activity) %> <%= link_to "#{eventToLanguageCourse(e.event_type, @project)}: "<< format_activity_title(e.event_title), (e.event_type.eql?("attachment")&&e.container.kind_of?(Project)) ? project_files_path(e.container) : e.event_url %> @@ -86,7 +86,7 @@ #判断是否显示真名 if @canShowRealName %> - <%= link_to (h @user.try(:realname)), user_path(@user) if @user %> + <%= link_to (h @user.try(:name)), user_path(@user) if @user %>(<%= link_to (h @user.try(:realname)), user_path(@user) if @user %>) <% else %> <%= link_to (h @user.try(:name)), user_path(@user) if @user %> <% end %> From 3546355f70f17a07120f9161046a2176ed85b80d Mon Sep 17 00:00:00 2001 From: sw <939547590@qq.com> Date: Wed, 28 May 2014 17:07:23 +0800 Subject: [PATCH 16/22] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=98=B5=E7=A7=B0=E5=8A=9F=E8=83=BD=E4=BB=A5=E5=8F=8A=E6=98=B5?= =?UTF-8?q?=E7=A7=B0=E6=98=AF=E5=90=A6=E9=87=8D=E5=A4=8D=E7=9A=84=E5=88=A4?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/my_controller.rb | 1 + app/views/my/account.html.erb | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/controllers/my_controller.rb b/app/controllers/my_controller.rb index 0723cf16..a87df4fe 100644 --- a/app/controllers/my_controller.rb +++ b/app/controllers/my_controller.rb @@ -54,6 +54,7 @@ class MyController < ApplicationController @user.safe_attributes = params[:user] @user.pref.attributes = params[:pref] @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') + @user.login = params[:login] unless @user.user_extensions.nil? if @user.user_extensions.identity == 2 @user.firstname = params[:enterprise_name] diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index c9390bcc..80554565 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -55,20 +55,25 @@ <%= l(:label_information_plural) %> + + +

    + <%= f.text_field :login, :required => true, :name => "login"%> + <%= l(:label_max_number) %> +
    +

    +
    +

    - + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    <%= l(:label_new_course_description) %> :
    <%= project.description %>
    <%= l(:label_create_time) %> :<%= format_time course.created_at %>
    主讲老师 :<%= link_to(@user.lastname+@user.firstname, user_path(@user)) %>
    <%= l(:label_class_period) %> :<%= course.class_period.to_s %> <%= l(:label_class_hour) %>
    <%= l(:label_main_term) %> :<%= course.time.to_s %> <%= course.term %>
    <%= l(:label_teacher_work_unit) %> :<%= @user.user_extensions.occupation %>
    +
    +
    +
    + From 97ae2356bfb47cf44e9c67291ebebd6cb6535b9b Mon Sep 17 00:00:00 2001 From: z9hang Date: Wed, 28 May 2014 20:09:49 +0800 Subject: [PATCH 19/22] =?UTF-8?q?=E5=BC=95=E7=94=A8=E8=A2=AB=E9=81=AE?= =?UTF-8?q?=E4=BD=8F=E4=B8=8D=E8=83=BD=E7=82=B9=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/issues/show.html.erb | 6 ++++-- public/themes/redpenny-master/stylesheets/application.css | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/issues/show.html.erb b/app/views/issues/show.html.erb index 6a9c4b6c..d261957c 100644 --- a/app/views/issues/show.html.erb +++ b/app/views/issues/show.html.erb @@ -26,7 +26,7 @@ <% end %> - +
    <%= render_issue_subject_with_tree(@issue) %>
    @@ -35,7 +35,7 @@ <%= render :partial => "/praise_tread/praise_tread",:locals => {:obj => @issue,:show_flag => true,:user_id =>User.current.id}%> - +

    <%= authoring @issue.created_on, @issue.author %>. <% if @issue.created_on != @issue.updated_on %> @@ -107,10 +107,12 @@ end %> <% if false # !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>


    +
    <%= link_to_new_subtask(@issue) if User.current.allowed_to?(:manage_subtasks, @project) %>
    +

    <%=l(:label_subtask_plural)%>

    <%= render_descendants_tree(@issue) unless @issue.leaf? %>
    diff --git a/public/themes/redpenny-master/stylesheets/application.css b/public/themes/redpenny-master/stylesheets/application.css index 7de766ce..20935830 100644 --- a/public/themes/redpenny-master/stylesheets/application.css +++ b/public/themes/redpenny-master/stylesheets/application.css @@ -968,7 +968,7 @@ hr p { font-size: 13px; - position:relative;/*gcm*/ + /*position:relative;/*gcm*/ } /*end*/ div.issue From 006e4ae45e731ef3113234df6f4443ebb067b159 Mon Sep 17 00:00:00 2001 From: yanxd Date: Thu, 29 May 2014 18:34:34 +0800 Subject: [PATCH 20/22] user tab bar --- app/helpers/users_helper.rb | 44 ++++++++++--------- .../users/_course_list_have_entity.html.erb | 2 +- app/views/users/_my_joinedcourse.html.erb | 2 +- app/views/users/show.html.erb | 26 ++++++++++- app/views/users/user_projects.html.erb | 17 ++++++- app/views/users/watch_projects.html.erb | 22 +++++++++- public/stylesheets/nyan.css | 18 ++++---- 7 files changed, 93 insertions(+), 38 deletions(-) diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index f412e0c8..87b495f0 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -69,28 +69,30 @@ module UsersHelper #
  • <%= link_to("所有反馈", {:controller => 'users', :action => 'show', :type => 2}) %>
  • # - def show_activity(state) - content = ''.html_safe - case state - when 0 - s = content_tag('span', l(:label_user_all_activity), :class => "current-page") - content << content_tag('li', s) - content << content_tag('li', link_to(l(:label_user_activity_myself), {:controller => 'users', :action => 'show', :type => 1})) - content << content_tag('li', link_to(l(:label_user_all_respond), {:controller => 'users', :action => 'show', :type => 2})) - when 1 - s = content_tag('span', l(:label_user_activity_myself), :class => "current-page") - content << content_tag('li', link_to(l(:label_user_all_activity), {:controller => 'users', :action => 'show'})) - content << content_tag('li', s, :class => "current-page") - content << content_tag('li', link_to(l(:label_user_all_respond), {:controller => 'users', :action => 'show', :type => 2})) - when 2 - s = content_tag('span', l(:label_user_all_respond), :class => "current-page") - content << content_tag('li', link_to(l(:label_user_all_activity), {:controller => 'users', :action => 'show'})) - content << content_tag('li', link_to(l(:label_user_activity_myself), {:controller => 'users', :action => 'show', :type => 1})) - content << content_tag('li', s, :class => "current-page") - end - content_tag('div', content, :class => "pagination") - end + # TODO: 待删 + # def show_activity(state) + # content = ''.html_safe + # case state + # when 0 + # s = content_tag('span', l(:label_user_all_activity), :class => "current-page") + # content << content_tag('li', s) + # content << content_tag('li', link_to(l(:label_user_activity_myself), {:controller => 'users', :action => 'show', :type => 1})) + # content << content_tag('li', link_to(l(:label_user_all_respond), {:controller => 'users', :action => 'show', :type => 2})) + # when 1 + # s = content_tag('span', l(:label_user_activity_myself), :class => "current-page") + # content << content_tag('li', link_to(l(:label_user_all_activity), {:controller => 'users', :action => 'show'})) + # content << content_tag('li', s, :class => "current-page") + # content << content_tag('li', link_to(l(:label_user_all_respond), {:controller => 'users', :action => 'show', :type => 2})) + # when 2 + # s = content_tag('span', l(:label_user_all_respond), :class => "current-page") + # content << content_tag('li', link_to(l(:label_user_all_activity), {:controller => 'users', :action => 'show'})) + # content << content_tag('li', link_to(l(:label_user_activity_myself), {:controller => 'users', :action => 'show', :type => 1})) + # content << content_tag('li', s, :class => "current-page") + # end + # content_tag('div', content, :class => "pagination") + # end + #TODO: 待删 def watch_projects(state) content = ''.html_safe case state diff --git a/app/views/users/_course_list_have_entity.html.erb b/app/views/users/_course_list_have_entity.html.erb index 87dd5263..19fb88a6 100644 --- a/app/views/users/_course_list_have_entity.html.erb +++ b/app/views/users/_course_list_have_entity.html.erb @@ -1,4 +1,4 @@ -
    +
    <%= l(:label_limit_time) %>: <%= @bid.deadline %>
    @@ -281,6 +300,9 @@ <% end %> \ No newline at end of file diff --git a/app/views/users/user_projects.html.erb b/app/views/users/user_projects.html.erb index ffb42033..e16700cd 100644 --- a/app/views/users/user_projects.html.erb +++ b/app/views/users/user_projects.html.erb @@ -1,7 +1,18 @@ -<%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new', :course => 0, :project_type => 0}, :class => 'icon icon-add') if(User.current.allowed_to?(:add_project, nil, :global => true) && @user == User.current)%> + -<%= watch_projects @state %> + +
    + +<%#= watch_projects @state %> <% unless @memberships.empty? %>
      @@ -48,5 +59,7 @@ $(document).ready(function($) { $("#content .tabs_new~").find("a").attr("target", "_blank"); $("#content .tabs_new~ .pagination").find("a").removeAttr("target"); + $('[mode=take]').click(function(event) {window.location.href='<%=user_projects_user_url%>'; }); + $('[mode=watched]').click(function(event) {window.location.href='<%=watch_projects_user_url(type: 1)%>'; }); }); diff --git a/app/views/users/watch_projects.html.erb b/app/views/users/watch_projects.html.erb index 42047d4e..bb668242 100644 --- a/app/views/users/watch_projects.html.erb +++ b/app/views/users/watch_projects.html.erb @@ -1,6 +1,17 @@ -<%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new', :course => 0, :project_type => 0}, :class => 'icon icon-add') if(User.current.allowed_to?(:add_project, nil, :global => true) && @user == User.current)%> + -<%= watch_projects @state%> + + + +<%#= watch_projects @state%> <% if @watch_projects.count > 0 %> <% for watch_project in @watch_projects %>
    @@ -30,3 +41,10 @@ <% else %> <%= l(:label_watch_no_projects)%> <% end %> + diff --git a/public/stylesheets/nyan.css b/public/stylesheets/nyan.css index 458f3dee..dfcf890f 100644 --- a/public/stylesheets/nyan.css +++ b/public/stylesheets/nyan.css @@ -1205,14 +1205,14 @@ div.pagination { /* user_courses *******************************************************************************/ -.user_course_list { +.menu-div { margin: 0; padding: 0; position: relative; margin-top: -15px; } -.user_course_list .menu{ +.menu-div .menu{ display: block; background-color: #f9f9f9; border-radius: 2px 2px 0 0; @@ -1225,7 +1225,7 @@ div.pagination { padding-left: 15px; } -.user_course_list .menu:after { +.menu-div .menu:after { content: "."; visibility: hidden; display: block; @@ -1233,16 +1233,16 @@ div.pagination { clear: both; } -.user_course_list .menu ul { +.menu-div .menu ul { margin: 0; padding: 0; float: right; margin-right: 30px; } -.user_course_list .menu ul { +.menu-div .menu ul { } -.user_course_list .menu li { +.menu-div .menu li { display: inline-block; position: relative; height: 40px; @@ -1250,17 +1250,17 @@ div.pagination { cursor: pointer; } -.user_course_list .menu li:hover { +.menu-div .menu li:hover { color: #00a1d6; } -.user_course_list .menu li.on { +.menu-div .menu li.on { color: #00a1d6; font-weight: bold; } -.user_course_list .list_top { +.menu-div .list_top { margin: 20px auto 0px; } From 9194b32060310813e2a5c96a2f813a6b26ab7470 Mon Sep 17 00:00:00 2001 From: yanxd Date: Fri, 30 May 2014 09:31:04 +0800 Subject: [PATCH 21/22] =?UTF-8?q?file/index=E4=B8=8A=E4=BC=A0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=8A=9F=E8=83=BD=E9=87=8C=E3=80=82=20remote:=20true?= =?UTF-8?q?=20=E6=96=87=E4=BB=B6=E6=98=AF=E4=B8=8D=E8=83=BD=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E7=9A=84=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/files/_new.html.erb | 2 +- app/views/files/index.html.erb | 27 +++++++++++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app/views/files/_new.html.erb b/app/views/files/_new.html.erb index 7dcf82e1..a1cfb37e 100644 --- a/app/views/files/_new.html.erb +++ b/app/views/files/_new.html.erb @@ -2,7 +2,7 @@ <% versions = project.versions.sort %> <% attachmenttypes = project.attachmenttypes %> <%= error_messages_for 'attachment' %> -<%= form_tag(project_files_path(project), :multipart => true,:remote => true,:method => :post,:name=>"upload_form", :class => "tabular") do %> +<%= form_tag(project_files_path(project), :multipart => true,:remote => false,:method => :post,:name=>"upload_form", :class => "tabular") do %>

    diff --git a/app/views/files/index.html.erb b/app/views/files/index.html.erb index 231be8be..9fc430e3 100644 --- a/app/views/files/index.html.erb +++ b/app/views/files/index.html.erb @@ -25,9 +25,9 @@ :onchange => "attachment_contenttypes_searchex(this.value)" %> <% end %> - +