From 797ed5081df55d0fcaa584384d757eae561abf98 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Tue, 30 Jun 2015 22:12:34 +0800 Subject: [PATCH 001/169] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 1 + app/controllers/repositories_controller.rb | 8 ++++++++ app/views/projects/_development_group.html.erb | 2 +- config/initializers/gitlab_config.rb | 7 +++++++ lib/tasks/gitlab.rake | 11 +++++++++++ 5 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 config/initializers/gitlab_config.rb create mode 100644 lib/tasks/gitlab.rake diff --git a/Gemfile b/Gemfile index 660a7ff49..5a26ed6e0 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,7 @@ unless RUBY_PLATFORM =~ /w32/ gem 'iconv' end +gem 'gitlab' gem 'rest-client' gem "mysql2", "= 0.3.18" gem 'redis-rails' diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 607c9b5db..e80fc4a9a 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -250,6 +250,14 @@ update return -1 end end + + if params[:to] == 'gitlab' + g = Gitlab.client + g.post('/session', body: {email: User.current.mail, password: User.current.hashed_password}) + redirect_to "http://192.168.41.130:3000/gitlab-org/gitlab-shell/tree/master" + return + end + #if( !User.current.member_of?(@project) || @project.hidden_repo) @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? diff --git a/app/views/projects/_development_group.html.erb b/app/views/projects/_development_group.html.erb index d4bc7012a..20a9976e5 100644 --- a/app/views/projects/_development_group.html.erb +++ b/app/views/projects/_development_group.html.erb @@ -41,7 +41,7 @@ <%# --版本库被设置成私有、module中设置不显示、没有创建版本库 三种情况不显示-- %> <% if visible_repository?(@project) %> <% end %> diff --git a/config/initializers/gitlab_config.rb b/config/initializers/gitlab_config.rb new file mode 100644 index 000000000..ddd1efec8 --- /dev/null +++ b/config/initializers/gitlab_config.rb @@ -0,0 +1,7 @@ +Gitlab.configure do |config| + config.endpoint = 'http://192.168.41.130:3000/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] + config.private_token = 'cK15gUDwvt8EEkzwQ_63' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN'] + # Optional + # config.user_agent = 'Custom User Agent' # user agent, default: 'Gitlab Ruby Gem [version]' + # config.sudo = 'user' # username for sudo mode, default: nil +end diff --git a/lib/tasks/gitlab.rake b/lib/tasks/gitlab.rake new file mode 100644 index 000000000..b0b64ff48 --- /dev/null +++ b/lib/tasks/gitlab.rake @@ -0,0 +1,11 @@ +namespace :gitlab do + desc "sync users to gitlab" + task :sync => :environment do + User.where(login: 'guange').find_each do |user| + g = Gitlab.client + unless g.get("/users?search=#{user.mail}") + g.create_user(user.mail, user.hashed_password, name: user.show_name, username: user.login) + end + end + end +end From 3614c6ee9b9980c27e845b616ab15c9b803a06fe Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sun, 12 Jul 2015 15:56:15 +0800 Subject: [PATCH 002/169] =?UTF-8?q?project=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/project.rb | 6 ++ ...50712063406_add_gitlab_user_id_to_users.rb | 5 ++ db/schema.rb | 3 +- lib/tasks/gitlab.rake | 76 +++++++++++++++++-- 4 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20150712063406_add_gitlab_user_id_to_users.rb diff --git a/app/models/project.rb b/app/models/project.rb index 49ed2d0ee..49d46a93a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -848,6 +848,10 @@ class Project < ActiveRecord::Base end end + def owner + User.find(self.user_id) + end + private def after_parent_changed(parent_was) @@ -1162,5 +1166,7 @@ class Project < ActiveRecord::Base :forge_act_id => self.id,:forge_act_type => "ProjectCreateInfo") fa.save! end + + end diff --git a/db/migrate/20150712063406_add_gitlab_user_id_to_users.rb b/db/migrate/20150712063406_add_gitlab_user_id_to_users.rb new file mode 100644 index 000000000..f6e2d60cd --- /dev/null +++ b/db/migrate/20150712063406_add_gitlab_user_id_to_users.rb @@ -0,0 +1,5 @@ +class AddGitlabUserIdToUsers < ActiveRecord::Migration + def change + add_column :users, :gid, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 420432355..bc2f2abfe 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20150619060110) do +ActiveRecord::Schema.define(:version => 20150712063406) do create_table "activities", :force => true do |t| t.integer "act_id", :null => false @@ -1429,6 +1429,7 @@ ActiveRecord::Schema.define(:version => 20150619060110) do t.string "identity_url" t.string "mail_notification", :default => "", :null => false t.string "salt", :limit => 64 + t.integer "gid" end add_index "users", ["auth_source_id"], :name => "index_users_on_auth_source_id" diff --git a/lib/tasks/gitlab.rake b/lib/tasks/gitlab.rake index b0b64ff48..2d53eaa0a 100644 --- a/lib/tasks/gitlab.rake +++ b/lib/tasks/gitlab.rake @@ -1,11 +1,75 @@ namespace :gitlab do - desc "sync users to gitlab" - task :sync => :environment do - User.where(login: 'guange').find_each do |user| - g = Gitlab.client - unless g.get("/users?search=#{user.mail}") - g.create_user(user.mail, user.hashed_password, name: user.show_name, username: user.login) + namespace :sync do + + module Helper + def self.change_password(uid, en_pwd, salt) + g = Gitlab.client + g.edit_user(uid, :encrypted_password=>en_pwd, :password_salt=>salt) end end + + + desc "sync users to gitlab" + task :users => :environment do + # User.where(username: 'root').find_each do |user| + User.where(login: 'guange1').find_each do |user| + begin + g = Gitlab.client + u = g.get("/users?search=#{user.mail}").first + unless u + u = g.create_user(user.mail, user.hashed_password, name: user.show_name, username: user.login) + user.gid = u.id + user.save! + puts "create user #{user.login}" + end + Helper.change_password(u.id, user.hashed_password, user.salt) + rescue => e + puts e + end + end + end + + desc "update user password" + task :password => :environment do + Helper.change_password(1,'5188b7a65acf294ee7deceb397b6f9c62214ea50','dcb8d9fffabec60c2d0d1030b679fbbb') + end + + + desc "sync projects to gitlab" + task :projects => :environment do + Project.where(id: 505).find_each do |project| + g = Gitlab.client + gid = project.owner.gid + raise "unknow gid" unless gid + path = project.repositories.where(:is_default => true).first.root_url.split('/').last + path = path.split('.').first + raise "unknow path" unless path + gproject = g.create_project(project.identifier, + path: path, + description: project.description, + wiki_enabled: false, + wall_enabled: false, + issues_enabled: false, + snippets_enabled: false, + public: false, + user_id: gid, + import_url: 'https://github.com/gitlabhq/gitlab-cli.git' + ) + + # add team members + # + GUEST = 10 + REPORTER = 20 + DEVELOPER = 30 + MASTER = 40 + OWNER = 50 + + project.members.each do |m| + g.add_team_member(gproject.id, m.user.gid, DEVELOPER) + end + + end + end + end end From 006ed66d7a63d87c5bcca9b22c7c861f3911d32d Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Wed, 15 Jul 2015 15:51:49 +0800 Subject: [PATCH 003/169] =?UTF-8?q?=E6=96=B0=E6=B3=A8=E5=86=8C=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=8F=AF=E4=BB=A5=E5=90=8C=E6=AD=A5=E5=88=B0gitlab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/user.rb | 12 ++++++++++++ config/application.rb | 8 ++++++++ config/initializers/gitlab_config.rb | 2 +- lib/tasks/gitlab.rake | 9 +++++++-- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index b4723cceb..5605ea038 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -203,6 +203,7 @@ class User < Principal # added by fq after_create :act_as_activity # end + before_create :sync_gitlab_user scope :in_group, lambda {|group| group_id = group.is_a?(Group) ? group.id : group.to_i @@ -994,6 +995,17 @@ class User < Principal end + private + def sync_gitlab_user + user = self + g = Gitlab.client + u = g.get("/users?search=#{user.mail}").first + unless u + u = g.create_user(user.mail, user.password, name: user.show_name, username: user.login, confirm: "true") + self.gid = u.id + puts "create user #{user.login}" + end + end end diff --git a/config/application.rb b/config/application.rb index 83ba21b05..fc19faf49 100644 --- a/config/application.rb +++ b/config/application.rb @@ -69,6 +69,14 @@ module RedmineApp config.action_view.sanitized_allowed_tags = 'div', 'p', 'span', 'img', 'embed' + config.before_initialize do + puts "before initialize" + end + + config.after_initialize do + puts "after initialize" + end + if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb')) instance_eval File.read(File.join(File.dirname(__FILE__), 'additional_environment.rb')) end diff --git a/config/initializers/gitlab_config.rb b/config/initializers/gitlab_config.rb index ddd1efec8..3e2159336 100644 --- a/config/initializers/gitlab_config.rb +++ b/config/initializers/gitlab_config.rb @@ -1,5 +1,5 @@ Gitlab.configure do |config| - config.endpoint = 'http://192.168.41.130:3000/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] + config.endpoint = 'http://192.168.41.130:3000/trustie/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] config.private_token = 'cK15gUDwvt8EEkzwQ_63' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN'] # Optional # config.user_agent = 'Custom User Agent' # user agent, default: 'Gitlab Ruby Gem [version]' diff --git a/lib/tasks/gitlab.rake b/lib/tasks/gitlab.rake index 2d53eaa0a..5795ae4f4 100644 --- a/lib/tasks/gitlab.rake +++ b/lib/tasks/gitlab.rake @@ -4,7 +4,9 @@ namespace :gitlab do module Helper def self.change_password(uid, en_pwd, salt) g = Gitlab.client - g.edit_user(uid, :encrypted_password=>en_pwd, :password_salt=>salt) + options = {:encrypted_password=>en_pwd, :password_salt=>salt} + g.put("/users/ext/#{uid}", :body => options) + # g.edit_user(uid, :encrypted_password=>en_pwd, :password_salt=>salt) end end @@ -17,7 +19,7 @@ namespace :gitlab do g = Gitlab.client u = g.get("/users?search=#{user.mail}").first unless u - u = g.create_user(user.mail, user.hashed_password, name: user.show_name, username: user.login) + u = g.create_user(user.mail, user.hashed_password, name: user.show_name, username: user.login, confirm: "true") user.gid = u.id user.save! puts "create user #{user.login}" @@ -44,6 +46,9 @@ namespace :gitlab do path = project.repositories.where(:is_default => true).first.root_url.split('/').last path = path.split('.').first raise "unknow path" unless path + + # import url http://xianbo_trustie2:1234@repository.trustie.net/xianbo/trustie2.git + # can use password gproject = g.create_project(project.identifier, path: path, description: project.description, From 59a4b4656b34a82927eb39411e4b35a7919e6746 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Thu, 16 Jul 2015 20:46:04 +0800 Subject: [PATCH 004/169] change git config --- config/initializers/gitlab_config.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config/initializers/gitlab_config.rb b/config/initializers/gitlab_config.rb index 3e2159336..4041b45f4 100644 --- a/config/initializers/gitlab_config.rb +++ b/config/initializers/gitlab_config.rb @@ -1,6 +1,8 @@ Gitlab.configure do |config| - config.endpoint = 'http://192.168.41.130:3000/trustie/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] - config.private_token = 'cK15gUDwvt8EEkzwQ_63' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN'] + # config.endpoint = 'http://192.168.41.130:3000/trustie/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] + # config.private_token = 'cK15gUDwvt8EEkzwQ_63' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN'] + config.endpoint = 'http://gitlab.trustie.net/trustie/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] + config.private_token = 'fxm19wjRAs4REgTJwgtn' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN'] # Optional # config.user_agent = 'Custom User Agent' # user agent, default: 'Gitlab Ruby Gem [version]' # config.sudo = 'user' # username for sudo mode, default: nil From 9d8596a14c3442953ca89d02af7d5e9a8b1a4f96 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sat, 1 Aug 2015 18:48:55 +0800 Subject: [PATCH 005/169] =?UTF-8?q?=E6=96=B0=E5=A2=9Egitlab=20adapter?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E9=80=82=E9=85=8Dgitlab=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 2 +- app/controllers/repositories_controller.rb | 9 +- app/models/repository/gitlab.rb | 259 +++++++++++++ config/initializers/gitlab_config.rb | 2 +- lib/gitlab-cli/Gemfile | 4 + lib/gitlab-cli/LICENSE.txt | 24 ++ lib/gitlab-cli/README.md | 121 ++++++ lib/gitlab-cli/Rakefile | 9 + lib/gitlab-cli/bin/gitlab | 7 + lib/gitlab-cli/gitlab.gemspec | 26 ++ lib/gitlab-cli/lib/gitlab.rb | 37 ++ lib/gitlab-cli/lib/gitlab/api.rb | 17 + lib/gitlab-cli/lib/gitlab/cli.rb | 47 +++ lib/gitlab-cli/lib/gitlab/cli_helpers.rb | 175 +++++++++ lib/gitlab-cli/lib/gitlab/client.rb | 18 + lib/gitlab-cli/lib/gitlab/client/branches.rb | 79 ++++ lib/gitlab-cli/lib/gitlab/client/groups.rb | 88 +++++ lib/gitlab-cli/lib/gitlab/client/issues.rb | 92 +++++ .../lib/gitlab/client/merge_requests.rb | 107 ++++++ .../lib/gitlab/client/milestones.rb | 57 +++ lib/gitlab-cli/lib/gitlab/client/notes.rb | 106 ++++++ lib/gitlab-cli/lib/gitlab/client/projects.rb | 300 +++++++++++++++ .../lib/gitlab/client/repositories.rb | 89 +++++ lib/gitlab-cli/lib/gitlab/client/snippets.rb | 86 +++++ .../lib/gitlab/client/system_hooks.rb | 58 +++ lib/gitlab-cli/lib/gitlab/client/users.rb | 123 ++++++ lib/gitlab-cli/lib/gitlab/configuration.rb | 39 ++ lib/gitlab-cli/lib/gitlab/error.rb | 42 +++ lib/gitlab-cli/lib/gitlab/help.rb | 44 +++ lib/gitlab-cli/lib/gitlab/objectified_hash.rb | 24 ++ lib/gitlab-cli/lib/gitlab/request.rb | 113 ++++++ lib/gitlab-cli/lib/gitlab/shell.rb | 51 +++ lib/gitlab-cli/lib/gitlab/version.rb | 3 + lib/gitlab-cli/spec/fixtures/branch.json | 1 + lib/gitlab-cli/spec/fixtures/branches.json | 1 + .../spec/fixtures/comment_merge_request.json | 1 + .../spec/fixtures/create_branch.json | 1 + .../spec/fixtures/create_merge_request.json | 1 + .../spec/fixtures/error_already_exists.json | 1 + lib/gitlab-cli/spec/fixtures/group.json | 60 +++ .../spec/fixtures/group_create.json | 1 + .../spec/fixtures/group_member.json | 1 + .../spec/fixtures/group_member_delete.json | 1 + .../spec/fixtures/group_members.json | 1 + lib/gitlab-cli/spec/fixtures/groups.json | 2 + lib/gitlab-cli/spec/fixtures/issue.json | 1 + lib/gitlab-cli/spec/fixtures/issues.json | 1 + lib/gitlab-cli/spec/fixtures/key.json | 1 + lib/gitlab-cli/spec/fixtures/keys.json | 1 + .../spec/fixtures/merge_request.json | 1 + .../spec/fixtures/merge_request_comments.json | 1 + .../spec/fixtures/merge_requests.json | 1 + lib/gitlab-cli/spec/fixtures/milestone.json | 1 + lib/gitlab-cli/spec/fixtures/milestones.json | 1 + lib/gitlab-cli/spec/fixtures/note.json | 1 + lib/gitlab-cli/spec/fixtures/notes.json | 1 + lib/gitlab-cli/spec/fixtures/project.json | 1 + .../spec/fixtures/project_commit.json | 13 + .../spec/fixtures/project_commit_diff.json | 10 + .../spec/fixtures/project_commits.json | 1 + .../spec/fixtures/project_delete_key.json | 8 + .../spec/fixtures/project_events.json | 1 + .../spec/fixtures/project_for_user.json | 1 + .../spec/fixtures/project_fork_link.json | 1 + .../spec/fixtures/project_hook.json | 1 + .../spec/fixtures/project_hooks.json | 1 + .../spec/fixtures/project_issues.json | 1 + lib/gitlab-cli/spec/fixtures/project_key.json | 6 + .../spec/fixtures/project_keys.json | 6 + .../spec/fixtures/project_tags.json | 1 + lib/gitlab-cli/spec/fixtures/projects.json | 1 + .../spec/fixtures/protect_branch.json | 1 + lib/gitlab-cli/spec/fixtures/session.json | 1 + lib/gitlab-cli/spec/fixtures/snippet.json | 1 + lib/gitlab-cli/spec/fixtures/snippets.json | 1 + lib/gitlab-cli/spec/fixtures/system_hook.json | 1 + .../spec/fixtures/system_hook_test.json | 1 + .../spec/fixtures/system_hooks.json | 1 + lib/gitlab-cli/spec/fixtures/tag.json | 1 + lib/gitlab-cli/spec/fixtures/team_member.json | 1 + .../spec/fixtures/team_members.json | 1 + .../spec/fixtures/unprotect_branch.json | 1 + .../spec/fixtures/update_merge_request.json | 1 + lib/gitlab-cli/spec/fixtures/user.json | 1 + lib/gitlab-cli/spec/fixtures/users.json | 1 + lib/gitlab-cli/spec/gitlab/cli_spec.rb | 80 ++++ .../spec/gitlab/client/branches_spec.rb | 103 +++++ .../spec/gitlab/client/groups_spec.rb | 111 ++++++ .../spec/gitlab/client/issues_spec.rb | 122 ++++++ .../spec/gitlab/client/merge_requests_spec.rb | 124 ++++++ .../spec/gitlab/client/milestones_spec.rb | 66 ++++ .../spec/gitlab/client/notes_spec.rb | 156 ++++++++ .../spec/gitlab/client/projects_spec.rb | 357 ++++++++++++++++++ .../spec/gitlab/client/repositories_spec.rb | 92 +++++ .../spec/gitlab/client/snippets_spec.rb | 85 +++++ .../spec/gitlab/client/system_hooks_spec.rb | 69 ++++ .../spec/gitlab/client/users_spec.rb | 192 ++++++++++ .../spec/gitlab/objectified_hash_spec.rb | 23 ++ lib/gitlab-cli/spec/gitlab/request_spec.rb | 48 +++ lib/gitlab-cli/spec/gitlab_spec.rb | 65 ++++ lib/gitlab-cli/spec/spec_helper.rb | 74 ++++ lib/redmine/scm/adapters/gitlab_adapter.rb | 287 ++++++++++++++ lib/trustie.rb | 1 + lib/trustie/gitlab/api.rb | 35 ++ lib/trustie/gitlab/helper.rb | 0 spec/requests/gitlab_request_spec.rb | 13 + 106 files changed, 4502 insertions(+), 9 deletions(-) create mode 100644 app/models/repository/gitlab.rb create mode 100644 lib/gitlab-cli/Gemfile create mode 100644 lib/gitlab-cli/LICENSE.txt create mode 100644 lib/gitlab-cli/README.md create mode 100644 lib/gitlab-cli/Rakefile create mode 100644 lib/gitlab-cli/bin/gitlab create mode 100644 lib/gitlab-cli/gitlab.gemspec create mode 100644 lib/gitlab-cli/lib/gitlab.rb create mode 100644 lib/gitlab-cli/lib/gitlab/api.rb create mode 100644 lib/gitlab-cli/lib/gitlab/cli.rb create mode 100644 lib/gitlab-cli/lib/gitlab/cli_helpers.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client/branches.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client/groups.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client/issues.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client/merge_requests.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client/milestones.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client/notes.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client/projects.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client/repositories.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client/snippets.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client/system_hooks.rb create mode 100644 lib/gitlab-cli/lib/gitlab/client/users.rb create mode 100644 lib/gitlab-cli/lib/gitlab/configuration.rb create mode 100644 lib/gitlab-cli/lib/gitlab/error.rb create mode 100644 lib/gitlab-cli/lib/gitlab/help.rb create mode 100644 lib/gitlab-cli/lib/gitlab/objectified_hash.rb create mode 100644 lib/gitlab-cli/lib/gitlab/request.rb create mode 100644 lib/gitlab-cli/lib/gitlab/shell.rb create mode 100644 lib/gitlab-cli/lib/gitlab/version.rb create mode 100644 lib/gitlab-cli/spec/fixtures/branch.json create mode 100644 lib/gitlab-cli/spec/fixtures/branches.json create mode 100644 lib/gitlab-cli/spec/fixtures/comment_merge_request.json create mode 100644 lib/gitlab-cli/spec/fixtures/create_branch.json create mode 100644 lib/gitlab-cli/spec/fixtures/create_merge_request.json create mode 100644 lib/gitlab-cli/spec/fixtures/error_already_exists.json create mode 100644 lib/gitlab-cli/spec/fixtures/group.json create mode 100644 lib/gitlab-cli/spec/fixtures/group_create.json create mode 100644 lib/gitlab-cli/spec/fixtures/group_member.json create mode 100644 lib/gitlab-cli/spec/fixtures/group_member_delete.json create mode 100644 lib/gitlab-cli/spec/fixtures/group_members.json create mode 100644 lib/gitlab-cli/spec/fixtures/groups.json create mode 100644 lib/gitlab-cli/spec/fixtures/issue.json create mode 100644 lib/gitlab-cli/spec/fixtures/issues.json create mode 100644 lib/gitlab-cli/spec/fixtures/key.json create mode 100644 lib/gitlab-cli/spec/fixtures/keys.json create mode 100644 lib/gitlab-cli/spec/fixtures/merge_request.json create mode 100644 lib/gitlab-cli/spec/fixtures/merge_request_comments.json create mode 100644 lib/gitlab-cli/spec/fixtures/merge_requests.json create mode 100644 lib/gitlab-cli/spec/fixtures/milestone.json create mode 100644 lib/gitlab-cli/spec/fixtures/milestones.json create mode 100644 lib/gitlab-cli/spec/fixtures/note.json create mode 100644 lib/gitlab-cli/spec/fixtures/notes.json create mode 100644 lib/gitlab-cli/spec/fixtures/project.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_commit.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_commit_diff.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_commits.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_delete_key.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_events.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_for_user.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_fork_link.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_hook.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_hooks.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_issues.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_key.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_keys.json create mode 100644 lib/gitlab-cli/spec/fixtures/project_tags.json create mode 100644 lib/gitlab-cli/spec/fixtures/projects.json create mode 100644 lib/gitlab-cli/spec/fixtures/protect_branch.json create mode 100644 lib/gitlab-cli/spec/fixtures/session.json create mode 100644 lib/gitlab-cli/spec/fixtures/snippet.json create mode 100644 lib/gitlab-cli/spec/fixtures/snippets.json create mode 100644 lib/gitlab-cli/spec/fixtures/system_hook.json create mode 100644 lib/gitlab-cli/spec/fixtures/system_hook_test.json create mode 100644 lib/gitlab-cli/spec/fixtures/system_hooks.json create mode 100644 lib/gitlab-cli/spec/fixtures/tag.json create mode 100644 lib/gitlab-cli/spec/fixtures/team_member.json create mode 100644 lib/gitlab-cli/spec/fixtures/team_members.json create mode 100644 lib/gitlab-cli/spec/fixtures/unprotect_branch.json create mode 100644 lib/gitlab-cli/spec/fixtures/update_merge_request.json create mode 100644 lib/gitlab-cli/spec/fixtures/user.json create mode 100644 lib/gitlab-cli/spec/fixtures/users.json create mode 100644 lib/gitlab-cli/spec/gitlab/cli_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/client/branches_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/client/groups_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/client/issues_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/client/merge_requests_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/client/milestones_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/client/notes_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/client/projects_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/client/repositories_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/client/snippets_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/client/system_hooks_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/client/users_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/objectified_hash_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab/request_spec.rb create mode 100644 lib/gitlab-cli/spec/gitlab_spec.rb create mode 100644 lib/gitlab-cli/spec/spec_helper.rb create mode 100644 lib/redmine/scm/adapters/gitlab_adapter.rb create mode 100644 lib/trustie/gitlab/api.rb create mode 100644 lib/trustie/gitlab/helper.rb create mode 100644 spec/requests/gitlab_request_spec.rb diff --git a/Gemfile b/Gemfile index eaaff1d01..d5c8c35d0 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ unless RUBY_PLATFORM =~ /w32/ gem 'iconv' end -gem 'gitlab' +gem 'gitlab', path: 'lib/gitlab-cli' gem 'rest-client' gem "mysql2", "= 0.3.18" gem 'redis-rails' diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 7b364a46b..7219f4c08 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -251,16 +251,11 @@ update end end - if params[:to] == 'gitlab' - g = Gitlab.client - g.post('/session', body: {email: User.current.mail, password: User.current.hashed_password}) - redirect_to "http://192.168.41.130:3000/gitlab-org/gitlab-shell/tree/master" - return - end - #if( !User.current.member_of?(@project) || @project.hidden_repo) @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? + + # :name, :path, :kind, :size, :lastrev, :changeset @entries = @repository.entries(@path, @rev) @changeset = @repository.find_changeset_by_name(@rev) diff --git a/app/models/repository/gitlab.rb b/app/models/repository/gitlab.rb new file mode 100644 index 000000000..5ec725660 --- /dev/null +++ b/app/models/repository/gitlab.rb @@ -0,0 +1,259 @@ +#coding=utf-8 + +require 'redmine/scm/adapters/git_adapter' + +class Repository::Gitlab < Repository + + attr_protected :root_url + validates_presence_of :url + + def self.human_attribute_name(attribute_key_name, *args) + attr_name = attribute_key_name.to_s + if attr_name == "url" + attr_name = "path_to_repository" + end + super(attr_name, *args) + end + + def self.scm_adapter_class + Redmine::Scm::Adapters::GitlabAdapter + end + + def self.scm_name + 'Gitlab' + end + + def commits(authors, start_date, end_date, branch='master') + scm.commits(authors, start_date, end_date,branch).map {|commit| + [commit[:author], commit[:num]] + } + end + + def report_last_commit + extra_report_last_commit + end + + def extra_report_last_commit + return false if extra_info.nil? + v = extra_info["extra_report_last_commit"] + return false if v.nil? + v.to_s != '0' + end + + def supports_directory_revisions? + true + end + + def supports_revision_graph? + true + end + + def repo_log_encoding + 'UTF-8' + end + + # Returns the identifier for the given git changeset + def self.changeset_identifier(changeset) + changeset.scmid + end + + # Returns the readable identifier for the given git changeset + def self.format_changeset_identifier(changeset) + changeset.revision[0, 8] + end + + def branches + scm.branches + end + + def tags + scm.tags + end + + def default_branch + scm.default_branch + rescue Exception => e + logger.error "git: error during get default branch: #{e.message}" + nil + end + + def find_changeset_by_name(name) + if name.present? + changesets.where(:revision => name.to_s).first || + changesets.where('scmid LIKE ?', "#{name}%").first + end + end + + def entries(path=nil, identifier=nil) + entries = scm.entries(path, identifier, :report_last_commit => extra_report_last_commit) + load_entries_changesets(entries) + entries + end + + # With SCMs that have a sequential commit numbering, + # such as Subversion and Mercurial, + # Redmine is able to be clever and only fetch changesets + # going forward from the most recent one it knows about. + # + # However, Git does not have a sequential commit numbering. + # + # In order to fetch only new adding revisions, + # Redmine needs to save "heads". + # + # In Git and Mercurial, revisions are not in date order. + # Redmine Mercurial fixed issues. + # * Redmine Takes Too Long On Large Mercurial Repository + # http://www.redmine.org/issues/3449 + # * Sorting for changesets might go wrong on Mercurial repos + # http://www.redmine.org/issues/3567 + # + # Database revision column is text, so Redmine can not sort by revision. + # Mercurial has revision number, and revision number guarantees revision order. + # Redmine Mercurial model stored revisions ordered by database id to database. + # So, Redmine Mercurial model can use correct ordering revisions. + # + # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10" + # to get limited revisions from old to new. + # But, Git 1.7.3.4 does not support --reverse with -n or --skip. + # + # The repository can still be fully reloaded by calling #clear_changesets + # before fetching changesets (eg. for offline resync) + def fetch_changesets + scm_brs = branches + return if scm_brs.nil? || scm_brs.empty? + + h1 = extra_info || {} + h = h1.dup + repo_heads = scm_brs.map{ |br| br.scmid } + h["heads"] ||= [] + prev_db_heads = h["heads"].dup + if prev_db_heads.empty? + prev_db_heads += heads_from_branches_hash + end + return if prev_db_heads.sort == repo_heads.sort + + h["db_consistent"] ||= {} + if changesets.count == 0 + h["db_consistent"]["ordering"] = 1 + merge_extra_info(h) + self.save + elsif ! h["db_consistent"].has_key?("ordering") + h["db_consistent"]["ordering"] = 0 + merge_extra_info(h) + self.save + end + save_revisions(prev_db_heads, repo_heads) + end + + def save_revisions(prev_db_heads, repo_heads) + h = {} + opts = {} + opts[:reverse] = true + opts[:excludes] = prev_db_heads + opts[:includes] = repo_heads + + revisions = scm.revisions('', nil, nil, opts) + return if revisions.blank? + + # Make the search for existing revisions in the database in a more sufficient manner + # + # Git branch is the reference to the specific revision. + # Git can *delete* remote branch and *re-push* branch. + # + # $ git push remote :branch + # $ git push remote branch + # + # After deleting branch, revisions remain in repository until "git gc". + # On git 1.7.2.3, default pruning date is 2 weeks. + # So, "git log --not deleted_branch_head_revision" return code is 0. + # + # After re-pushing branch, "git log" returns revisions which are saved in database. + # So, Redmine needs to scan revisions and database every time. + # + # This is replacing the one-after-one queries. + # Find all revisions, that are in the database, and then remove them from the revision array. + # Then later we won't need any conditions for db existence. + # Query for several revisions at once, and remove them from the revisions array, if they are there. + # Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits). + # If there are no revisions (because the original code's algorithm filtered them), + # then this part will be stepped over. + # We make queries, just if there is any revision. + limit = 100 + offset = 0 + revisions_copy = revisions.clone # revisions will change + while offset < revisions_copy.size + recent_changesets_slice = changesets.find( + :all, + :conditions => [ + 'scmid IN (?)', + revisions_copy.slice(offset, limit).map{|x| x.scmid} + ] + ) + # Subtract revisions that redmine already knows about + recent_revisions = recent_changesets_slice.map{|c| c.scmid} + revisions.reject!{|r| recent_revisions.include?(r.scmid)} + offset += limit + end + + revisions.each do |rev| + transaction do + # There is no search in the db for this revision, because above we ensured, + # that it's not in the db. + save_revision(rev) + end + end + h["heads"] = repo_heads.dup + merge_extra_info(h) + self.save + end + private :save_revisions + + def save_revision(rev) + parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact + changeset = Changeset.create( + :repository => self, + :revision => rev.identifier, + :scmid => rev.scmid, + :committer => rev.author, + :committed_on => rev.time, + :comments => rev.message, + :parents => parents + ) + unless changeset.new_record? + rev.paths.each { |change| changeset.create_change(change) } + end + changeset + end + private :save_revision + + def heads_from_branches_hash + h1 = extra_info || {} + h = h1.dup + h["branches"] ||= {} + h['branches'].map{|br, hs| hs['last_scmid']} + end + + def latest_changesets(path,rev,limit=10) + revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) + return [] if revisions.nil? || revisions.empty? + + changesets.find( + :all, + :conditions => [ + "scmid IN (?)", + revisions.map!{|c| c.scmid} + ] + ) + end + + def clear_extra_info_of_changesets + return if extra_info.nil? + v = extra_info["extra_report_last_commit"] + write_attribute(:extra_info, nil) + h = {} + h["extra_report_last_commit"] = v + merge_extra_info(h) + self.save + end + private :clear_extra_info_of_changesets +end diff --git a/config/initializers/gitlab_config.rb b/config/initializers/gitlab_config.rb index 4041b45f4..61fc12287 100644 --- a/config/initializers/gitlab_config.rb +++ b/config/initializers/gitlab_config.rb @@ -1,7 +1,7 @@ Gitlab.configure do |config| # config.endpoint = 'http://192.168.41.130:3000/trustie/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] # config.private_token = 'cK15gUDwvt8EEkzwQ_63' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN'] - config.endpoint = 'http://gitlab.trustie.net/trustie/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] + config.endpoint = 'http://git.trustie.net/trustie/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] config.private_token = 'fxm19wjRAs4REgTJwgtn' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN'] # Optional # config.user_agent = 'Custom User Agent' # user agent, default: 'Gitlab Ruby Gem [version]' diff --git a/lib/gitlab-cli/Gemfile b/lib/gitlab-cli/Gemfile new file mode 100644 index 000000000..54fbdf177 --- /dev/null +++ b/lib/gitlab-cli/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in gitlab.gemspec +gemspec diff --git a/lib/gitlab-cli/LICENSE.txt b/lib/gitlab-cli/LICENSE.txt new file mode 100644 index 000000000..2c289176d --- /dev/null +++ b/lib/gitlab-cli/LICENSE.txt @@ -0,0 +1,24 @@ +Copyright (c) 2012-2014 Nihad Abbasov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/gitlab-cli/README.md b/lib/gitlab-cli/README.md new file mode 100644 index 000000000..1aa73996c --- /dev/null +++ b/lib/gitlab-cli/README.md @@ -0,0 +1,121 @@ +# Gitlab + +[![Build Status](https://travis-ci.org/NARKOZ/gitlab.png)](http://travis-ci.org/NARKOZ/gitlab) + +[website](http://narkoz.github.io/gitlab) | +[documentation](http://rubydoc.info/gems/gitlab/frames) + +Gitlab is a Ruby wrapper and CLI for the [GitLab API](https://github.com/gitlabhq/gitlabhq/tree/master/doc/api#gitlab-api). + +## Installation + +Install it from rubygems: + +```sh +gem install gitlab +``` + +Or add to a Gemfile: + +```ruby +gem 'gitlab' +# gem 'gitlab', :git => 'git://github.com/NARKOZ/gitlab.git' +``` + +## Usage + +Configuration example: + +```ruby +Gitlab.configure do |config| + config.endpoint = 'https://example.net/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] + config.private_token = 'qEsq1pt6HJPaNciie3MG' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN'] + # Optional + # config.user_agent = 'Custom User Agent' # user agent, default: 'Gitlab Ruby Gem [version]' + # config.sudo = 'user' # username for sudo mode, default: nil +end +``` + +(Note: If you are using Gitlab.com's hosted service, your endpoint will be `https://gitlab.com/api/v3`) + +Usage examples: + +```ruby +# set an API endpoint +Gitlab.endpoint = 'http://example.net/api/v3' +# => "http://example.net/api/v3" + +# set a user private token +Gitlab.private_token = 'qEsq1pt6HJPaNciie3MG' +# => "qEsq1pt6HJPaNciie3MG" + +# list projects +Gitlab.projects(:per_page => 5) +# => [#1, "code"=>"brute", "name"=>"Brute", "description"=>nil, "path"=>"brute", "default_branch"=>nil, "owner"=>#1, "email"=>"john@example.com", "name"=>"John Smith", "blocked"=>false, "created_at"=>"2012-09-17T09:41:56Z"}>, "private"=>true, "issues_enabled"=>true, "merge_requests_enabled"=>true, "wall_enabled"=>true, "wiki_enabled"=>true, "created_at"=>"2012-09-17T09:41:56Z"}>, #2, "code"=>"mozart", "name"=>"Mozart", "description"=>nil, "path"=>"mozart", "default_branch"=>nil, "owner"=>#1, "email"=>"john@example.com", "name"=>"John Smith", "blocked"=>false, "created_at"=>"2012-09-17T09:41:56Z"}>, "private"=>true, "issues_enabled"=>true, "merge_requests_enabled"=>true, "wall_enabled"=>true, "wiki_enabled"=>true, "created_at"=>"2012-09-17T09:41:57Z"}>, #3, "code"=>"gitlab", "name"=>"Gitlab", "description"=>nil, "path"=>"gitlab", "default_branch"=>nil, "owner"=>#1, "email"=>"john@example.com", "name"=>"John Smith", "blocked"=>false, "created_at"=>"2012-09-17T09:41:56Z"}>, "private"=>true, "issues_enabled"=>true, "merge_requests_enabled"=>true, "wall_enabled"=>true, "wiki_enabled"=>true, "created_at"=>"2012-09-17T09:41:58Z"}>] + +# initialize a new client +g = Gitlab.client(:endpoint => 'https://api.example.com', :private_token => 'qEsq1pt6HJPaNciie3MG') +# => # + +# get a user +user = g.user +# => #1, "email"=>"john@example.com", "name"=>"John Smith", "bio"=>nil, "skype"=>"", "linkedin"=>"", "twitter"=>"john", "dark_scheme"=>false, "theme_id"=>1, "blocked"=>false, "created_at"=>"2012-09-17T09:41:56Z"}> + +# get a user's email +user.email +# => "john@example.com" + +# set a sudo mode to perform API calls as another user +Gitlab.sudo = 'other_user' +# => "other_user" + +# disable a sudo mode +Gitlab.sudo = nil +# => nil +``` + +For more information, refer to [documentation](http://rubydoc.info/gems/gitlab/frames). + +## CLI + +Usage examples: + +```sh +# list users +gitlab users + +# get current user +gitlab user + +# get a user +gitlab user 2 + +# filter output +gitlab user --only=id,username + +gitlab user --except=email,bio +``` + +## CLI Shell + +Usage examples: + +```sh +# start shell session +gitlab shell + +# list available commands +gitlab> help + +# list groups +gitlab> groups + +# protect a branch +gitlab> protect_branch 1 master +``` + +For more information, refer to [website](http://narkoz.github.io/gitlab). + +## License + +Released under the BSD 2-clause license. See LICENSE.txt for details. diff --git a/lib/gitlab-cli/Rakefile b/lib/gitlab-cli/Rakefile new file mode 100644 index 000000000..bf451bdcb --- /dev/null +++ b/lib/gitlab-cli/Rakefile @@ -0,0 +1,9 @@ +require "bundler/gem_tasks" + +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) do |spec| + spec.pattern = FileList['spec/**/*_spec.rb'] + spec.rspec_opts = ['--color', '--format d'] +end + +task :default => :spec diff --git a/lib/gitlab-cli/bin/gitlab b/lib/gitlab-cli/bin/gitlab new file mode 100644 index 000000000..af0fd0cc0 --- /dev/null +++ b/lib/gitlab-cli/bin/gitlab @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +$:.unshift File.expand_path('../../lib', __FILE__) + +require 'gitlab/cli' + +Gitlab::CLI.start(ARGV) diff --git a/lib/gitlab-cli/gitlab.gemspec b/lib/gitlab-cli/gitlab.gemspec new file mode 100644 index 000000000..7d5f30862 --- /dev/null +++ b/lib/gitlab-cli/gitlab.gemspec @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'gitlab/version' + +Gem::Specification.new do |gem| + gem.name = "gitlab" + gem.version = Gitlab::VERSION + gem.authors = ["Nihad Abbasov"] + gem.email = ["mail@narkoz.me"] + gem.description = %q{Ruby client and CLI for GitLab API} + gem.summary = %q{A Ruby wrapper and CLI for the GitLab API} + gem.homepage = "https://github.com/narkoz/gitlab" + + gem.files = `git ls-files`.split($/) + gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } + gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) + gem.require_paths = ["lib"] + + gem.add_runtime_dependency 'httparty' + gem.add_runtime_dependency 'terminal-table' + + gem.add_development_dependency 'rake' + gem.add_development_dependency 'rspec' + gem.add_development_dependency 'webmock' +end diff --git a/lib/gitlab-cli/lib/gitlab.rb b/lib/gitlab-cli/lib/gitlab.rb new file mode 100644 index 000000000..95c382678 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab.rb @@ -0,0 +1,37 @@ +require 'gitlab/version' +require 'gitlab/objectified_hash' +require 'gitlab/configuration' +require 'gitlab/error' +require 'gitlab/request' +require 'gitlab/api' +require 'gitlab/client' + +module Gitlab + extend Configuration + + # Alias for Gitlab::Client.new + # + # @return [Gitlab::Client] + def self.client(options={}) + Gitlab::Client.new(options) + end + + # Delegate to Gitlab::Client + def self.method_missing(method, *args, &block) + return super unless client.respond_to?(method) + client.send(method, *args, &block) + end + + # Delegate to Gitlab::Client + def self.respond_to?(method) + return client.respond_to?(method) || super + end + + # Returns an unsorted array of available client methods. + # + # @return [Array] + def self.actions + hidden = /endpoint|private_token|user_agent|sudo|get|post|put|\Adelete\z|validate|set_request_defaults/ + (Gitlab::Client.instance_methods - Object.methods).reject {|e| e[hidden]} + end +end diff --git a/lib/gitlab-cli/lib/gitlab/api.rb b/lib/gitlab-cli/lib/gitlab/api.rb new file mode 100644 index 000000000..62b3f47dc --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/api.rb @@ -0,0 +1,17 @@ +module Gitlab + # @private + class API < Request + # @private + attr_accessor(*Configuration::VALID_OPTIONS_KEYS) + + # Creates a new API. + # @raise [Error:MissingCredentials] + def initialize(options={}) + options = Gitlab.options.merge(options) + Configuration::VALID_OPTIONS_KEYS.each do |key| + send("#{key}=", options[key]) + end + set_request_defaults @endpoint, @private_token, @sudo + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/cli.rb b/lib/gitlab-cli/lib/gitlab/cli.rb new file mode 100644 index 000000000..57c2aa5b4 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/cli.rb @@ -0,0 +1,47 @@ +require 'gitlab' +require 'terminal-table/import' +require_relative 'cli_helpers' +require_relative 'shell' + +class Gitlab::CLI + extend Helpers + + def self.start(args) + command = args.shift.strip rescue 'help' + run(command, args) + end + + def self.run(cmd, args=[]) + case cmd + when 'help' + puts actions_table + when 'info' + endpoint = Gitlab.endpoint ? Gitlab.endpoint : 'not set' + private_token = Gitlab.private_token ? Gitlab.private_token : 'not set' + puts "Gitlab endpoint is #{endpoint}" + puts "Gitlab private token is #{private_token}" + puts "Ruby Version is #{RUBY_VERSION}" + puts "Gitlab Ruby Gem #{Gitlab::VERSION}" + when '-v', '--version' + puts "Gitlab Ruby Gem #{Gitlab::VERSION}" + when 'shell' + Gitlab::Shell.start + else + unless valid_command?(cmd) + puts "Unknown command. Run `gitlab help` for a list of available commands." + exit(1) + end + + if args.any? && (args.last.start_with?('--only=') || args.last.start_with?('--except=')) + command_args = args[0..-2] + else + command_args = args + end + + confirm_command(cmd) + + data = gitlab_helper(cmd, command_args) { exit(1) } + output_table(cmd, args, data) + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/cli_helpers.rb b/lib/gitlab-cli/lib/gitlab/cli_helpers.rb new file mode 100644 index 000000000..d1c777dc0 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/cli_helpers.rb @@ -0,0 +1,175 @@ +class Gitlab::CLI + # Defines methods related to CLI output and formatting. + module Helpers + extend self + + # Returns filtered required fields. + # + # @return [Array] + def required_fields(args) + if args.any? && args.last.start_with?('--only=') + args.last.gsub('--only=', '').split(',') + else + [] + end + end + + # Returns filtered excluded fields. + # + # @return [Array] + def excluded_fields(args) + if args.any? && args.last.start_with?('--except=') + args.last.gsub('--except=', '').split(',') + else + [] + end + end + + # Confirms command is valid. + # + # @return [Boolean] + def valid_command?(cmd) + command = cmd.is_a?(Symbol) ? cmd : cmd.to_sym + Gitlab.actions.include?(command) + end + + # Confirms command with a desctructive action. + # + # @return [String] + def confirm_command(cmd) + if cmd.start_with?('remove_') || cmd.start_with?('delete_') + puts "Are you sure? (y/n)" + if %w(y yes).include?($stdin.gets.to_s.strip.downcase) + puts 'Proceeding..' + else + puts 'Command aborted.' + exit(1) + end + end + end + + # Table with available commands. + # + # @return [String] + def actions_table + client = Gitlab::Client.new(endpoint: '') + actions = Gitlab.actions + methods = [] + + actions.each do |action| + methods << { + name: action, + owner: client.method(action).owner.to_s.gsub('Gitlab::Client::', '') + } + end + + owners = methods.map {|m| m[:owner]}.uniq.sort + methods_c = methods.group_by {|m| m[:owner]} + methods_c = methods_c.map {|_, v| [_, v.sort_by {|hv| hv[:name]}] } + methods_c = Hash[methods_c.sort_by(&:first).map {|k, v| [k, v]}] + max_column_length = methods_c.values.max_by(&:size).size + + rows = max_column_length.times.map do |i| + methods_c.keys.map do |key| + methods_c[key][i] ? methods_c[key][i][:name] : '' + end + end + + table do |t| + t.title = "Available commands (#{actions.size} total)" + t.headings = owners + + rows.each do |row| + t.add_row row + end + end + end + + # Decides which table to use. + # + # @return [String] + def output_table(cmd, args, data) + case data + when Gitlab::ObjectifiedHash + puts single_record_table(data, cmd, args) + when Array + puts multiple_record_table(data, cmd, args) + else + puts data.inspect + end + end + + # Table for a single record. + # + # @return [String] + def single_record_table(data, cmd, args) + hash = data.to_h + keys = hash.keys.sort {|x, y| x.to_s <=> y.to_s } + keys = keys & required_fields(args) if required_fields(args).any? + keys = keys - excluded_fields(args) + + table do |t| + t.title = "Gitlab.#{cmd} #{args.join(', ')}" + + keys.each_with_index do |key, index| + case value = hash[key] + when Hash + value = 'Hash' + when nil + value = 'null' + end + + t.add_row [key, value] + t.add_separator unless keys.size - 1 == index + end + end + end + + # Table for multiple records. + # + # @return [String] + def multiple_record_table(data, cmd, args) + return 'No data' if data.empty? + + arr = data.map(&:to_h) + keys = arr.first.keys.sort {|x, y| x.to_s <=> y.to_s } + keys = keys & required_fields(args) if required_fields(args).any? + keys = keys - excluded_fields(args) + + table do |t| + t.title = "Gitlab.#{cmd} #{args.join(', ')}" + t.headings = keys + + arr.each_with_index do |hash, index| + values = [] + + keys.each do |key| + case value = hash[key] + when Hash + value = 'Hash' + when nil + value = 'null' + end + + values << value + end + + t.add_row values + t.add_separator unless arr.size - 1 == index + end + end + end + + # Helper function to call Gitlab commands with args. + def gitlab_helper(cmd, args=[]) + begin + data = args.any? ? Gitlab.send(cmd, *args) : Gitlab.send(cmd) + rescue => e + puts e.message + yield if block_given? + end + + data + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/client.rb b/lib/gitlab-cli/lib/gitlab/client.rb new file mode 100644 index 000000000..5cb01ce2b --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client.rb @@ -0,0 +1,18 @@ +module Gitlab + # Wrapper for the Gitlab REST API. + class Client < API + Dir[File.expand_path('../client/*.rb', __FILE__)].each {|f| require f} + + include Branches + include Groups + include Issues + include MergeRequests + include Milestones + include Notes + include Projects + include Repositories + include Snippets + include SystemHooks + include Users + end +end diff --git a/lib/gitlab-cli/lib/gitlab/client/branches.rb b/lib/gitlab-cli/lib/gitlab/client/branches.rb new file mode 100644 index 000000000..0ba3afb29 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client/branches.rb @@ -0,0 +1,79 @@ +class Gitlab::Client + # Defines methods related to repositories. + module Branches + # Gets a list of project repositiory branches. + # + # @example + # Gitlab.branches(42) + # + # @param [Integer] project The ID of a project. + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def branches(project, options={}) + get("/projects/#{project}/repository/branches", :query => options) + end + alias_method :repo_branches, :branches + + # Gets information about a repository branch. + # + # @example + # Gitlab.branch(3, 'api') + # Gitlab.repo_branch(5, 'master') + # + # @param [Integer] project The ID of a project. + # @param [String] branch The name of the branch. + # @return [Gitlab::ObjectifiedHash] + def branch(project, branch) + get("/projects/#{project}/repository/branches/#{branch}") + end + + alias_method :repo_branch, :branch + + # Protects a repository branch. + # + # @example + # Gitlab.protect_branch(3, 'api') + # Gitlab.repo_protect_branch(5, 'master') + # + # @param [Integer] project The ID of a project. + # @param [String] branch The name of the branch. + # @return [Gitlab::ObjectifiedHash] + def protect_branch(project, branch) + put("/projects/#{project}/repository/branches/#{branch}/protect") + end + alias_method :repo_protect_branch, :protect_branch + + # Unprotects a repository branch. + # + # @example + # Gitlab.unprotect_branch(3, 'api') + # Gitlab.repo_unprotect_branch(5, 'master') + # + # @param [Integer] project The ID of a project. + # @param [String] branch The name of the branch. + # @return [Gitlab::ObjectifiedHash] + def unprotect_branch(project, branch) + put("/projects/#{project}/repository/branches/#{branch}/unprotect") + end + alias_method :repo_unprotect_branch, :unprotect_branch + + # Creates a repository branch. Requires Gitlab >= 6.8.x + # + # @example + # Gitlab.create_branch(3, 'api') + # Gitlab.repo_create_branch(5, 'master') + # + # @param [Integer] project The ID of a project. + # @param [String] branch The name of the new branch. + # @param [String] ref Create branch from commit sha or existing branch + # @return [Gitlab::ObjectifiedHash] + def create_branch(project, branch, ref) + post("/projects/#{project}/repository/branches",:body => {:branch_name => branch, :ref => ref}) + end + alias_method :repo_create_branch, :create_branch + + end +end + diff --git a/lib/gitlab-cli/lib/gitlab/client/groups.rb b/lib/gitlab-cli/lib/gitlab/client/groups.rb new file mode 100644 index 000000000..1ea4bf5bc --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client/groups.rb @@ -0,0 +1,88 @@ +class Gitlab::Client + # Defines methods related to groups. + module Groups + # Gets a list of groups. + # + # @example + # Gitlab.groups + # Gitlab.groups(:per_page => 40) + # + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def groups(options={}) + get("/groups", :query => options) + end + + # Gets a single group. + # + # @example + # Gitlab.group(42) + # + # @param [Integer] id The ID of a group. + # @return [Gitlab::ObjectifiedHash] + def group(id) + get("/groups/#{id}") + end + + # Creates a new group. + # + # @param [String] name The name of a group. + # @param [String] path The path of a group. + # @return [Gitlab::ObjectifiedHash] Information about created group. + def create_group(name, path) + body = {:name => name, :path => path} + post("/groups", :body => body) + end + + # Get a list of group members. + # + # @example + # Gitlab.group_members(1) + # Gitlab.group_members(1, :per_page => 40) + # + # @param [Integer] id The ID of a group. + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def group_members(id, options={}) + get("/groups/#{id}/members", :query => options) + end + + # Adds a user to group. + # + # @example + # Gitlab.add_group_member(1, 2, 40) + # + # @param [Integer] team_id The group id to add a member to. + # @param [Integer] user_id The user id of the user to add to the team. + # @param [Integer] access_level Project access level. + # @return [Gitlab::ObjectifiedHash] Information about added team member. + def add_group_member(team_id, user_id, access_level) + post("/groups/#{team_id}/members", :body => {:user_id => user_id, :access_level => access_level}) + end + + # Removes user from user group. + # + # @example + # Gitlab.remove_group_member(1, 2) + # + # @param [Integer] team_id The group ID. + # @param [Integer] user_id The ID of a user. + # @return [Gitlab::ObjectifiedHash] Information about removed team member. + def remove_group_member(team_id, user_id) + delete("/groups/#{team_id}/members/#{user_id}") + end + + # Transfers a project to a group + # + # @param [Integer] id The ID of a group. + # @param [Integer] project_id The ID of a project. + def transfer_project_to_group(id, project_id) + body = {:id => id, :project_id => project_id} + post("/groups/#{id}/projects/#{project_id}", :body => body) + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/client/issues.rb b/lib/gitlab-cli/lib/gitlab/client/issues.rb new file mode 100644 index 000000000..7248df848 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client/issues.rb @@ -0,0 +1,92 @@ +class Gitlab::Client + # Defines methods related to issues. + module Issues + # Gets a list of user's issues. + # Will return a list of project's issues if project ID passed. + # + # @example + # Gitlab.issues + # Gitlab.issues(5) + # Gitlab.issues(:per_page => 40) + # + # @param [Integer] project The ID of a project. + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def issues(project=nil, options={}) + if project.to_i.zero? + get("/issues", :query => options) + else + get("/projects/#{project}/issues", :query => options) + end + end + + # Gets a single issue. + # + # @example + # Gitlab.issue(5, 42) + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of an issue. + # @return [Gitlab::ObjectifiedHash] + def issue(project, id) + get("/projects/#{project}/issues/#{id}") + end + + # Creates a new issue. + # + # @param [Integer] project The ID of a project. + # @param [String] title The title of an issue. + # @param [Hash] options A customizable set of options. + # @option options [String] :description The description of an issue. + # @option options [Integer] :assignee_id The ID of a user to assign issue. + # @option options [Integer] :milestone_id The ID of a milestone to assign issue. + # @option options [String] :labels Comma-separated label names for an issue. + # @return [Gitlab::ObjectifiedHash] Information about created issue. + def create_issue(project, title, options={}) + body = {:title => title}.merge(options) + post("/projects/#{project}/issues", :body => body) + end + + # Updates an issue. + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of an issue. + # @param [Hash] options A customizable set of options. + # @option options [String] :title The title of an issue. + # @option options [String] :description The description of an issue. + # @option options [Integer] :assignee_id The ID of a user to assign issue. + # @option options [Integer] :milestone_id The ID of a milestone to assign issue. + # @option options [String] :labels Comma-separated label names for an issue. + # @option options [String] :state_event The state event of an issue ('close' or 'reopen'). + # @return [Gitlab::ObjectifiedHash] Information about updated issue. + def edit_issue(project, id, options={}) + put("/projects/#{project}/issues/#{id}", :body => options) + end + + # Closes an issue. + # + # @example + # Gitlab.close_issue(3, 42) + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of an issue. + # @return [Gitlab::ObjectifiedHash] Information about closed issue. + def close_issue(project, id) + put("/projects/#{project}/issues/#{id}", :body => {:state_event => 'close'}) + end + + # Reopens an issue. + # + # @example + # Gitlab.reopen_issue(3, 42) + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of an issue. + # @return [Gitlab::ObjectifiedHash] Information about reopened issue. + def reopen_issue(project, id) + put("/projects/#{project}/issues/#{id}", :body => {:state_event => 'reopen'}) + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/client/merge_requests.rb b/lib/gitlab-cli/lib/gitlab/client/merge_requests.rb new file mode 100644 index 000000000..1ccc25081 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client/merge_requests.rb @@ -0,0 +1,107 @@ +class Gitlab::Client + # Defines methods related to merge requests. + module MergeRequests + # Gets a list of project merge requests. + # + # @example + # Gitlab.merge_requests(5) + # Gitlab.merge_requests(:per_page => 40) + # + # @param [Integer] project The ID of a project. + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def merge_requests(project, options={}) + get("/projects/#{project}/merge_requests", :query => options) + end + + # Gets a single merge request. + # + # @example + # Gitlab.merge_request(5, 36) + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of a merge request. + # @return 'source_branch', :target_branch => 'target_branch') + # Gitlab.create_merge_request(5, 'New merge request', + # :source_branch => 'source_branch', :target_branch => 'target_branch', :assignee_id => 42) + # + # @param [Integer] project The ID of a project. + # @param [String] title The title of a merge request. + # @param [Hash] options A customizable set of options. + # @option options [String] :source_branch (required) The source branch name. + # @option options [String] :target_branch (required) The target branch name. + # @option options [Integer] :assignee_id (optional) The ID of a user to assign merge request. + # @return [Gitlab::ObjectifiedHash] Information about created merge request. + def create_merge_request(project, title, options={}) + check_attributes!(options, [:source_branch, :target_branch]) + + body = {:title => title}.merge(options) + post("/projects/#{project}/merge_requests", :body => body) + end + + # Updates a merge request. + # + # @example + # Gitlab.update_merge_request(5, 42, :title => 'New title') + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of a merge request. + # @param [Hash] options A customizable set of options. + # @option options [String] :title The title of a merge request. + # @option options [String] :source_branch The source branch name. + # @option options [String] :target_branch The target branch name. + # @option options [Integer] :assignee_id The ID of a user to assign merge request. + # @option options [String] :state_event New state (close|reopen|merge). + # @return [Gitlab::ObjectifiedHash] Information about updated merge request. + def update_merge_request(project, id, options={}) + put("/projects/#{project}/merge_request/#{id}", :body => options) + end + + # Adds a comment to a merge request. + # + # @example + # Gitlab.create_merge_request_comment(5, 1, "Awesome merge!") + # Gitlab.create_merge_request_comment('gitlab', 1, "Awesome merge!") + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of a merge request. + # @param [String] note The content of a comment. + # @return [Gitlab::ObjectifiedHash] Information about created merge request comment. + def create_merge_request_comment(project, id, note) + post("/projects/#{project}/merge_request/#{id}/comments", :body => {:note => note}) + end + + # Gets the comments on a merge request. + # + # @example + # Gitlab.merge_request_comments(5, 1) + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of a merge request. + # @return [Gitlab::ObjectifiedHash] The merge request's comments. + def merge_request_comments(project, id) + get("/projects/#{project}/merge_request/#{id}/comments") + end + + private + + def check_attributes!(options, attrs) + attrs.each do |attr| + unless options.has_key?(attr) || options.has_key?(attr.to_s) + raise Gitlab::Error::MissingAttributes.new("Missing '#{attr}' parameter") + end + end + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/client/milestones.rb b/lib/gitlab-cli/lib/gitlab/client/milestones.rb new file mode 100644 index 000000000..4ba4d5e87 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client/milestones.rb @@ -0,0 +1,57 @@ +class Gitlab::Client + # Defines methods related to milestones. + module Milestones + # Gets a list of project's milestones. + # + # @example + # Gitlab.milestones(5) + # + # @param [Integer] project The ID of a project. + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def milestones(project, options={}) + get("/projects/#{project}/milestones", :query => options) + end + + # Gets a single milestone. + # + # @example + # Gitlab.milestone(5, 36) + # + # @param [Integer, String] project The ID of a project. + # @param [Integer] id The ID of a milestone. + # @return [Gitlab::ObjectifiedHash] + def milestone(project, id) + get("/projects/#{project}/milestones/#{id}") + end + + # Creates a new milestone. + # + # @param [Integer] project The ID of a project. + # @param [String] title The title of a milestone. + # @param [Hash] options A customizable set of options. + # @option options [String] :description The description of a milestone. + # @option options [String] :due_date The due date of a milestone. + # @return [Gitlab::ObjectifiedHash] Information about created milestone. + def create_milestone(project, title, options={}) + body = {:title => title}.merge(options) + post("/projects/#{project}/milestones", :body => body) + end + + # Updates a milestone. + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of a milestone. + # @param [Hash] options A customizable set of options. + # @option options [String] :title The title of a milestone. + # @option options [String] :description The description of a milestone. + # @option options [String] :due_date The due date of a milestone. + # @option options [String] :state_event The state of a milestone ('close' or 'activate'). + # @return [Gitlab::ObjectifiedHash] Information about updated milestone. + def edit_milestone(project, id, options={}) + put("/projects/#{project}/milestones/#{id}", :body => options) + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/client/notes.rb b/lib/gitlab-cli/lib/gitlab/client/notes.rb new file mode 100644 index 000000000..7d782e8e2 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client/notes.rb @@ -0,0 +1,106 @@ +class Gitlab::Client + # Defines methods related to notes. + module Notes + # Gets a list of projects notes. + # + # @example + # Gitlab.notes(5) + # + # @param [Integer] project The ID of a project. + # @return [Array] + def notes(project) + get("/projects/#{project}/notes") + end + + # Gets a list of notes for a issue. + # + # @example + # Gitlab.issue_notes(5, 10) + # + # @param [Integer] project The ID of a project. + # @param [Integer] issue The ID of an issue. + # @return [Array] + def issue_notes(project, issue) + get("/projects/#{project}/issues/#{issue}/notes") + end + + # Gets a list of notes for a snippet. + # + # @example + # Gitlab.snippet_notes(5, 1) + # + # @param [Integer] project The ID of a project. + # @param [Integer] snippet The ID of a snippet. + # @return [Array] + def snippet_notes(project, snippet) + get("/projects/#{project}/snippets/#{snippet}/notes") + end + + # Gets a single wall note. + # + # @example + # Gitlab.note(5, 15) + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of a note. + # @return [Gitlab::ObjectifiedHash] + def note(project, id) + get("/projects/#{project}/notes/#{id}") + end + + # Gets a single issue note. + # + # @example + # Gitlab.issue_note(5, 10, 1) + # + # @param [Integer] project The ID of a project. + # @param [Integer] issue The ID of an issue. + # @param [Integer] id The ID of a note. + # @return [Gitlab::ObjectifiedHash] + def issue_note(project, issue, id) + get("/projects/#{project}/issues/#{issue}/notes/#{id}") + end + + # Gets a single snippet note. + # + # @example + # Gitlab.snippet_note(5, 11, 3) + # + # @param [Integer] project The ID of a project. + # @param [Integer] snippet The ID of a snippet. + # @param [Integer] id The ID of an note. + # @return [Gitlab::ObjectifiedHash] + def snippet_note(project, snippet, id) + get("/projects/#{project}/snippets/#{snippet}/notes/#{id}") + end + + # Creates a new wall note. + # + # @param [Integer] project The ID of a project. + # @param [String] body The body of a note. + # @return [Gitlab::ObjectifiedHash] Information about created note. + def create_note(project, body) + post("/projects/#{project}/notes", :body => {:body => body}) + end + + # Creates a new issue note. + # + # @param [Integer] project The ID of a project. + # @param [Integer] issue The ID of an issue. + # @param [String] body The body of a note. + # @return [Gitlab::ObjectifiedHash] Information about created note. + def create_issue_note(project, issue, body) + post("/projects/#{project}/issues/#{issue}/notes", :body => {:body => body}) + end + + # Creates a new snippet note. + # + # @param [Integer] project The ID of a project. + # @param [Integer] snippet The ID of a snippet. + # @param [String] body The body of a note. + # @return [Gitlab::ObjectifiedHash] Information about created note. + def create_snippet_note(project, snippet, body) + post("/projects/#{project}/snippets/#{snippet}/notes", :body => {:body => body}) + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/client/projects.rb b/lib/gitlab-cli/lib/gitlab/client/projects.rb new file mode 100644 index 000000000..04ea682aa --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client/projects.rb @@ -0,0 +1,300 @@ +class Gitlab::Client + # Defines methods related to projects. + module Projects + # Gets a list of projects owned by the authenticated user. + # + # @example + # Gitlab.projects + # + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @option options [String] :scope Scope of projects. 'owned' for list of projects owned by the authenticated user, 'all' to get all projects (admin only) + # @return [Array] + def projects(options={}) + if (options[:scope]) + get("/projects/#{options[:scope]}", :query => options) + else + get("/projects", :query => options) + end + end + + # Gets information about a project. + # + # @example + # Gitlab.project(3) + # Gitlab.project('gitlab') + # + # @param [Integer, String] id The ID or name of a project. + # @return [Gitlab::ObjectifiedHash] + def project(id) + get("/projects/#{id}") + end + + # Gets a list of project events. + # + # @example + # Gitlab.project_events(42) + # Gitlab.project_events('gitlab') + # + # @param [Integer, String] project The ID or name of a project. + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def project_events(project, options={}) + get("/projects/#{project}/events", :query => options) + end + + # Creates a new project. + # + # @example + # Gitlab.create_project('gitlab') + # Gitlab.create_project('viking', :description => 'Awesome project') + # Gitlab.create_project('Red', :wall_enabled => false) + # + # @param [String] name The name of a project. + # @param [Hash] options A customizable set of options. + # @option options [String] :description The description of a project. + # @option options [String] :default_branch The default branch of a project. + # @option options [String] :group_id The group in which to create a project. + # @option options [String] :namespace_id The namespace in which to create a project. + # @option options [Boolean] :wiki_enabled The wiki integration for a project (0 = false, 1 = true). + # @option options [Boolean] :wall_enabled The wall functionality for a project (0 = false, 1 = true). + # @option options [Boolean] :issues_enabled The issues integration for a project (0 = false, 1 = true). + # @option options [Boolean] :snippets_enabled The snippets integration for a project (0 = false, 1 = true). + # @option options [Boolean] :merge_requests_enabled The merge requests functionality for a project (0 = false, 1 = true). + # @option options [Boolean] :public The setting for making a project public (0 = false, 1 = true). + # @option options [Integer] :user_id The user/owner id of a project. + # @return [Gitlab::ObjectifiedHash] Information about created project. + def create_project(name, options={}) + url = options[:user_id] ? "/projects/user/#{options[:user_id]}" : "/projects" + post(url, :body => {:name => name}.merge(options)) + end + + # Deletes a project. + # + # @example + # Gitlab.delete_project(4) + # + # @param [Integer, String] id The ID or name of a project. + # @return [Gitlab::ObjectifiedHash] Information about deleted project. + def delete_project(id) + delete("/projects/#{id}") + end + + # Gets a list of project team members. + # + # @example + # Gitlab.team_members(42) + # Gitlab.team_members('gitlab') + # + # @param [Integer, String] project The ID or name of a project. + # @param [Hash] options A customizable set of options. + # @option options [String] :query The search query. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def team_members(project, options={}) + get("/projects/#{project}/members", :query => options) + end + + # Gets a project team member. + # + # @example + # Gitlab.team_member('gitlab', 2) + # + # @param [Integer, String] project The ID or name of a project. + # @param [Integer] id The ID of a project team member. + # @return [Gitlab::ObjectifiedHash] + def team_member(project, id) + get("/projects/#{project}/members/#{id}") + end + + # Adds a user to project team. + # + # @example + # Gitlab.add_team_member('gitlab', 2, 40) + # + # @param [Integer, String] project The ID or name of a project. + # @param [Integer] id The ID of a user. + # @param [Integer] access_level The access level to project. + # @param [Hash] options A customizable set of options. + # @return [Gitlab::ObjectifiedHash] Information about added team member. + def add_team_member(project, id, access_level) + post("/projects/#{project}/members", :body => {:user_id => id, :access_level => access_level}) + end + + # Updates a team member's project access level. + # + # @example + # Gitlab.edit_team_member('gitlab', 3, 20) + # + # @param [Integer, String] project The ID or name of a project. + # @param [Integer] id The ID of a user. + # @param [Integer] access_level The access level to project. + # @param [Hash] options A customizable set of options. + # @return [Array] Information about updated team member. + def edit_team_member(project, id, access_level) + put("/projects/#{project}/members/#{id}", :body => {:access_level => access_level}) + end + + # Removes a user from project team. + # + # @example + # Gitlab.remove_team_member('gitlab', 2) + # + # @param [Integer, String] project The ID or name of a project. + # @param [Integer] id The ID of a user. + # @param [Hash] options A customizable set of options. + # @return [Gitlab::ObjectifiedHash] Information about removed team member. + def remove_team_member(project, id) + delete("/projects/#{project}/members/#{id}") + end + + # Gets a list of project hooks. + # + # @example + # Gitlab.project_hooks(42) + # Gitlab.project_hooks('gitlab') + # + # @param [Integer, String] project The ID or name of a project. + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def project_hooks(project, options={}) + get("/projects/#{project}/hooks", :query => options) + end + + # Gets a project hook. + # + # @example + # Gitlab.project_hook(42, 5) + # Gitlab.project_hook('gitlab', 5) + # + # @param [Integer, String] project The ID or name of a project. + # @param [Integer] id The ID of a hook. + # @return [Gitlab::ObjectifiedHash] + def project_hook(project, id) + get("/projects/#{project}/hooks/#{id}") + end + + # Adds a new hook to the project. + # + # @example + # Gitlab.add_project_hook(42, 'https://api.example.net/v1/webhooks/ci') + # + # @param [Integer, String] project The ID or name of a project. + # @param [String] url The hook URL. + # @param [Hash] options Events list (`{push_events: true, merge_requests_events: false}`). + # @return [Gitlab::ObjectifiedHash] Information about added hook. + def add_project_hook(project, url, options = {}) + available_events = [:push_events, :merge_requests_events, :issues_events] + passed_events = available_events.select { |event| options[event] } + events = Hash[passed_events.map { |event| [event, options[event]] }] + + post("/projects/#{project}/hooks", :body => {:url => url}.merge(events)) + end + + # Updates a project hook URL. + # + # @example + # Gitlab.edit_project_hook(42, 1, 'https://api.example.net/v1/webhooks/ci') + # + # @param [Integer, String] project The ID or name of a project. + # @param [Integer] id The ID of the hook. + # @param [String] url The hook URL. + # @return [Gitlab::ObjectifiedHash] Information about updated hook. + def edit_project_hook(project, id, url) + put("/projects/#{project}/hooks/#{id}", :body => {:url => url}) + end + + # Deletes a hook from project. + # + # @example + # Gitlab.delete_project_hook('gitlab', 4) + # + # @param [Integer, String] project The ID or name of a project. + # @param [String] id The ID of the hook. + # @return [Gitlab::ObjectifiedHash] Information about deleted hook. + def delete_project_hook(project, id) + delete("/projects/#{project}/hooks/#{id}") + end + + # Mark this project as forked from the other + # + # @example + # Gitlab.make_forked(42, 24) + # + # @param [Integer, String] project The ID or name of a project. + # @param [Integer] id The ID of the project it is forked from. + # @return [Gitlab::ObjectifiedHash] Information about the forked project. + def make_forked_from(project, id) + post("/projects/#{project}/fork/#{id}") + end + + # Remove a forked_from relationship for a project. + # + # @example + # Gitlab.remove_forked(42) + # + # @param [Integer, String] project The ID or name of a project. + # @param [Integer] project The ID of the project it is forked from + # @return [Gitlab::ObjectifiedHash] Information about the forked project. + def remove_forked(project) + delete("/projects/#{project}/fork") + end + + # Gets a project deploy keys. + # + # @example + # Gitlab.deploy_keys(42) + # + # @param [Integer] project The ID of a project. + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def deploy_keys(project, options={}) + get("/projects/#{project}/keys", :query => options) + end + + # Gets a single project deploy key. + # + # @example + # Gitlab.deploy_key(42, 1) + # + # @param [Integer, String] project The ID of a project. + # @param [Integer] id The ID of a deploy key. + # @return [Gitlab::ObjectifiedHash] + def deploy_key(project, id) + get("/projects/#{project}/keys/#{id}") + end + + # Creates a new deploy key. + # + # @example + # Gitlab.create_deploy_key(42, 'My Key', 'Key contents') + # + # @param [Integer] project The ID of a project. + # @param [String] title The title of a deploy key. + # @param [String] key The content of a deploy key. + # @return [Gitlab::ObjectifiedHash] Information about created deploy key. + def create_deploy_key(project, title, key) + post("/projects/#{project}/keys", body: {title: title, key: key}) + end + + # Deletes a deploy key from project. + # + # @example + # Gitlab.delete_deploy_key(42, 1) + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of a deploy key. + # @return [Gitlab::ObjectifiedHash] Information about deleted deploy key. + def delete_deploy_key(project, id) + delete("/projects/#{project}/keys/#{id}") + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/client/repositories.rb b/lib/gitlab-cli/lib/gitlab/client/repositories.rb new file mode 100644 index 000000000..f489e5009 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client/repositories.rb @@ -0,0 +1,89 @@ +class Gitlab::Client + # Defines methods related to repositories. + module Repositories + + def trees(project, options={}) + get "/projects/#{project}/repository/tree", query: options + end + alias_method :repo_trees, :trees + + def files(project, file_path, ref) + get "/projects/#{project}/repository/files", query: {file_path: file_path, ref: ref} + end + alias_method :repo_files, :files + + # Gets a list of project repository tags. + # + # @example + # Gitlab.tags(42) + # + # @param [Integer] project The ID of a project. + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def tags(project, options={}) + get("/projects/#{project}/repository/tags", :query => options) + end + alias_method :repo_tags, :tags + + # Creates a new project repository tag. + # + # @example + # Gitlab.create_tag(42,'new_tag','master') + # + # @param [Integer] project The ID of a project. + # @param [String] tag_name The name of the new tag. + # @param [String] ref The ref (commit sha, branch name, or another tag) the tag will point to. + # @return [Gitlab::ObjectifiedHash] + def create_tag(project, tag_name, ref) + post("/projects/#{project}/repository/tags", body: {tag_name: tag_name, ref: ref}) + end + alias_method :repo_create_tag, :create_tag + + # Gets a list of project commits. + # + # @example + # Gitlab.commits('viking') + # Gitlab.repo_commits('gitlab', :ref_name => 'api') + # + # @param [Integer] project The ID of a project. + # @param [Hash] options A customizable set of options. + # @option options [String] :ref_name The branch or tag name of a project repository. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def commits(project, options={}) + get("/projects/#{project}/repository/commits", :query => options) + end + alias_method :repo_commits, :commits + + # Gets a specific commit identified by the commit hash or name of a branch or tag. + # + # @example + # Gitlab.commit(42, '6104942438c14ec7bd21c6cd5bd995272b3faff6') + # Gitlab.repo_commit(3, 'ed899a2f4b50b4370feeea94676502b42383c746') + # + # @param [Integer] project The ID of a project. + # @param [String] sha The commit hash or name of a repository branch or tag + # @return [Gitlab::ObjectifiedHash] + def commit(project, sha) + get("/projects/#{project}/repository/commits/#{sha}") + end + alias_method :repo_commit, :commit + + # Get the diff of a commit in a project. + # + # @example + # Gitlab.commit_diff(42, '6104942438c14ec7bd21c6cd5bd995272b3faff6') + # Gitlab.repo_commit_diff(3, 'ed899a2f4b50b4370feeea94676502b42383c746') + # + # @param [Integer] project The ID of a project. + # @param [String] sha The name of a repository branch or tag or if not given the default branch. + # @return [Gitlab::ObjectifiedHash] + def commit_diff(project, sha) + get("/projects/#{project}/repository/commits/#{sha}/diff") + end + alias_method :repo_commit_diff, :commit_diff + end +end diff --git a/lib/gitlab-cli/lib/gitlab/client/snippets.rb b/lib/gitlab-cli/lib/gitlab/client/snippets.rb new file mode 100644 index 000000000..594d37402 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client/snippets.rb @@ -0,0 +1,86 @@ +class Gitlab::Client + # Defines methods related to snippets. + module Snippets + # Gets a list of project's snippets. + # + # @example + # Gitlab.snippets(42) + # + # @param [Integer] project The ID of a project. + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Gitlab::ObjectifiedHash] + def snippets(project, options={}) + get("/projects/#{project}/snippets", :query => options) + end + + # Gets information about a snippet. + # + # @example + # Gitlab.snippet(2, 14) + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of a snippet. + # @return [Gitlab::ObjectifiedHash] + def snippet(project, id) + get("/projects/#{project}/snippets/#{id}") + end + + # Creates a new snippet. + # + # @example + # Gitlab.create_snippet(42, {:title => 'REST', :file_name => 'api.rb', :code => 'some code'}) + # + # @param [Integer] project The ID of a project. + # @param [Hash] options A customizable set of options. + # @option options [String] :title (required) The title of a snippet. + # @option options [String] :file_name (required) The name of a snippet file. + # @option options [String] :code (required) The content of a snippet. + # @option options [String] :lifetime (optional) The expiration date of a snippet. + # @return [Gitlab::ObjectifiedHash] Information about created snippet. + def create_snippet(project, options={}) + check_attributes!(options, [:title, :file_name, :code]) + post("/projects/#{project}/snippets", :body => options) + end + + # Updates a snippet. + # + # @example + # Gitlab.edit_snippet(42, 34, :file_name => 'README.txt') + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of a snippet. + # @param [Hash] options A customizable set of options. + # @option options [String] :title The title of a snippet. + # @option options [String] :file_name The name of a snippet file. + # @option options [String] :code The content of a snippet. + # @option options [String] :lifetime The expiration date of a snippet. + # @return [Gitlab::ObjectifiedHash] Information about updated snippet. + def edit_snippet(project, id, options={}) + put("/projects/#{project}/snippets/#{id}", :body => options) + end + + # Deletes a snippet. + # + # @example + # Gitlab.delete_snippet(2, 14) + # + # @param [Integer] project The ID of a project. + # @param [Integer] id The ID of a snippet. + # @return [Gitlab::ObjectifiedHash] Information about deleted snippet. + def delete_snippet(project, id) + delete("/projects/#{project}/snippets/#{id}") + end + + private + + def check_attributes!(options, attrs) + attrs.each do |attr| + unless options.has_key?(attr) || options.has_key?(attr.to_s) + raise Gitlab::Error::MissingAttributes.new("Missing '#{attr}' parameter") + end + end + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/client/system_hooks.rb b/lib/gitlab-cli/lib/gitlab/client/system_hooks.rb new file mode 100644 index 000000000..59db4f924 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client/system_hooks.rb @@ -0,0 +1,58 @@ +class Gitlab::Client + # Defines methods related to system hooks. + module SystemHooks + # Gets a list of system hooks. + # + # @example + # Gitlab.hooks + # Gitlab.system_hooks + # + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def hooks(options={}) + get("/hooks", query: options) + end + alias_method :system_hooks, :hooks + + # Adds a new system hook. + # + # @example + # Gitlab.add_hook('http://example.com/hook') + # Gitlab.add_system_hook('https://api.example.net/v1/hook') + # + # @param [String] url The hook URL. + # @return [Gitlab::ObjectifiedHash] + def add_hook(url) + post("/hooks", :body => {:url => url}) + end + alias_method :add_system_hook, :add_hook + + # Tests a system hook. + # + # @example + # Gitlab.hook(3) + # Gitlab.system_hook(12) + # + # @param [Integer] id The ID of a system hook. + # @return [Array] + def hook(id) + get("/hooks/#{id}") + end + alias_method :system_hook, :hook + + # Deletes a new system hook. + # + # @example + # Gitlab.delete_hook(3) + # Gitlab.delete_system_hook(12) + # + # @param [Integer] id The ID of a system hook. + # @return [Gitlab::ObjectifiedHash] + def delete_hook(id) + delete("/hooks/#{id}") + end + alias_method :delete_system_hook, :delete_hook + end +end diff --git a/lib/gitlab-cli/lib/gitlab/client/users.rb b/lib/gitlab-cli/lib/gitlab/client/users.rb new file mode 100644 index 000000000..3fc83cd1b --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/client/users.rb @@ -0,0 +1,123 @@ +class Gitlab::Client + # Defines methods related to users. + module Users + # Gets a list of users. + # + # @example + # Gitlab.users + # + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def users(options={}) + get("/users", :query => options) + end + + # Gets information about a user. + # Will return information about an authorized user if no ID passed. + # + # @example + # Gitlab.user + # Gitlab.user(2) + # + # @param [Integer] id The ID of a user. + # @return [Gitlab::ObjectifiedHash] + def user(id=nil) + id.to_i.zero? ? get("/user") : get("/users/#{id}") + end + + # Creates a new user. + # Requires authentication from an admin account. + # + # @param [String] email The email of a user. + # @param [String] password The password of a user. + # @param [Hash] options A customizable set of options. + # @option options [String] :name The name of a user. Defaults to email. + # @option options [String] :skype The skype of a user. + # @option options [String] :linkedin The linkedin of a user. + # @option options [String] :twitter The twitter of a user. + # @option options [Integer] :projects_limit The limit of projects for a user. + # @return [Gitlab::ObjectifiedHash] Information about created user. + def create_user(email, password, options={}) + body = {:email => email, :password => password, :name => email}.merge(options) + post("/users", :body => body) + end + + # Updates a user. + # + # @param [Integer] id The ID of a user. + # @param [Hash] options A customizable set of options. + # @option options [String] email The email of a user. + # @option options [String] password The password of a user. + # @option options [String] :name The name of a user. Defaults to email. + # @option options [String] :skype The skype of a user. + # @option options [String] :linkedin The linkedin of a user. + # @option options [String] :twitter The twitter of a user. + # @option options [Integer] :projects_limit The limit of projects for a user. + # @return [Gitlab::ObjectifiedHash] Information about created user. + def edit_user(user_id, options={}) + put("/users/#{user_id}", :body => options) + end + + # Creates a new user session. + # + # @example + # Gitlab.session('jack@example.com', 'secret12345') + # + # @param [String] email The email of a user. + # @param [String] password The password of a user. + # @return [Gitlab::ObjectifiedHash] + # @note This method doesn't require private_token to be set. + def session(email, password) + post("/session", :body => {:email => email, :password => password}) + end + + # Gets a list of user's SSH keys. + # + # @example + # Gitlab.ssh_keys + # + # @param [Hash] options A customizable set of options. + # @option options [Integer] :page The page number. + # @option options [Integer] :per_page The number of results per page. + # @return [Array] + def ssh_keys(options={}) + get("/user/keys", :query => options) + end + + # Gets information about SSH key. + # + # @example + # Gitlab.ssh_key(1) + # + # @param [Integer] id The ID of a user's SSH key. + # @return [Gitlab::ObjectifiedHash] + def ssh_key(id) + get("/user/keys/#{id}") + end + + # Creates a new SSH key. + # + # @example + # Gitlab.create_ssh_key('key title', 'key body') + # + # @param [String] title The title of an SSH key. + # @param [String] key The SSH key body. + # @return [Gitlab::ObjectifiedHash] Information about created SSH key. + def create_ssh_key(title, key) + post("/user/keys", :body => {:title => title, :key => key}) + end + + # Deletes an SSH key. + # + # @example + # Gitlab.delete_ssh_key(1) + # + # @param [Integer] id The ID of a user's SSH key. + # @return [Gitlab::ObjectifiedHash] Information about deleted SSH key. + def delete_ssh_key(id) + delete("/user/keys/#{id}") + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/configuration.rb b/lib/gitlab-cli/lib/gitlab/configuration.rb new file mode 100644 index 000000000..b36ed5a7d --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/configuration.rb @@ -0,0 +1,39 @@ +module Gitlab + # Defines constants and methods related to configuration. + module Configuration + # An array of valid keys in the options hash when configuring a Gitlab::API. + VALID_OPTIONS_KEYS = [:endpoint, :private_token, :user_agent, :sudo, :httparty].freeze + + # The user agent that will be sent to the API endpoint if none is set. + DEFAULT_USER_AGENT = "Gitlab Ruby Gem #{Gitlab::VERSION}".freeze + + # @private + attr_accessor(*VALID_OPTIONS_KEYS) + + # Sets all configuration options to their default values + # when this module is extended. + def self.extended(base) + base.reset + end + + # Convenience method to allow configuration options to be set in a block. + def configure + yield self + end + + # Creates a hash of options and their values. + def options + VALID_OPTIONS_KEYS.inject({}) do |option, key| + option.merge!(key => send(key)) + end + end + + # Resets all configuration options to the defaults. + def reset + self.endpoint = ENV['GITLAB_API_ENDPOINT'] + self.private_token = ENV['GITLAB_API_PRIVATE_TOKEN'] + self.sudo = nil + self.user_agent = DEFAULT_USER_AGENT + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/error.rb b/lib/gitlab-cli/lib/gitlab/error.rb new file mode 100644 index 000000000..96a8a39d6 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/error.rb @@ -0,0 +1,42 @@ +module Gitlab + module Error + # Custom error class for rescuing from all Gitlab errors. + class Error < StandardError; end + + # Raise when attributes are missing. + class MissingAttributes < Error; end + + # Raised when API endpoint credentials not configured. + class MissingCredentials < Error; end + + # Raised when impossible to parse response body. + class Parsing < Error; end + + # Raised when API endpoint returns the HTTP status code 400. + class BadRequest < Error; end + + # Raised when API endpoint returns the HTTP status code 401. + class Unauthorized < Error; end + + # Raised when API endpoint returns the HTTP status code 403. + class Forbidden < Error; end + + # Raised when API endpoint returns the HTTP status code 404. + class NotFound < Error; end + + # Raised when API endpoint returns the HTTP status code 405. + class MethodNotAllowed < Error; end + + # Raised when API endpoint returns the HTTP status code 409. + class Conflict < Error; end + + # Raised when API endpoint returns the HTTP status code 500. + class InternalServerError < Error; end + + # Raised when API endpoint returns the HTTP status code 502. + class BadGateway < Error; end + + # Raised when API endpoint returns the HTTP status code 503. + class ServiceUnavailable < Error; end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/help.rb b/lib/gitlab-cli/lib/gitlab/help.rb new file mode 100644 index 000000000..6ead8af94 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/help.rb @@ -0,0 +1,44 @@ +require 'gitlab' +require 'gitlab/cli_helpers' + +module Gitlab::Help + extend Gitlab::CLI::Helpers + + def self.get_help(methods,cmd=nil) + help = '' + + if cmd.nil? || cmd == 'help' + help = actions_table + else + ri_cmd = `which ri`.chomp + + if $? == 0 + namespace = methods.select {|m| m[:name] === cmd }.map {|m| m[:owner]+'.'+m[:name] }.shift + + if namespace + begin + ri_output = `#{ri_cmd} -T #{namespace} 2>&1`.chomp + + if $? == 0 + ri_output.gsub!(/#{cmd}\((.*?)\)/, cmd+' \1') + ri_output.gsub!(/Gitlab\./, 'gitlab> ') + ri_output.gsub!(/Gitlab\..+$/, '') + ri_output.gsub!(/\,\s?/, ' ') + help = ri_output + else + help = "Ri docs not found for #{namespace}, please install the docs to use 'help'" + end + rescue => e + puts e.message + end + else + help = "Unknown command: #{cmd}" + end + else + help = "'ri' tool not found in your PATH, please install it to use the help." + end + end + + puts help + end +end diff --git a/lib/gitlab-cli/lib/gitlab/objectified_hash.rb b/lib/gitlab-cli/lib/gitlab/objectified_hash.rb new file mode 100644 index 000000000..87ca2fc77 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/objectified_hash.rb @@ -0,0 +1,24 @@ +module Gitlab + # Converts hashes to the objects. + class ObjectifiedHash + # Creates a new ObjectifiedHash object. + def initialize(hash) + @hash = hash + @data = hash.inject({}) do |data, (key,value)| + value = ObjectifiedHash.new(value) if value.is_a? Hash + data[key.to_s] = value + data + end + end + + def to_hash + @hash + end + alias_method :to_h, :to_hash + + # Delegate to ObjectifiedHash. + def method_missing(key) + @data.key?(key.to_s) ? @data[key.to_s] : nil + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/request.rb b/lib/gitlab-cli/lib/gitlab/request.rb new file mode 100644 index 000000000..64a8f6b4c --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/request.rb @@ -0,0 +1,113 @@ +require 'httparty' +require 'json' + +module Gitlab + # @private + class Request + include HTTParty + format :json + headers 'Accept' => 'application/json' + parser Proc.new { |body, _| parse(body) } + + attr_accessor :private_token + + # Converts the response body to an ObjectifiedHash. + def self.parse(body) + body = decode(body) + + if body.is_a? Hash + ObjectifiedHash.new body + elsif body.is_a? Array + body.collect! { |e| ObjectifiedHash.new(e) } + else + raise Error::Parsing.new "Couldn't parse a response body" + end + end + + # Decodes a JSON response into Ruby object. + def self.decode(response) + begin + JSON.load response + rescue JSON::ParserError + raise Error::Parsing.new "The response is not a valid JSON" + end + end + + def get(path, options={}) + set_httparty_config(options) + set_private_token_header(options) + validate self.class.get(path, options) + end + + def post(path, options={}) + set_httparty_config(options) + set_private_token_header(options, path) + validate self.class.post(path, options) + end + + def put(path, options={}) + set_httparty_config(options) + set_private_token_header(options) + validate self.class.put(path, options) + end + + def delete(path, options={}) + set_httparty_config(options) + set_private_token_header(options) + validate self.class.delete(path, options) + end + + # Checks the response code for common errors. + # Returns parsed response for successful requests. + def validate(response) + case response.code + when 400; raise Error::BadRequest.new error_message(response) + when 401; raise Error::Unauthorized.new error_message(response) + when 403; raise Error::Forbidden.new error_message(response) + when 404; raise Error::NotFound.new error_message(response) + when 405; raise Error::MethodNotAllowed.new error_message(response) + when 409; raise Error::Conflict.new error_message(response) + when 500; raise Error::InternalServerError.new error_message(response) + when 502; raise Error::BadGateway.new error_message(response) + when 503; raise Error::ServiceUnavailable.new error_message(response) + end + + response.parsed_response + end + + # Sets a base_uri and default_params for requests. + # @raise [Error::MissingCredentials] if endpoint not set. + def set_request_defaults(endpoint, private_token, sudo=nil) + raise Error::MissingCredentials.new("Please set an endpoint to API") unless endpoint + @private_token = private_token + + self.class.base_uri endpoint + self.class.default_params :sudo => sudo + self.class.default_params.delete(:sudo) if sudo.nil? + end + + private + + # Sets a PRIVATE-TOKEN header for requests. + # @raise [Error::MissingCredentials] if private_token not set. + def set_private_token_header(options, path=nil) + unless path == '/session' + raise Error::MissingCredentials.new("Please set a private_token for user") unless @private_token + options[:headers] = {'PRIVATE-TOKEN' => @private_token} + end + end + + # Set HTTParty configuration + # @see https://github.com/jnunemaker/httparty + def set_httparty_config(options) + if self.httparty + options.merge!(self.httparty) + end + end + + def error_message(response) + "Server responded with code #{response.code}, message: #{response.parsed_response.message}. " \ + "Request URI: #{response.request.base_uri}#{response.request.path}" + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/shell.rb b/lib/gitlab-cli/lib/gitlab/shell.rb new file mode 100644 index 000000000..9253b61e3 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/shell.rb @@ -0,0 +1,51 @@ +require 'gitlab' +require 'gitlab/help' +require 'gitlab/cli_helpers' +require 'readline' + +class Gitlab::Shell + extend Gitlab::CLI::Helpers + + def self.start + actions = Gitlab.actions + + comp = proc { |s| actions.map(&:to_s).grep(/^#{Regexp.escape(s)}/) } + + Readline.completion_proc = comp + Readline.completion_append_character = ' ' + + client = Gitlab::Client.new(endpoint: '') + + while buf = Readline.readline('gitlab> ', true) + next if buf.nil? || buf.empty? + break if buf == 'exit' + + buf = buf.scan(/["][^"]+["]|\S+/).map { |word| word.gsub(/^['"]|['"]$/,'') } + cmd = buf.shift + args = buf.count > 0 ? buf : [] + + if cmd == 'help' + methods = [] + + actions.each do |action| + methods << { + name: action.to_s, + owner: client.method(action).owner.to_s + } + end + + args[0].nil? ? Gitlab::Help.get_help(methods) : Gitlab::Help.get_help(methods, args[0]) + next + end + + data = if actions.include?(cmd.to_sym) + confirm_command(cmd) + gitlab_helper(cmd, args) + else + "'#{cmd}' is not a valid command. See the 'help' for a list of valid commands." + end + + output_table(cmd, args, data) + end + end +end diff --git a/lib/gitlab-cli/lib/gitlab/version.rb b/lib/gitlab-cli/lib/gitlab/version.rb new file mode 100644 index 000000000..42ba291e0 --- /dev/null +++ b/lib/gitlab-cli/lib/gitlab/version.rb @@ -0,0 +1,3 @@ +module Gitlab + VERSION = "3.2.0" +end diff --git a/lib/gitlab-cli/spec/fixtures/branch.json b/lib/gitlab-cli/spec/fixtures/branch.json new file mode 100644 index 000000000..34a02081f --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/branch.json @@ -0,0 +1 @@ +{"name":"api","commit":{"id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","parents":[{"id":"949b1df930bedace1dbd755aaa4a82e8c451a616"}],"tree":"f8c4b21c036339f92fcc5482aa28a41250553b27","message":"API: expose issues project id","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-07-25T04:22:21-07:00","committed_date":"2012-07-25T04:22:21-07:00"},"protected": true} diff --git a/lib/gitlab-cli/spec/fixtures/branches.json b/lib/gitlab-cli/spec/fixtures/branches.json new file mode 100644 index 000000000..05a39447d --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/branches.json @@ -0,0 +1 @@ +[{"name":"api","commit":{"id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","parents":[{"id":"949b1df930bedace1dbd755aaa4a82e8c451a616"}],"tree":"f8c4b21c036339f92fcc5482aa28a41250553b27","message":"API: expose issues project id","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-07-25T04:22:21-07:00","committed_date":"2012-07-25T04:22:21-07:00"}},{"name":"dashboard-feed","commit":{"id":"f8f6ff065eccc6ede4d35ed87a09bb962b84ca25","parents":[{"id":"2cf8010792c3075824ee27d0f037aeb178cbbf7e"}],"tree":"e17f2157143d550891d4669c10b7446e4739bc6d","message":"add projects atom feed","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-05-31T23:42:02-07:00","committed_date":"2012-05-31T23:42:02-07:00"}},{"name":"master","commit":{"id":"2cf8010792c3075824ee27d0f037aeb178cbbf7e","parents":[{"id":"af226ae9c9af406c8a0e0bbdf364563495c2f432"},{"id":"e851cb07762aa464aae10e8b4b28de87c1a6f925"}],"tree":"6c6845838039f01723d91f395a1d2fa1dcc82522","message":"Merge pull request #868 from SaitoWu/bugfix/encoding\n\nBugfix/encoding","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-05-30T00:24:43-07:00","committed_date":"2012-05-30T00:24:43-07:00"}},{"name":"preview_notes","commit":{"id":"3749e0d99ac6bfbc65889b1b7a5310e14e7fe89a","parents":[{"id":"2483181f2c3d4ea7d2c68147b19bc07fc3937b0c"}],"tree":"f8c56161b0d6561568f088df9961362eb1ece88b","message":"pass project_id to notes preview path","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-08-09T23:46:27-07:00","committed_date":"2012-08-09T23:46:27-07:00"}},{"name":"refactoring","commit":{"id":"7c7761099cae83f59fe5780340e100be890847b2","parents":[{"id":"058d80b3363dd4fc4417ca4f60f76119188a2470"}],"tree":"d7d4a94c700dc0e84ee943019213d2358a49c413","message":"fix deprecation warnings","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-05-29T07:16:28-07:00","committed_date":"2012-05-29T07:16:28-07:00"}}] diff --git a/lib/gitlab-cli/spec/fixtures/comment_merge_request.json b/lib/gitlab-cli/spec/fixtures/comment_merge_request.json new file mode 100644 index 000000000..742f33377 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/comment_merge_request.json @@ -0,0 +1 @@ +{"note":"Cool Merge Request!","author":{"id":1,"username":"jsmith","email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-07-11T01:32:18Z"}} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/create_branch.json b/lib/gitlab-cli/spec/fixtures/create_branch.json new file mode 100644 index 000000000..0b011abdb --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/create_branch.json @@ -0,0 +1 @@ +{"name":"api","commit":{ "id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","message":"API: expose issues project id","parent_ids":["949b1df930bedace1dbd755aaa4a82e8c451a616"],"authored_date":"2012-07-25T04:22:21-07:00","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","committed_date":"2012-07-25T04:22:21-07:00","committer_name":"Nihad Abbasov","committer_email":"narkoz.2008@gmail.com"},"protected": false} diff --git a/lib/gitlab-cli/spec/fixtures/create_merge_request.json b/lib/gitlab-cli/spec/fixtures/create_merge_request.json new file mode 100644 index 000000000..c27435168 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/create_merge_request.json @@ -0,0 +1 @@ +{"id":2,"target_branch":"master","source_branch":"api","project_id":3,"title":"New feature","closed":false,"merged":false,"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-10-19T05:56:05Z"},"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-10-19T05:56:14Z"}} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/error_already_exists.json b/lib/gitlab-cli/spec/fixtures/error_already_exists.json new file mode 100644 index 000000000..d1070f560 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/error_already_exists.json @@ -0,0 +1 @@ +{"message": "409 Already exists"} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/group.json b/lib/gitlab-cli/spec/fixtures/group.json new file mode 100644 index 000000000..bce3581d8 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/group.json @@ -0,0 +1,60 @@ +{"id": 10, "name": "GitLab-Group", "path": "gitlab-group", "owner_id": 6, "projects": [ + { + "id": 9, + "name": "mojito", + "description": null, + "default_branch": "master", + "owner": { + "id": 6, + "username": "jose", + "email": "jose@abc.com", + "name": "Jose Jose", + "blocked": false, + "created_at": "2013-02-06T06:54:06Z" + }, + "path": "mojito", + "path_with_namespace": "gitlab-group/mojito", + "issues_enabled": true, + "merge_requests_enabled": true, + "wall_enabled": true, + "wiki_enabled": true, + "created_at": "2013-02-06T16:59:15Z", + "namespace": { + "created_at": "2013-02-06T16:58:22Z", + "id": 10, + "name": "GitLab-Group", + "owner_id": 6, + "path": "gitlab-group", + "updated_at": "2013-02-06T16:58:22Z" + } + }, + { + "id": 10, + "name": "gitlabhq", + "description": null, + "default_branch": null, + "owner": { + "id": 6, + "username": "randx", + "email": "randx@github.com", + "name": "Dmitry Z", + "blocked": false, + "created_at": "2013-02-06T06:54:06Z" + }, + "path": "gitlabhq", + "path_with_namespace": "gitlab-group/gitlabhq", + "issues_enabled": true, + "merge_requests_enabled": true, + "wall_enabled": true, + "wiki_enabled": true, + "created_at": "2013-02-06T17:02:31Z", + "namespace": { + "created_at": "2013-02-06T16:58:22Z", + "id": 10, + "name": "GitLab-Group", + "owner_id": 6, + "path": "gitlab-group", + "updated_at": "2013-02-06T16:58:22Z" + } + } +]} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/group_create.json b/lib/gitlab-cli/spec/fixtures/group_create.json new file mode 100644 index 000000000..67445f68b --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/group_create.json @@ -0,0 +1 @@ +{"id":3,"name":"Gitlab-Group","path":"gitlab-group","owner_id":1} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/group_member.json b/lib/gitlab-cli/spec/fixtures/group_member.json new file mode 100644 index 000000000..feef54322 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/group_member.json @@ -0,0 +1 @@ +{"id":2,"username":"jsmith","email":"jsmith@local.host","name":"John Smith","state":"active","created_at":"2013-09-04T18:15:30Z","access_level":10} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/group_member_delete.json b/lib/gitlab-cli/spec/fixtures/group_member_delete.json new file mode 100644 index 000000000..ff052edf6 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/group_member_delete.json @@ -0,0 +1 @@ +{"created_at":"2013-09-04T18:18:15Z","group_access":10,"group_id":3,"id":2,"notification_level":3,"updated_at":"2013-09-04T18:18:15Z","user_id":2} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/group_members.json b/lib/gitlab-cli/spec/fixtures/group_members.json new file mode 100644 index 000000000..02ddc1089 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/group_members.json @@ -0,0 +1 @@ +[{"id":1,"username":"eraymond","email":"eraymond@local.host","name":"Edward Raymond","state":"active","created_at":"2013-08-30T16:16:22Z","access_level":50},{"id":1,"username":"jsmith","email":"jsmith@local.host","name":"John Smith","state":"active","created_at":"2013-08-30T16:16:22Z","access_level":50}] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/groups.json b/lib/gitlab-cli/spec/fixtures/groups.json new file mode 100644 index 000000000..7d8b426a4 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/groups.json @@ -0,0 +1,2 @@ +[{"id": 3,"name": "ThreeGroup","path": "threegroup","owner_id": 1},{"id": 5,"name": "Five-Group","path": "five-group","owner_id": 2},{"id": 8,"name": "Eight Group","path": "eight-group","owner_id": 6} +] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/issue.json b/lib/gitlab-cli/spec/fixtures/issue.json new file mode 100644 index 000000000..9f70318a7 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/issue.json @@ -0,0 +1 @@ +{"id":33,"project_id":3,"title":"Beatae possimus nostrum nihil reiciendis laboriosam nihil delectus alias accusantium dolor unde.","description":null,"labels":[],"milestone":null,"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/issues.json b/lib/gitlab-cli/spec/fixtures/issues.json new file mode 100644 index 000000000..62e4cadd2 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/issues.json @@ -0,0 +1 @@ +[{"id":1,"project_id":1,"title":"Culpa eius recusandae suscipit autem distinctio dolorum.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":6,"project_id":2,"title":"Ut in dolorum omnis sed sit aliquam.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":12,"project_id":3,"title":"Veniam et tempore quidem eum reprehenderit cupiditate non aut velit eaque.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":21,"project_id":1,"title":"Vitae ea aliquam et quo eligendi sapiente voluptatum labore hic nihil culpa.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":26,"project_id":2,"title":"Quo enim est nihil atque placeat voluptas neque eos voluptas.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":32,"project_id":3,"title":"Deserunt tenetur impedit est beatae voluptas voluptas quaerat quisquam.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"}] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/key.json b/lib/gitlab-cli/spec/fixtures/key.json new file mode 100644 index 000000000..6595c8ceb --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/key.json @@ -0,0 +1 @@ +{"id":1,"title":"narkoz@helium","key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkUsh42Nh1yefGd1jbSELn5XsY8p5Oxmau0/1HqHnjuYOaj5X+kHccFDwtmtg9Ox8ua/+WptNsiE8IUwsD3zKgEjajgwq3gMeeFdxfXwM+tEvHOOMV9meRrgRWGYCToPbT6sR7/YMAYa7cPqWSpx/oZhYfz4XtoMv3ZZT1fZMmx3MY3HwXwW8j+obJyN2K4LN0TFi9RPgWWYn0DCyb9OccmABimt3i74WoJ/OT8r6/7swce8+OSe0Q2wBhyTtvxg2vtUcoek8Af+EZaUMBwSEzEsocOCzwQvjF5XUk5o7dJ8nP8W3RE60JWX57t16eQm7lBmumLYfszpn2isd6W7a1 narkoz@helium"} diff --git a/lib/gitlab-cli/spec/fixtures/keys.json b/lib/gitlab-cli/spec/fixtures/keys.json new file mode 100644 index 000000000..d81fca6ad --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/keys.json @@ -0,0 +1 @@ +[{"id":1,"title":"narkoz@helium","key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkUsh42Nh1yefGd1jbSELn5XsY8p5Oxmau0/1HqHnjuYOaj5X+kHccFDwtmtg9Ox8ua/+WptNsiE8IUwsD3zKgEjajgwq3gMeeFdxfXwM+tEvHOOMV9meRrgRWGYCToPbT6sR7/YMAYa7cPqWSpx/oZhYfz4XtoMv3ZZT1fZMmx3MY3HwXwW8j+obJyN2K4LN0TFi9RPgWWYn0DCyb9OccmABimt3i74WoJ/OT8r6/7swce8+OSe0Q2wBhyTtvxg2vtUcoek8Af+EZaUMBwSEzEsocOCzwQvjF5XUk5o7dJ8nP8W3RE60JWX57t16eQm7lBmumLYfszpn2isd6W7a1 narkoz@helium"}] diff --git a/lib/gitlab-cli/spec/fixtures/merge_request.json b/lib/gitlab-cli/spec/fixtures/merge_request.json new file mode 100644 index 000000000..5278f4664 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/merge_request.json @@ -0,0 +1 @@ +{"id":1,"target_branch":"master","source_branch":"api","project_id":3,"title":"New feature","closed":false,"merged":false,"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-10-19T05:56:05Z"},"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-10-19T05:56:14Z"}} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/merge_request_comments.json b/lib/gitlab-cli/spec/fixtures/merge_request_comments.json new file mode 100644 index 000000000..3b9733ef3 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/merge_request_comments.json @@ -0,0 +1 @@ +[{"note":"this is the 1st comment on the 2merge merge request","author":{"id":11,"username":"admin","email":"admin@example.com","name":"A User","state":"active","created_at":"2014-03-06T08:17:35.000Z"}},{"note":"another discussion point on the 2merge request","author":{"id":12,"username":"admin","email":"admin@example.com","name":"A User","state":"active","created_at":"2014-03-06T08:17:35.000Z"}}] diff --git a/lib/gitlab-cli/spec/fixtures/merge_requests.json b/lib/gitlab-cli/spec/fixtures/merge_requests.json new file mode 100644 index 000000000..ea32ac4e6 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/merge_requests.json @@ -0,0 +1 @@ +[{"id":1,"target_branch":"master","source_branch":"api","project_id":3,"title":"New feature","closed":false,"merged":false,"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-10-19T05:56:05Z"},"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-10-19T05:56:14Z"}}] diff --git a/lib/gitlab-cli/spec/fixtures/milestone.json b/lib/gitlab-cli/spec/fixtures/milestone.json new file mode 100644 index 000000000..94ea3d360 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/milestone.json @@ -0,0 +1 @@ +{"id":1,"project_id":3,"title":"3.0","description":"","due_date":"2012-10-22","closed":false,"updated_at":"2012-09-17T10:15:31Z","created_at":"2012-09-17T10:15:31Z"} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/milestones.json b/lib/gitlab-cli/spec/fixtures/milestones.json new file mode 100644 index 000000000..f9e309af6 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/milestones.json @@ -0,0 +1 @@ +[{"id":1,"project_id":3,"title":"3.0","description":"","due_date":"2012-10-22","closed":false,"updated_at":"2012-09-17T10:15:31Z","created_at":"2012-09-17T10:15:31Z"}] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/note.json b/lib/gitlab-cli/spec/fixtures/note.json new file mode 100644 index 000000000..3f575aed3 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/note.json @@ -0,0 +1 @@ +{"id":1201,"body":"The solution is rather tricky","author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"created_at":"2012-11-27T19:16:44Z"} diff --git a/lib/gitlab-cli/spec/fixtures/notes.json b/lib/gitlab-cli/spec/fixtures/notes.json new file mode 100644 index 000000000..15c0d8ca2 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/notes.json @@ -0,0 +1 @@ +[{"id":1201,"body":"The solution is rather tricky","author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"created_at":"2012-11-27T19:16:44Z"},{"id":1207,"body":"I know, right?","author":{"id":1,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"created_at":"2012-11-27T19:58:21Z"}] diff --git a/lib/gitlab-cli/spec/fixtures/project.json b/lib/gitlab-cli/spec/fixtures/project.json new file mode 100644 index 000000000..1f4f96028 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project.json @@ -0,0 +1 @@ +{"id":3,"code":"gitlab","name":"Gitlab","description":null,"path":"gitlab","default_branch":null,"owner":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"public":false,"issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"created_at":"2012-09-17T09:41:58Z"} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/project_commit.json b/lib/gitlab-cli/spec/fixtures/project_commit.json new file mode 100644 index 000000000..ace52b700 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_commit.json @@ -0,0 +1,13 @@ +{ + "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6", + "short_id": "6104942438c", + "title": "Sanitize for network graph", + "author_name": "randx", + "author_email": "dmitriy.zaporozhets@gmail.com", + "created_at": "2012-09-20T09:06:12+03:00", + "committed_date": "2012-09-20T09:06:12+03:00", + "authored_date": "2012-09-20T09:06:12+03:00", + "parent_ids": [ + "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba" + ] +} diff --git a/lib/gitlab-cli/spec/fixtures/project_commit_diff.json b/lib/gitlab-cli/spec/fixtures/project_commit_diff.json new file mode 100644 index 000000000..ad3dfde86 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_commit_diff.json @@ -0,0 +1,10 @@ +{ + "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files", + "new_path": "doc/update/5.4-to-6.0.md", + "old_path": "doc/update/5.4-to-6.0.md", + "a_mode": null, + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false +} diff --git a/lib/gitlab-cli/spec/fixtures/project_commits.json b/lib/gitlab-cli/spec/fixtures/project_commits.json new file mode 100644 index 000000000..58cb5020d --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_commits.json @@ -0,0 +1 @@ +[{"id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","short_id":"f7dd067490f","title":"API: expose issues project id","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-25T04:22:21-07:00"},{"id":"949b1df930bedace1dbd755aaa4a82e8c451a616","short_id":"949b1df930b","title":"API: update docs","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-25T02:35:41-07:00"},{"id":"1b95c8bff351f6718ec31ac1de1e48c57bc95d44","short_id":"1b95c8bff35","title":"API: ability to get project by id","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-25T02:18:30-07:00"},{"id":"92d98f5a0c28bffd7b070cda190b07ab72667d58","short_id":"92d98f5a0c2","title":"Merge pull request #1118 from patthoyts/pt/ldap-missing-password","author_name":"Dmitriy Zaporozhets","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-25T01:51:06-07:00"},{"id":"60d3e94874964a626f105d3598e1c122addcf43e","short_id":"60d3e948749","title":"Merge pull request #1122 from patthoyts/pt/missing-log","author_name":"Dmitriy Zaporozhets","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-25T01:50:34-07:00"},{"id":"b683a71aa1230f17f9df47661c77dfeae27027de","short_id":"b683a71aa12","title":"Merge pull request #1135 from NARKOZ/api","author_name":"Dmitriy Zaporozhets","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-25T01:48:00-07:00"},{"id":"fbb41100db35cf2def2c8b4d896b7015d56bd15b","short_id":"fbb41100db3","title":"update help section with issues API docs","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-24T05:52:43-07:00"},{"id":"eca823c1c7cef45cc18c6ab36d2327650c85bfc3","short_id":"eca823c1c7c","title":"Merge branch 'master' into api","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-24T05:46:36-07:00"},{"id":"024e0348904179a8dea81c01e27a5f014cf57499","short_id":"024e0348904","title":"update API docs","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-24T05:25:01-07:00"},{"id":"7b33d8cbcab3b0ee5789ec607455ab62130db69f","short_id":"7b33d8cbcab","title":"add issues API","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-24T05:19:51-07:00"},{"id":"6035ad7e1fe519d0c6a42731790183889e3ba31d","short_id":"6035ad7e1fe","title":"Create the githost.log file if necessary.","author_name":"Pat Thoyts","author_email":"patthoyts@users.sourceforge.net","created_at":"2012-07-21T07:32:04-07:00"},{"id":"a2d244ec062f3348f6cd1c5218c6097402c5f562","short_id":"a2d244ec062","title":"Handle LDAP missing credentials error with a flash message.","author_name":"Pat Thoyts","author_email":"patthoyts@users.sourceforge.net","created_at":"2012-07-21T01:04:05-07:00"},{"id":"8b7e404b5b6944e9c92cc270b2e5d0005781d49d","short_id":"8b7e404b5b6","title":"Up to 2.7.0","author_name":"randx","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-21T00:53:55-07:00"},{"id":"11721b0dbe82c35789be3e4fa8e14663934b2ff5","short_id":"11721b0dbe8","title":"Help section for system hooks completed","author_name":"randx","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-21T00:47:57-07:00"},{"id":"9c8a1e651716212cf50a623d98e03b8dbdb2c64a","short_id":"9c8a1e65171","title":"Fix system hook example","author_name":"randx","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-21T00:32:42-07:00"},{"id":"4261acda90ff4c61326d80cba026ae76e8551f8f","short_id":"4261acda90f","title":"move SSH keys tab closer to begining","author_name":"randx","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-21T00:27:09-07:00"},{"id":"a69fc5dd23bd502fd36892a80eec21a4c53891f8","short_id":"a69fc5dd23b","title":"Endless event loading for dsahboard","author_name":"randx","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-21T00:23:05-07:00"},{"id":"860fa1163a5fbdfec2bb01ff2d584351554dee29","short_id":"860fa1163a5","title":"Merge pull request #1117 from patthoyts/pt/user-form","author_name":"Dmitriy Zaporozhets","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-20T14:23:49-07:00"},{"id":"787e5e94acf5e20280416c9fda105ef5b77576b3","short_id":"787e5e94acf","title":"Fix english on the edit user form.","author_name":"Pat Thoyts","author_email":"patthoyts@users.sourceforge.net","created_at":"2012-07-20T14:18:42-07:00"},{"id":"9267cb04b0b3fdf127899c4b7e636dc27fac06d3","short_id":"9267cb04b0b","title":"Merge branch 'refactoring_controllers' of dev.gitlabhq.com:gitlabhq","author_name":"Dmitriy Zaporozhets","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-20T07:24:56-07:00"}] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/project_delete_key.json b/lib/gitlab-cli/spec/fixtures/project_delete_key.json new file mode 100644 index 000000000..ed4141599 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_delete_key.json @@ -0,0 +1,8 @@ +{ + "created_at": "2013-10-05T15:05:26Z", + "fingerprint": "5c:b5:e6:b0:f5:31:65:3f:a6:b5:59:86:32:cc:15:e1", + "id": 2, + "key": "ssh-rsa ...", + "updated_at": "2013-10-05T15:05:26Z", + "user_id": null +} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/project_events.json b/lib/gitlab-cli/spec/fixtures/project_events.json new file mode 100644 index 000000000..4d5afc04e --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_events.json @@ -0,0 +1 @@ +[{"title":null,"project_id":2,"action_name":"opened","target_id":null,"target_type":null,"author_id":1,"data":{"before":"ac0c1aa3898d6dea54d7868ea6f9c45fd5e30c59","after":"66350dbb62a221bc619b665aef3e1e7d3b306747","ref":"refs/heads/master","user_id":1,"user_name":"Administrator","project_id":2,"repository":{"name":"gitlab-ci","url":"git@demo.gitlab.com:gitlab/gitlab-ci.git","description":"Continuous integration server for gitlabhq | Coordinator","homepage":"http://demo.gitlab.com/gitlab/gitlab-ci"},"commits":[{"id":"8cf469b039931bab37bbf025e6b69287ea3cfb0e","message":"Modify screenshot\n\nSigned-off-by: Dmitriy Zaporozhets \u003Cdummy@gmail.com\u003E","timestamp":"2014-05-20T10:34:27+00:00","url":"http://demo.gitlab.com/gitlab/gitlab-ci/commit/8cf469b039931bab37bbf025e6b69287ea3cfb0e","author":{"name":"Dummy","email":"dummy@gmail.com"}},{"id":"66350dbb62a221bc619b665aef3e1e7d3b306747","message":"Edit some code\n\nSigned-off-by: Dmitriy Zaporozhets \u003Cdummy@gmail.com\u003E","timestamp":"2014-05-20T10:35:15+00:00","url":"http://demo.gitlab.com/gitlab/gitlab-ci/commit/66350dbb62a221bc619b665aef3e1e7d3b306747","author":{"name":"Dummy","email":"dummy@gmail.com"}}],"total_commits_count":2},"target_title":null,"created_at":"2014-05-20T10:35:26.240Z"},{"title":null,"project_id":2,"action_name":"opened","target_id":2,"target_type":"MergeRequest","author_id":1,"data":null,"target_title":" Morbi et cursus leo. Sed eget vestibulum sapien","created_at":"2014-05-20T10:24:11.917Z"}] diff --git a/lib/gitlab-cli/spec/fixtures/project_for_user.json b/lib/gitlab-cli/spec/fixtures/project_for_user.json new file mode 100644 index 000000000..e2835d865 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_for_user.json @@ -0,0 +1 @@ +{"id":1,"code":"brute","name":"Brute","description":null,"path":"brute","default_branch":null,"owner":{"id":1,"email":"john@example.com","name":"John Owner","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"private":true,"issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"created_at":"2012-09-17T09:41:56Z"} diff --git a/lib/gitlab-cli/spec/fixtures/project_fork_link.json b/lib/gitlab-cli/spec/fixtures/project_fork_link.json new file mode 100644 index 000000000..f1490dfa7 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_fork_link.json @@ -0,0 +1 @@ +{"created_at":"2013-07-03T13:51:48Z","forked_from_project_id":24,"forked_to_project_id":42,"id":1,"updated_at":"2013-07-03T13:51:48Z"} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/project_hook.json b/lib/gitlab-cli/spec/fixtures/project_hook.json new file mode 100644 index 000000000..180dd4555 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_hook.json @@ -0,0 +1 @@ +{"id":1,"url":"https://api.example.net/v1/webhooks/ci"} diff --git a/lib/gitlab-cli/spec/fixtures/project_hooks.json b/lib/gitlab-cli/spec/fixtures/project_hooks.json new file mode 100644 index 000000000..e70d4122c --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_hooks.json @@ -0,0 +1 @@ +[{"id":1,"url":"https://api.example.net/v1/webhooks/ci"}] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/project_issues.json b/lib/gitlab-cli/spec/fixtures/project_issues.json new file mode 100644 index 000000000..87fb2fb18 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_issues.json @@ -0,0 +1 @@ +[{"id":36,"project_id":3,"title":"Eos ut modi et laudantium quasi porro voluptas sed.","description":null,"labels":[],"milestone":null,"assignee":{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":35,"project_id":3,"title":"Ducimus illo in iure voluptatem dolores labore.","description":null,"labels":[],"milestone":null,"assignee":{"id":4,"email":"nicole@mertz.com","name":"Felipe Davis","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":4,"email":"nicole@mertz.com","name":"Felipe Davis","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":34,"project_id":3,"title":"Rem tempora voluptatum atque eum sit nihil neque.","description":null,"labels":[],"milestone":null,"assignee":{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":33,"project_id":3,"title":"Beatae possimus nostrum nihil reiciendis laboriosam nihil delectus alias accusantium dolor unde.","description":null,"labels":[],"milestone":null,"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":32,"project_id":3,"title":"Deserunt tenetur impedit est beatae voluptas voluptas quaerat quisquam.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":16,"project_id":3,"title":"Numquam earum aut laudantium reprehenderit voluptatem aut.","description":null,"labels":[],"milestone":null,"assignee":{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":15,"project_id":3,"title":"Qui veritatis voluptas fuga voluptate voluptas cupiditate.","description":null,"labels":[],"milestone":null,"assignee":{"id":4,"email":"nicole@mertz.com","name":"Felipe Davis","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":4,"email":"nicole@mertz.com","name":"Felipe Davis","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":14,"project_id":3,"title":"In assumenda et ipsa qui debitis voluptatem incidunt.","description":null,"labels":[],"milestone":null,"assignee":{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":13,"project_id":3,"title":"Illo eveniet consequatur enim iste provident facilis rerum voluptatem et architecto aut.","description":null,"labels":[],"milestone":null,"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":12,"project_id":3,"title":"Veniam et tempore quidem eum reprehenderit cupiditate non aut velit eaque.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"}] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/project_key.json b/lib/gitlab-cli/spec/fixtures/project_key.json new file mode 100644 index 000000000..d917f9466 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_key.json @@ -0,0 +1,6 @@ +{ + "id": 2, + "title": "Key Title", + "key": "ssh-rsa ...", + "created_at": "2013-09-22T18:34:32Z" +} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/project_keys.json b/lib/gitlab-cli/spec/fixtures/project_keys.json new file mode 100644 index 000000000..dd22f9668 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_keys.json @@ -0,0 +1,6 @@ +[{ + "id": 2, + "title": "Key Title", + "key": "ssh-rsa ...", + "created_at": "2013-09-22T18:34:32Z" +}] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/project_tags.json b/lib/gitlab-cli/spec/fixtures/project_tags.json new file mode 100644 index 000000000..1e2fb96cb --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/project_tags.json @@ -0,0 +1 @@ +[{"name":"v2.8.2","commit":{"id":"a502f67c0b358cc6b391df0c5dca48375c21fcad","parents":[{"id":"4381084af341684240b1a671d368511afcf5774a"}],"tree":"1612068bdd20de5d14b3096cfa4c621e2051ed4c","message":"Up to 2.8.2","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-08-24T02:03:48-07:00","committed_date":"2012-08-24T02:03:48-07:00"}},{"name":"v2.8.1","commit":{"id":"ed2b53cd1c34c421b23208eeb502a141a6829f9d","parents":[{"id":"7ab587a47791e371f5c109c14097a5d1d7776ea5"}],"tree":"b7393b0b33b777583b285e85b423c4e5ab7f859f","message":"Up to 2.8.1","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-08-22T23:17:18-07:00","committed_date":"2012-08-22T23:17:18-07:00"}},{"name":"v2.8.0pre","commit":{"id":"b2c6ba97a25d299e83c51493d7bc770c13b8ed1a","parents":[{"id":"05da3801f53f06fdc529b4f3820af1380039f245"},{"id":"66399d558da45fb9cd3ea972a47a4f7bb12bfc8d"}],"tree":"36ad53f35bce1fe3f2a4a5f840e7b1bdbfed9c82","message":"Merge pull request #1230 from tsigo/hooray_apostrophes\n\nCorrect usage of \"Can't\"","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-08-16T14:11:08-07:00","committed_date":"2012-08-16T14:11:08-07:00"}},{"name":"v2.8.0","commit":{"id":"5c7ed6fa26b47ac71ff6ba04720d85df6d74b200","parents":[{"id":"d1daeba1736ba145fe525ce08a91f29495a3abf1"}],"tree":"4fc230ff2dbc0e75f27321eac2976aba5a6d323d","message":"Up to 2.8","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-08-21T15:15:26-07:00","committed_date":"2012-08-21T15:15:26-07:00"}},{"name":"v2.7.0pre","commit":{"id":"72a571724d84d112f98a5543c971e9b3b9da1383","parents":[{"id":"3ac840ff06e0ee5b349c52b5a8c02e803a17eec3"},{"id":"990b9217d9a55e26a53d4143d4a3c89123384327"}],"tree":"64b104df5d956e21e0749dc8e70849d1989de36f","message":"Merge pull request #1096 from moregeek/show-flash-note-when-destroying-a-project\n\nshow flash notice after deletion of a project","author":{"name":"Valeriy Sizov","email":"vsv2711@gmail.com"},"committer":{"name":"Valeriy Sizov","email":"vsv2711@gmail.com"},"authored_date":"2012-07-18T05:35:42-07:00","committed_date":"2012-07-18T05:35:42-07:00"}},{"name":"v2.7.0","commit":{"id":"8b7e404b5b6944e9c92cc270b2e5d0005781d49d","parents":[{"id":"11721b0dbe82c35789be3e4fa8e14663934b2ff5"}],"tree":"89fe8c5ff58daaedea07a910cffb14b04ebcc828","message":"Up to 2.7.0","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-07-21T00:53:55-07:00","committed_date":"2012-07-21T00:53:55-07:00"}},{"name":"v2.6.3","commit":{"id":"666cdb22792dd955a286b9993d6235b4cdd68b4b","parents":[{"id":"d92446df1fdba87101c92c90b1c34eb2f1eebef4"}],"tree":"888173aa4c12a4920d318c35b950095d3505673d","message":"up to 2.6.3","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-06-26T09:20:47-07:00","committed_date":"2012-06-26T09:21:28-07:00"}},{"name":"v2.6.2","commit":{"id":"39fecb554f172a0c8ea00316e612e1d37efc7200","parents":[{"id":"68389588d664100590b1a6ca7eedd50860b7e9bc"}],"tree":"53accb25e0b9d038d550cf387753bde15fe4ad19","message":"Up to 2.6.2","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-06-22T13:50:58-07:00","committed_date":"2012-06-22T13:50:58-07:00"}},{"name":"v2.6.1","commit":{"id":"d92a22c9e627268eca697bbd9b660d8c335df953","parents":[{"id":"193804516b8b0783c850981456e947f888ff51bb"}],"tree":"4ac1b5225f597ab55372cb5e950b121d6f55e386","message":"Up to 2.6.1","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-06-22T12:49:03-07:00","committed_date":"2012-06-22T12:49:03-07:00"}},{"name":"v2.6.0","commit":{"id":"b32465712becfbcf83d63b1e6eff7d1483fdabea","parents":[{"id":"1903f6ade027df0f10ef96b9439495eeda07482c"}],"tree":"ffbc05fd0f1771c1602c956df9556260048c7167","message":"Up to 2.6","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-06-21T10:25:23-07:00","committed_date":"2012-06-21T10:25:23-07:00"}},{"name":"v2.5.0","commit":{"id":"cc8369144db2147d2956e8dd7d314e9a7dfd4fbb","parents":[{"id":"1b2068eaa91e5002d01a220c65da21dad8ccb071"}],"tree":"666a442e00689911169e8cc336c5ce60d014854c","message":"Prevent app crash in case if encoding failed","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-05-22T04:57:04-07:00","committed_date":"2012-05-22T04:57:04-07:00"}},{"name":"v2.4.2","commit":{"id":"f18339c26d673c5f8b4c19776036fd42a0de30aa","parents":[{"id":"c937d06c3c98e9ffce8ec1132203eaff6bf7b231"},{"id":"35e602f19c83585d64aa2043ed26eeb8cd7b40e2"}],"tree":"5101f0cd8e395fee1996764298a202437757e85b","message":"Merge branch 'master' of github.com:gitlabhq/gitlabhq","author":{"name":"Zevs","email":"vsv2711@gmail.com"},"committer":{"name":"Zevs","email":"vsv2711@gmail.com"},"authored_date":"2012-04-29T14:24:59-07:00","committed_date":"2012-04-29T14:24:59-07:00"}},{"name":"v2.4.1","commit":{"id":"d97a9aa4a44ff9f452144fad348fd9d7e3b48260","parents":[{"id":"21f3da23589d50038728393f0badc6255b5762ca"}],"tree":"905c33874b064778199f806749d5688e33d64be3","message":"fixed email markdown","author":{"name":"gitlabhq","email":"m@gitlabhq.com"},"committer":{"name":"gitlabhq","email":"m@gitlabhq.com"},"authored_date":"2012-04-23T05:32:56-07:00","committed_date":"2012-04-23T05:32:56-07:00"}},{"name":"v2.4.0pre","commit":{"id":"1845429268364e75bffdeb1075de8f1606e157ec","parents":[{"id":"45b18365d5f409f196a02a4e6e2b77b8ebef909b"}],"tree":"423c70246fa7ffd8804b26628fea34bdb2b22846","message":"Use try for commit prev_commit_id detection","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-04-19T13:35:35-07:00","committed_date":"2012-04-19T13:35:35-07:00"}},{"name":"v2.4.0","commit":{"id":"204c66461ed519eb0078be7e8ac8a6cb56834753","parents":[{"id":"511d07c47c9bf3a18bfa276d452c899369432a22"}],"tree":"9416c777cccf87d348f5705078e82f3f97485e19","message":"corrected exception for automerge","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-04-22T06:49:45-07:00","committed_date":"2012-04-22T06:49:45-07:00"}},{"name":"v2.3.1","commit":{"id":"fa8219e0a753e642a6f1dbdfc010d01ae8a949ee","parents":[{"id":"81da8e46f24913ccf42d3e2644962cbcbc0f9c2e"}],"tree":"5debfcd6d17f9d582aace6ac9b80db27d5c1fe36","message":"better MR list, dashboard pollished","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-03-22T13:57:04-07:00","committed_date":"2012-03-22T13:57:04-07:00"}},{"name":"v2.3.0pre","commit":{"id":"cadf12c60cc27c5b0b8273c1de4b190a0e88bd7d","parents":[{"id":"724ea16c348bc61cf7cb3dbe362c6f30cff1b2c7"}],"tree":"6f4c22761fd2dee405d3fbf38f9dd835bb3c8694","message":"Merged activities & projects pages","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-03-19T15:05:35-07:00","committed_date":"2012-03-19T15:05:35-07:00"}},{"name":"v2.3.0","commit":{"id":"b57faf9282d7df6cdd62953d474652a0ae2e6896","parents":[{"id":"cadf12c60cc27c5b0b8273c1de4b190a0e88bd7d"}],"tree":"f0d5b826df373191b4681452fc2ae4c5970cef4a","message":"Push events polished","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-03-20T14:59:35-07:00","committed_date":"2012-03-20T14:59:35-07:00"}},{"name":"v2.2.0pre","commit":{"id":"6a445b42003007cbb6c06f477c4d7a0b175688c1","parents":[{"id":"22f4c1908d0fc2dbce02e74ed03bf65f028d78d6"}],"tree":"9c60577833f6ca717acdebfa66140124c88e8471","message":"fixed forgot password form","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-02-20T10:37:37-08:00","committed_date":"2012-02-20T10:37:37-08:00"}},{"name":"v2.2.0","commit":{"id":"9e6d0710e927aa8ea834b8a9ae9f277be617ac7d","parents":[{"id":"8c40aab120dbc5507ab9cc8d7ad8e2519d6e9f25"},{"id":"6ea87c47f0f8a24ae031c3fff17bc913889ecd00"}],"tree":"86c831ab21236f21ffa5b97c752369612ce41b39","message":"Merge pull request #443 from CedricGatay/fix/incorrectLineNumberingInDiff\n\nIncorrect line numbering in diff","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-02-22T07:14:54-08:00","committed_date":"2012-02-22T07:14:54-08:00"}},{"name":"v2.1.0","commit":{"id":"98d6492582d232ed86525aa31ccbf280f4cbdaef","parents":[{"id":"611c5a87ab0c083a43785323b09cc47f554c3ba4"}],"tree":"1689b3cad580a18fd9b429ee0b33dac21c9f5a48","message":"removed broken migration","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-01-22T10:52:06-08:00","committed_date":"2012-01-22T10:52:06-08:00"}},{"name":"v2.0.0","commit":{"id":"9a2a8612769d472503b367fa35e99f6fb2876704","parents":[{"id":"2f7b67161952fc9ab322eba6878511b5f2dd5cf1"}],"tree":"26cdb4e66b5e664fe4bcd57d011c54c9c9c26ded","message":"Design tab for profile. Colorscheme as db value","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2011-12-20T12:47:09-08:00","committed_date":"2011-12-20T12:47:09-08:00"}},{"name":"v1.2.2","commit":{"id":"139a332293b9d8c4e5436619036e093483d8347f","parents":[{"id":"ade12da9488bea19d12505c371ead35686a1436e"}],"tree":"365d57f4df5c5dcac69b666cf6d2bfd8ef058008","message":"updated readme","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-11-25T14:30:51-08:00","committed_date":"2011-11-25T14:30:51-08:00"}},{"name":"v1.2.1","commit":{"id":"7ebba27db21719c0035bab65fea92a4780051c73","parents":[{"id":"b56024100d40457a998f83adae3cdc830c997cda"},{"id":"a4fbe13fce87cb6ff2a27a2574ae25bf1dad145c"}],"tree":"b121a7576af1503a96954ce9a94598a68579e053","message":"Merge branch 'master' of dev.gitlabhq.com:gitlabhq","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-11-22T13:15:09-08:00","committed_date":"2011-11-22T13:15:09-08:00"}},{"name":"v1.2.0pre","commit":{"id":"86829cae50857b5edf74b935380c6f68a19c2282","parents":[{"id":"a6b99319381c2d62ec4b92d64805e8de8965859e"}],"tree":"6aab9d13000584fa96fb3cb34d94f3b122bd1143","message":"fixed min height for menu","author":{"name":"gitlabhq","email":"m@gitlabhq.com"},"committer":{"name":"gitlabhq","email":"m@gitlabhq.com"},"authored_date":"2011-11-22T06:03:27-08:00","committed_date":"2011-11-22T06:03:27-08:00"}},{"name":"v1.2.0","commit":{"id":"b56024100d40457a998f83adae3cdc830c997cda","parents":[{"id":"4451b8df8ad6d4b6d79fbce77687c6c2fd37d0a9"}],"tree":"f402cbb6d54526a32b30968c98410bae97b27c8d","message":"lil style fixes","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-11-22T09:57:25-08:00","committed_date":"2011-11-22T09:57:25-08:00"}},{"name":"v1.1.0pre","commit":{"id":"6b030fd41d697e327d2935b406cba70b6a460504","parents":[{"id":"3a2b273316fb29d63b489906f85d9b5329377258"}],"tree":"63b1fdb2a0f135f7074f6a94da14543b8450dd71","message":"1.1pre1","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-10-21T10:04:41-07:00","committed_date":"2011-10-21T10:04:41-07:00"}},{"name":"v1.1.0","commit":{"id":"ba8048d71019b5aaa1f92ee5c3415bfddaa9babb","parents":[{"id":"6b030fd41d697e327d2935b406cba70b6a460504"}],"tree":"4db2b5f4f9b374dd1be3579459bc5947c225c9ba","message":"v1.1.0","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-10-22T06:07:26-07:00","committed_date":"2011-10-22T06:07:26-07:00"}},{"name":"v1.0.2","commit":{"id":"3a2b273316fb29d63b489906f85d9b5329377258","parents":[{"id":"757ea634665e475bf69c1ec962040a0511ee8aeb"},{"id":"c374eb80ff9fb71d37faffc15714bf98b632d3e5"}],"tree":"e0d8170e61a9468a7bb5d4e63305171ec1efa6bf","message":"Merge pull request #40 from vslinko/patch-1\n\nIncrease max key length. Some keys has comment after key string.","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2011-10-18T23:30:06-07:00","committed_date":"2011-10-18T23:30:06-07:00"}},{"name":"v1.0.1","commit":{"id":"7b5799a97998b68416f1b6233ce427135c99165a","parents":[{"id":"0541b3f3c5dcd291d144c83d9731c75ee811b4e0"},{"id":"7b67480c76db8b9a9ccdc80015cc500dc6d26892"}],"tree":"e052185e9dd72a1b1a04d59a5f9efbf3c0369601","message":"Merge branch '1x' of github.com:gitlabhq/gitlabhq into 1x","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-10-14T15:16:44-07:00","committed_date":"2011-10-14T15:16:44-07:00"}}] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/projects.json b/lib/gitlab-cli/spec/fixtures/projects.json new file mode 100644 index 000000000..deab4c5f3 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/projects.json @@ -0,0 +1 @@ +[{"id":1,"code":"brute","name":"Brute","description":null,"path":"brute","default_branch":null,"owner":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"private":true,"issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"created_at":"2012-09-17T09:41:56Z"},{"id":2,"code":"mozart","name":"Mozart","description":null,"path":"mozart","default_branch":null,"owner":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"private":true,"issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"created_at":"2012-09-17T09:41:57Z"},{"id":3,"code":"gitlab","name":"Gitlab","description":null,"path":"gitlab","default_branch":null,"owner":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"private":true,"issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"created_at":"2012-09-17T09:41:58Z"}] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/protect_branch.json b/lib/gitlab-cli/spec/fixtures/protect_branch.json new file mode 100644 index 000000000..752bc2389 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/protect_branch.json @@ -0,0 +1 @@ +{"name":"api","commit":{"id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","parents":[{"id":"949b1df930bedace1dbd755aaa4a82e8c451a616"}],"tree":"f8c4b21c036339f92fcc5482aa28a41250553b27","message":"API: expose issues project id","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-07-25T04:22:21-07:00","committed_date":"2012-07-25T04:22:21-07:00"},"protected":true} diff --git a/lib/gitlab-cli/spec/fixtures/session.json b/lib/gitlab-cli/spec/fixtures/session.json new file mode 100644 index 000000000..e4f5ba35f --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/session.json @@ -0,0 +1 @@ +{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z","private_token":"qEsq1pt6HJPaNciie3MG"} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/snippet.json b/lib/gitlab-cli/spec/fixtures/snippet.json new file mode 100644 index 000000000..34e9d994d --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/snippet.json @@ -0,0 +1 @@ +{"id":1,"title":"Rails Console ActionMailer","file_name":"mailer_test.rb","author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"expires_at":"2012-09-24T00:00:00Z","updated_at":"2012-09-17T09:51:42Z","created_at":"2012-09-17T09:51:42Z"} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/snippets.json b/lib/gitlab-cli/spec/fixtures/snippets.json new file mode 100644 index 000000000..26457995c --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/snippets.json @@ -0,0 +1 @@ +[{"id":1,"title":"Rails Console ActionMailer","file_name":"mailer_test.rb","author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"expires_at":"2012-09-24T00:00:00Z","updated_at":"2012-09-17T09:51:42Z","created_at":"2012-09-17T09:51:42Z"}] diff --git a/lib/gitlab-cli/spec/fixtures/system_hook.json b/lib/gitlab-cli/spec/fixtures/system_hook.json new file mode 100644 index 000000000..0028b7a52 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/system_hook.json @@ -0,0 +1 @@ +{"id": 3, "url": "http://example.com/hook", "created_at": "2013-10-02T10:15:31Z"} diff --git a/lib/gitlab-cli/spec/fixtures/system_hook_test.json b/lib/gitlab-cli/spec/fixtures/system_hook_test.json new file mode 100644 index 000000000..cc79044ff --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/system_hook_test.json @@ -0,0 +1 @@ +{ "event_name": "project_create", "name": "Ruby", "path": "ruby", "project_id": 1, "owner_name": "Someone", "owner_email": "example@gitlabhq.com" } diff --git a/lib/gitlab-cli/spec/fixtures/system_hooks.json b/lib/gitlab-cli/spec/fixtures/system_hooks.json new file mode 100644 index 000000000..2b58791c3 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/system_hooks.json @@ -0,0 +1 @@ +[{"id": 3, "url": "http://example.com/hook", "created_at": "2013-10-02T10:15:31Z"}] diff --git a/lib/gitlab-cli/spec/fixtures/tag.json b/lib/gitlab-cli/spec/fixtures/tag.json new file mode 100644 index 000000000..a56a09262 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/tag.json @@ -0,0 +1 @@ +{"name": "v1.0.0","commit": {"id": "2695effb5807a22ff3d138d593fd856244e155e7","parents": [],"message": "Initial commit","authored_date": "2012-05-28T04:42:42-07:00","author_name": "John Smith","author email": "john@example.com","committer_name": "Jack Smith","committed_date": "2012-05-28T04:42:42-07:00","committer_email": "jack@example.com"},"protected": false} diff --git a/lib/gitlab-cli/spec/fixtures/team_member.json b/lib/gitlab-cli/spec/fixtures/team_member.json new file mode 100644 index 000000000..fd3ac3852 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/team_member.json @@ -0,0 +1 @@ +{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z","access_level":40} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/team_members.json b/lib/gitlab-cli/spec/fixtures/team_members.json new file mode 100644 index 000000000..a2fe19e3b --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/team_members.json @@ -0,0 +1 @@ +[{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z","access_level":40},{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z","access_level":20},{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","blocked":false,"created_at":"2012-09-17T09:42:03Z","access_level":40},{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","blocked":false,"created_at":"2012-09-17T09:42:03Z","access_level":40},{"id":6,"email":"faye.watsica@rohanwalter.com","name":"Ambrose Hansen","blocked":false,"created_at":"2012-09-17T09:42:03Z","access_level":40},{"id":7,"email":"maida@walshtorp.name","name":"Alana Hahn","blocked":false,"created_at":"2012-09-17T09:42:03Z","access_level":20}] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/unprotect_branch.json b/lib/gitlab-cli/spec/fixtures/unprotect_branch.json new file mode 100644 index 000000000..854c8274a --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/unprotect_branch.json @@ -0,0 +1 @@ +{"name":"api","commit":{"id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","parents":[{"id":"949b1df930bedace1dbd755aaa4a82e8c451a616"}],"tree":"f8c4b21c036339f92fcc5482aa28a41250553b27","message":"API: expose issues project id","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-07-25T04:22:21-07:00","committed_date":"2012-07-25T04:22:21-07:00"},"protected":false} diff --git a/lib/gitlab-cli/spec/fixtures/update_merge_request.json b/lib/gitlab-cli/spec/fixtures/update_merge_request.json new file mode 100644 index 000000000..735819ff3 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/update_merge_request.json @@ -0,0 +1 @@ +{"id":1,"target_branch":"master","source_branch":"api","project_id":3,"title":"A different new feature","closed":false,"merged":false,"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-10-19T05:56:05Z"},"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-10-19T05:56:14Z"}} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/user.json b/lib/gitlab-cli/spec/fixtures/user.json new file mode 100644 index 000000000..4e0daca50 --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/user.json @@ -0,0 +1 @@ +{"id":1,"email":"john@example.com","name":"John Smith","bio":null,"skype":"","linkedin":"","twitter":"john","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:41:56Z"} \ No newline at end of file diff --git a/lib/gitlab-cli/spec/fixtures/users.json b/lib/gitlab-cli/spec/fixtures/users.json new file mode 100644 index 000000000..14c6388bf --- /dev/null +++ b/lib/gitlab-cli/spec/fixtures/users.json @@ -0,0 +1 @@ +[{"id":1,"email":"john@example.com","name":"John Smith","bio":null,"skype":"","linkedin":"","twitter":"john","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:41:56Z"},{"id":2,"email":"jack@example.com","name":"Jack Smith","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"},{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"},{"id":4,"email":"nicole@mertz.com","name":"Felipe Davis","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"},{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"},{"id":6,"email":"faye.watsica@rohanwalter.com","name":"Ambrose Hansen","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"},{"id":7,"email":"maida@walshtorp.name","name":"Alana Hahn","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"}] \ No newline at end of file diff --git a/lib/gitlab-cli/spec/gitlab/cli_spec.rb b/lib/gitlab-cli/spec/gitlab/cli_spec.rb new file mode 100644 index 000000000..8b002ec64 --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/cli_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +describe Gitlab::CLI do + describe ".run" do + context "when command is version" do + it "should show gem version" do + output = capture_output { Gitlab::CLI.run('-v') } + expect(output).to eq("Gitlab Ruby Gem #{Gitlab::VERSION}\n") + end + end + + context "when command is info" do + it "should show environment info" do + output = capture_output { Gitlab::CLI.run('info') } + expect(output).to include("Gitlab endpoint is") + expect(output).to include("Gitlab private token is") + expect(output).to include("Ruby Version is") + expect(output).to include("Gitlab Ruby Gem") + end + end + + context "when command is help" do + it "should show available actions" do + output = capture_output { Gitlab::CLI.run('help') } + expect(output).to include('Available commands') + expect(output).to include('MergeRequests') + expect(output).to include('team_members') + end + end + + context "when command is user" do + before do + stub_get("/user", "user") + @output = capture_output { Gitlab::CLI.run('user') } + end + + it "should show executed command" do + expect(@output).to include('Gitlab.user') + end + + it "should show user data" do + expect(@output).to include('name') + expect(@output).to include('John Smith') + end + end + end + + describe ".start" do + context "when command with excluded fields" do + before do + stub_get("/user", "user") + args = ['user', '--except=id,email,name'] + @output = capture_output { Gitlab::CLI.start(args) } + end + + it "should show user data with excluded fields" do + expect(@output).to_not include('John Smith') + expect(@output).to include('bio') + expect(@output).to include('created_at') + end + end + + context "when command with required fields" do + before do + stub_get("/user", "user") + args = ['user', '--only=id,email,name'] + @output = capture_output { Gitlab::CLI.start(args) } + end + + it "should show user data with required fields" do + expect(@output).to include('id') + expect(@output).to include('name') + expect(@output).to include('email') + expect(@output).to include('John Smith') + expect(@output).to_not include('bio') + expect(@output).to_not include('created_at') + end + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab/client/branches_spec.rb b/lib/gitlab-cli/spec/gitlab/client/branches_spec.rb new file mode 100644 index 000000000..80c18ccb5 --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/client/branches_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +describe Gitlab::Client do + it { should respond_to :repo_branches } + it { should respond_to :repo_branch } + it { should respond_to :repo_protect_branch } + it { should respond_to :repo_unprotect_branch } + + describe ".branches" do + before do + stub_get("/projects/3/repository/branches", "branches") + @branches = Gitlab.branches(3) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/repository/branches")).to have_been_made + end + + it "should return an array of repository branches" do + expect(@branches).to be_an Array + expect(@branches.first.name).to eq("api") + end + end + + describe ".branch" do + before do + stub_get("/projects/3/repository/branches/api", "branch") + @branch = Gitlab.branch(3, "api") + end + + it "should get the correct resource" do + expect(a_get("/projects/3/repository/branches/api")).to have_been_made + end + + it "should return information about a repository branch" do + expect(@branch.name).to eq("api") + end + end + + describe ".protect_branch" do + before do + stub_put("/projects/3/repository/branches/api/protect", "protect_branch") + @branch = Gitlab.protect_branch(3, "api") + end + + it "should get the correct resource" do + expect(a_put("/projects/3/repository/branches/api/protect")).to have_been_made + end + + it "should return information about a protected repository branch" do + expect(@branch.name).to eq("api") + expect(@branch.protected).to eq(true) + end + end + + describe ".unprotect_branch" do + before do + stub_put("/projects/3/repository/branches/api/unprotect", "unprotect_branch") + @branch = Gitlab.unprotect_branch(3, "api") + end + + it "should get the correct resource" do + expect(a_put("/projects/3/repository/branches/api/unprotect")).to have_been_made + end + + it "should return information about an unprotected repository branch" do + expect(@branch.name).to eq("api") + expect(@branch.protected).to eq(false) + end + end + + describe ".create_branch" do + context "with branch name" do + before do + stub_post("/projects/3/repository/branches", "create_branch") + @branch = Gitlab.create_branch(3, "api","master") + end + + it "should get the correct resource" do + expect(a_post("/projects/3/repository/branches")).to have_been_made + end + + it "should return information about a new repository branch" do + expect(@branch.name).to eq("api") + end + end + context "with commit hash" do + before do + stub_post("/projects/3/repository/branches", "create_branch") + @branch = Gitlab.create_branch(3, "api","949b1df930bedace1dbd755aaa4a82e8c451a616") + end + + it "should get the correct resource" do + expect(a_post("/projects/3/repository/branches")).to have_been_made + end + + it "should return information about a new repository branch" do + expect(@branch.name).to eq("api") + end + end + end + +end diff --git a/lib/gitlab-cli/spec/gitlab/client/groups_spec.rb b/lib/gitlab-cli/spec/gitlab/client/groups_spec.rb new file mode 100644 index 000000000..ad17aedaa --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/client/groups_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +describe Gitlab::Client do + describe ".groups" do + before do + stub_get("/groups", "groups") + stub_get("/groups/3", "group") + @group = Gitlab.group(3) + @groups = Gitlab.groups + end + + it "should get the correct resource" do + expect(a_get("/groups")).to have_been_made + expect(a_get("/groups/3")).to have_been_made + end + + it "should return an array of Groups" do + expect(@groups).to be_an Array + expect(@groups.first.path).to eq("threegroup") + end + end + + describe ".create_group" do + before do + stub_post("/groups", "group_create") + @group = Gitlab.create_group('GitLab-Group', 'gitlab-path') + end + + it "should get the correct resource" do + expect(a_post("/groups"). + with(:body => {:path => 'gitlab-path', :name => 'GitLab-Group'})).to have_been_made + end + + it "should return information about a created group" do + expect(@group.name).to eq("Gitlab-Group") + expect(@group.path).to eq("gitlab-group") + end + end + + describe ".transfer_project_to_group" do + before do + stub_post("/projects", "project") + @project = Gitlab.create_project('Gitlab') + stub_post("/groups", "group_create") + @group = Gitlab.create_group('GitLab-Group', 'gitlab-path') + + stub_post("/groups/#{@group.id}/projects/#{@project.id}", "group_create") + @group_transfer = Gitlab.transfer_project_to_group(@group.id,@project.id) + end + + it "should post to the correct resource" do + expect(a_post("/groups/#{@group.id}/projects/#{@project.id}").with(:body => {:id => @group.id.to_s, :project_id => @project.id.to_s})).to have_been_made + end + + it "should return information about the group" do + expect(@group_transfer.name).to eq(@group.name) + expect(@group_transfer.path).to eq(@group.path) + expect(@group_transfer.id).to eq(@group.id) + end + end + + describe ".group_members" do + before do + stub_get("/groups/3/members", "group_members") + @members = Gitlab.group_members(3) + end + + it "should get the correct resource" do + expect(a_get("/groups/3/members")).to have_been_made + end + + it "should return information about a group members" do + expect(@members).to be_an Array + expect(@members.size).to eq(2) + expect(@members[1].name).to eq("John Smith") + end + end + + describe ".add_group_member" do + before do + stub_post("/groups/3/members", "group_member") + @member = Gitlab.add_group_member(3, 1, 40) + end + + it "should get the correct resource" do + expect(a_post("/groups/3/members"). + with(:body => {:user_id => '1', :access_level => '40'})).to have_been_made + end + + it "should return information about an added member" do + expect(@member.name).to eq("John Smith") + end + end + + describe ".remove_group_member" do + before do + stub_delete("/groups/3/members/1", "group_member_delete") + @group = Gitlab.remove_group_member(3, 1) + end + + it "should get the correct resource" do + expect(a_delete("/groups/3/members/1")).to have_been_made + end + + it "should return information about the group the member was removed from" do + expect(@group.group_id).to eq(3) + end + end + + +end diff --git a/lib/gitlab-cli/spec/gitlab/client/issues_spec.rb b/lib/gitlab-cli/spec/gitlab/client/issues_spec.rb new file mode 100644 index 000000000..e5a506b94 --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/client/issues_spec.rb @@ -0,0 +1,122 @@ +require 'spec_helper' + +describe Gitlab::Client do + describe ".issues" do + context "with project ID passed" do + before do + stub_get("/projects/3/issues", "project_issues") + @issues = Gitlab.issues(3) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/issues")).to have_been_made + end + + it "should return an array of project's issues" do + expect(@issues).to be_an Array + expect(@issues.first.project_id).to eq(3) + end + end + + context "without project ID passed" do + before do + stub_get("/issues", "issues") + @issues = Gitlab.issues + end + + it "should get the correct resource" do + expect(a_get("/issues")).to have_been_made + end + + it "should return an array of user's issues" do + expect(@issues).to be_an Array + expect(@issues.first.closed).to be_falsey + expect(@issues.first.author.name).to eq("John Smith") + end + end + end + + describe ".issue" do + before do + stub_get("/projects/3/issues/33", "issue") + @issue = Gitlab.issue(3, 33) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/issues/33")).to have_been_made + end + + it "should return information about an issue" do + expect(@issue.project_id).to eq(3) + expect(@issue.assignee.name).to eq("Jack Smith") + end + end + + describe ".create_issue" do + before do + stub_post("/projects/3/issues", "issue") + @issue = Gitlab.create_issue(3, 'title') + end + + it "should get the correct resource" do + expect(a_post("/projects/3/issues"). + with(:body => {:title => 'title'})).to have_been_made + end + + it "should return information about a created issue" do + expect(@issue.project_id).to eq(3) + expect(@issue.assignee.name).to eq("Jack Smith") + end + end + + describe ".edit_issue" do + before do + stub_put("/projects/3/issues/33", "issue") + @issue = Gitlab.edit_issue(3, 33, :title => 'title') + end + + it "should get the correct resource" do + expect(a_put("/projects/3/issues/33"). + with(:body => {:title => 'title'})).to have_been_made + end + + it "should return information about an edited issue" do + expect(@issue.project_id).to eq(3) + expect(@issue.assignee.name).to eq("Jack Smith") + end + end + + describe ".close_issue" do + before do + stub_put("/projects/3/issues/33", "issue") + @issue = Gitlab.close_issue(3, 33) + end + + it "should get the correct resource" do + expect(a_put("/projects/3/issues/33"). + with(:body => {:state_event => 'close'})).to have_been_made + end + + it "should return information about an closed issue" do + expect(@issue.project_id).to eq(3) + expect(@issue.assignee.name).to eq("Jack Smith") + end + end + + describe ".reopen_issue" do + before do + stub_put("/projects/3/issues/33", "issue") + @issue = Gitlab.reopen_issue(3, 33) + end + + it "should get the correct resource" do + expect(a_put("/projects/3/issues/33"). + with(:body => {:state_event => 'reopen'})).to have_been_made + end + + it "should return information about an reopened issue" do + expect(@issue.project_id).to eq(3) + expect(@issue.assignee.name).to eq("Jack Smith") + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab/client/merge_requests_spec.rb b/lib/gitlab-cli/spec/gitlab/client/merge_requests_spec.rb new file mode 100644 index 000000000..a336da9f5 --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/client/merge_requests_spec.rb @@ -0,0 +1,124 @@ +require 'spec_helper' + +describe Gitlab::Client do + describe ".merge_requests" do + before do + stub_get("/projects/3/merge_requests", "merge_requests") + @merge_requests = Gitlab.merge_requests(3) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/merge_requests")).to have_been_made + end + + it "should return an array of project's merge requests" do + expect(@merge_requests).to be_an Array + expect(@merge_requests.first.project_id).to eq(3) + end + end + + describe ".merge_request" do + before do + stub_get("/projects/3/merge_request/1", "merge_request") + @merge_request = Gitlab.merge_request(3, 1) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/merge_request/1")).to have_been_made + end + + it "should return information about a merge request" do + expect(@merge_request.project_id).to eq(3) + expect(@merge_request.assignee.name).to eq("Jack Smith") + end + end + + describe ".create_merge_request" do + before do + stub_post("/projects/3/merge_requests", "create_merge_request") + end + + it "should fail if it doesn't have a source_branch" do + expect { + Gitlab.create_merge_request(3, 'New merge request', :target_branch => 'master') + }.to raise_error Gitlab::Error::MissingAttributes + end + + it "should fail if it doesn't have a target_branch" do + expect { + Gitlab.create_merge_request(3, 'New merge request', :source_branch => 'dev') + }.to raise_error Gitlab::Error::MissingAttributes + end + + it "should return information about a merge request" do + @merge_request = Gitlab.create_merge_request(3, 'New feature', + :source_branch => 'api', + :target_branch => 'master' + ) + expect(@merge_request.project_id).to eq(3) + expect(@merge_request.assignee.name).to eq("Jack Smith") + expect(@merge_request.title).to eq('New feature') + end + end + + describe ".update_merge_request" do + before do + stub_put("/projects/3/merge_request/2", "update_merge_request") + @merge_request = Gitlab.update_merge_request(3, 2, + :assignee_id => '1', + :target_branch => 'master', + :title => 'A different new feature' + ) + end + + it "should return information about a merge request" do + expect(@merge_request.project_id).to eq(3) + expect(@merge_request.assignee.name).to eq("Jack Smith") + expect(@merge_request.title).to eq('A different new feature') + end + end + + describe ".merge_request_comments" do + before do + stub_get("/projects/3/merge_request/2/comments", "merge_request_comments") + @merge_request = Gitlab.merge_request_comments(3, 2) + end + + it "should return merge request's comments" do + expect(@merge_request).to be_an Array + expect(@merge_request.length).to eq(2) + expect(@merge_request[0].note).to eq("this is the 1st comment on the 2merge merge request") + expect(@merge_request[0].author.id).to eq(11) + expect(@merge_request[1].note).to eq("another discussion point on the 2merge request") + expect(@merge_request[1].author.id).to eq(12) + end + end + + describe ".merge_request_comments" do + before do + stub_get("/projects/3/merge_request/2/comments", "merge_request_comments") + @merge_request = Gitlab.merge_request_comments(3, 2) + end + + it "should return merge request's comments" do + expect(@merge_request).to be_an Array + expect(@merge_request.length).to eq(2) + expect(@merge_request[0].note).to eq("this is the 1st comment on the 2merge merge request") + expect(@merge_request[0].author.id).to eq(11) + expect(@merge_request[1].note).to eq("another discussion point on the 2merge request") + expect(@merge_request[1].author.id).to eq(12) + end + end + + describe ".create_merge_request_comment" do + before do + stub_post("/projects/3/merge_request/2/comments", "comment_merge_request") + end + + it "should return information about a merge request" do + @merge_request = Gitlab.create_merge_request_comment(3, 2, 'Cool Merge Request!') + expect(@merge_request.note).to eq('Cool Merge Request!') + @merge_request.author.id == 1 + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab/client/milestones_spec.rb b/lib/gitlab-cli/spec/gitlab/client/milestones_spec.rb new file mode 100644 index 000000000..aa1e66b14 --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/client/milestones_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Gitlab::Client do + describe ".milestones" do + before do + stub_get("/projects/3/milestones", "milestones") + @milestones = Gitlab.milestones(3) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/milestones")).to have_been_made + end + + it "should return an array of project's milestones" do + expect(@milestones).to be_an Array + expect(@milestones.first.project_id).to eq(3) + end + end + + describe ".milestone" do + before do + stub_get("/projects/3/milestones/1", "milestone") + @milestone = Gitlab.milestone(3, 1) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/milestones/1")).to have_been_made + end + + it "should return information about a milestone" do + expect(@milestone.project_id).to eq(3) + end + end + + describe ".create_milestone" do + before do + stub_post("/projects/3/milestones", "milestone") + @milestone = Gitlab.create_milestone(3, 'title') + end + + it "should get the correct resource" do + expect(a_post("/projects/3/milestones"). + with(:body => {:title => 'title'})).to have_been_made + end + + it "should return information about a created milestone" do + expect(@milestone.project_id).to eq(3) + end + end + + describe ".edit_milestone" do + before do + stub_put("/projects/3/milestones/33", "milestone") + @milestone = Gitlab.edit_milestone(3, 33, :title => 'title') + end + + it "should get the correct resource" do + expect(a_put("/projects/3/milestones/33"). + with(:body => {:title => 'title'})).to have_been_made + end + + it "should return information about an edited milestone" do + expect(@milestone.project_id).to eq(3) + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab/client/notes_spec.rb b/lib/gitlab-cli/spec/gitlab/client/notes_spec.rb new file mode 100644 index 000000000..d80cad476 --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/client/notes_spec.rb @@ -0,0 +1,156 @@ +require 'spec_helper' + +describe Gitlab::Client do + describe "notes" do + context "when wall notes" do + before do + stub_get("/projects/3/notes", "notes") + @notes = Gitlab.notes(3) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/notes")).to have_been_made + end + + it "should return an array of notes" do + expect(@notes).to be_an Array + expect(@notes.first.author.name).to eq("John Smith") + end + end + + context "when issue notes" do + before do + stub_get("/projects/3/issues/7/notes", "notes") + @notes = Gitlab.issue_notes(3, 7) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/issues/7/notes")).to have_been_made + end + + it "should return an array of notes" do + expect(@notes).to be_an Array + expect(@notes.first.author.name).to eq("John Smith") + end + end + + context "when snippet notes" do + before do + stub_get("/projects/3/snippets/7/notes", "notes") + @notes = Gitlab.snippet_notes(3, 7) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/snippets/7/notes")).to have_been_made + end + + it "should return an array of notes" do + expect(@notes).to be_an Array + expect(@notes.first.author.name).to eq("John Smith") + end + end + end + + describe "note" do + context "when wall note" do + before do + stub_get("/projects/3/notes/1201", "note") + @note = Gitlab.note(3, 1201) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/notes/1201")).to have_been_made + end + + it "should return information about a note" do + expect(@note.body).to eq("The solution is rather tricky") + expect(@note.author.name).to eq("John Smith") + end + end + + context "when issue note" do + before do + stub_get("/projects/3/issues/7/notes/1201", "note") + @note = Gitlab.issue_note(3, 7, 1201) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/issues/7/notes/1201")).to have_been_made + end + + it "should return information about a note" do + expect(@note.body).to eq("The solution is rather tricky") + expect(@note.author.name).to eq("John Smith") + end + end + + context "when snippet note" do + before do + stub_get("/projects/3/snippets/7/notes/1201", "note") + @note = Gitlab.snippet_note(3, 7, 1201) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/snippets/7/notes/1201")).to have_been_made + end + + it "should return information about a note" do + expect(@note.body).to eq("The solution is rather tricky") + expect(@note.author.name).to eq("John Smith") + end + end + end + + describe "create note" do + context "when wall note" do + before do + stub_post("/projects/3/notes", "note") + @note = Gitlab.create_note(3, "The solution is rather tricky") + end + + it "should get the correct resource" do + expect(a_post("/projects/3/notes"). + with(:body => {:body => 'The solution is rather tricky'})).to have_been_made + end + + it "should return information about a created note" do + expect(@note.body).to eq("The solution is rather tricky") + expect(@note.author.name).to eq("John Smith") + end + end + + context "when issue note" do + before do + stub_post("/projects/3/issues/7/notes", "note") + @note = Gitlab.create_issue_note(3, 7, "The solution is rather tricky") + end + + it "should get the correct resource" do + expect(a_post("/projects/3/issues/7/notes"). + with(:body => {:body => 'The solution is rather tricky'})).to have_been_made + end + + it "should return information about a created note" do + expect(@note.body).to eq("The solution is rather tricky") + expect(@note.author.name).to eq("John Smith") + end + end + + context "when snippet note" do + before do + stub_post("/projects/3/snippets/7/notes", "note") + @note = Gitlab.create_snippet_note(3, 7, "The solution is rather tricky") + end + + it "should get the correct resource" do + expect(a_post("/projects/3/snippets/7/notes"). + with(:body => {:body => 'The solution is rather tricky'})).to have_been_made + end + + it "should return information about a created note" do + expect(@note.body).to eq("The solution is rather tricky") + expect(@note.author.name).to eq("John Smith") + end + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab/client/projects_spec.rb b/lib/gitlab-cli/spec/gitlab/client/projects_spec.rb new file mode 100644 index 000000000..5096f8f66 --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/client/projects_spec.rb @@ -0,0 +1,357 @@ +require 'spec_helper' + +describe Gitlab::Client do + describe ".projects" do + before do + stub_get("/projects", "projects") + @projects = Gitlab.projects + end + + it "should get the correct resource" do + expect(a_get("/projects")).to have_been_made + end + + it "should return an array of projects" do + expect(@projects).to be_an Array + expect(@projects.first.name).to eq("Brute") + expect(@projects.first.owner.name).to eq("John Smith") + end + end + + describe ".project" do + before do + stub_get("/projects/3", "project") + @project = Gitlab.project(3) + end + + it "should get the correct resource" do + expect(a_get("/projects/3")).to have_been_made + end + + it "should return information about a project" do + expect(@project.name).to eq("Gitlab") + expect(@project.owner.name).to eq("John Smith") + end + end + + describe ".project_events" do + before do + stub_get("/projects/2/events", "project_events") + @events = Gitlab.project_events(2) + end + + it "should get the correct resource" do + expect(a_get("/projects/2/events")).to have_been_made + end + + it "should return an array of events" do + expect(@events).to be_an Array + expect(@events.size).to eq(2) + end + + it "should return the action name of the event" do + expect(@events.first.action_name).to eq("opened") + end + end + + describe ".create_project" do + before do + stub_post("/projects", "project") + @project = Gitlab.create_project('Gitlab') + end + + it "should get the correct resource" do + expect(a_post("/projects")).to have_been_made + end + + it "should return information about a created project" do + expect(@project.name).to eq("Gitlab") + expect(@project.owner.name).to eq("John Smith") + end + end + + describe ".create_project for user" do + before do + stub_post("/users", "user") + @owner = Gitlab.create_user("john@example.com", "pass", {name: 'John Owner'}) + stub_post("/projects/user/#{@owner.id}", "project_for_user") + @project = Gitlab.create_project('Brute', {:user_id => @owner.id}) + end + + it "should return information about a created project" do + expect(@project.name).to eq("Brute") + expect(@project.owner.name).to eq("John Owner") + end + end + + describe ".delete_project" do + before do + stub_delete("/projects/Gitlab", "project") + @project = Gitlab.delete_project('Gitlab') + end + + it "should get the correct resource" do + expect(a_delete("/projects/Gitlab")).to have_been_made + end + + it "should return information about a deleted project" do + expect(@project.name).to eq("Gitlab") + expect(@project.owner.name).to eq("John Smith") + end + end + + describe ".team_members" do + before do + stub_get("/projects/3/members", "team_members") + @team_members = Gitlab.team_members(3) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/members")).to have_been_made + end + + it "should return an array of team members" do + expect(@team_members).to be_an Array + expect(@team_members.first.name).to eq("John Smith") + end + end + + describe ".team_member" do + before do + stub_get("/projects/3/members/1", "team_member") + @team_member = Gitlab.team_member(3, 1) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/members/1")).to have_been_made + end + + it "should return information about a team member" do + expect(@team_member.name).to eq("John Smith") + end + end + + describe ".add_team_member" do + before do + stub_post("/projects/3/members", "team_member") + @team_member = Gitlab.add_team_member(3, 1, 40) + end + + it "should get the correct resource" do + expect(a_post("/projects/3/members"). + with(:body => {:user_id => '1', :access_level => '40'})).to have_been_made + end + + it "should return information about an added team member" do + expect(@team_member.name).to eq("John Smith") + end + end + + describe ".edit_team_member" do + before do + stub_put("/projects/3/members/1", "team_member") + @team_member = Gitlab.edit_team_member(3, 1, 40) + end + + it "should get the correct resource" do + expect(a_put("/projects/3/members/1"). + with(:body => {:access_level => '40'})).to have_been_made + end + + it "should return information about an edited team member" do + expect(@team_member.name).to eq("John Smith") + end + end + + describe ".remove_team_member" do + before do + stub_delete("/projects/3/members/1", "team_member") + @team_member = Gitlab.remove_team_member(3, 1) + end + + it "should get the correct resource" do + expect(a_delete("/projects/3/members/1")).to have_been_made + end + + it "should return information about a removed team member" do + expect(@team_member.name).to eq("John Smith") + end + end + + describe ".project_hooks" do + before do + stub_get("/projects/1/hooks", "project_hooks") + @hooks = Gitlab.project_hooks(1) + end + + it "should get the correct resource" do + expect(a_get("/projects/1/hooks")).to have_been_made + end + + it "should return an array of hooks" do + expect(@hooks).to be_an Array + expect(@hooks.first.url).to eq("https://api.example.net/v1/webhooks/ci") + end + end + + describe ".project_hook" do + before do + stub_get("/projects/1/hooks/1", "project_hook") + @hook = Gitlab.project_hook(1, 1) + end + + it "should get the correct resource" do + expect(a_get("/projects/1/hooks/1")).to have_been_made + end + + it "should return information about a hook" do + expect(@hook.url).to eq("https://api.example.net/v1/webhooks/ci") + end + end + + describe ".add_project_hook" do + context "without specified events" do + before do + stub_post("/projects/1/hooks", "project_hook") + @hook = Gitlab.add_project_hook(1, "https://api.example.net/v1/webhooks/ci") + end + + it "should get the correct resource" do + body = {:url => "https://api.example.net/v1/webhooks/ci"} + expect(a_post("/projects/1/hooks").with(:body => body)).to have_been_made + end + + it "should return information about an added hook" do + expect(@hook.url).to eq("https://api.example.net/v1/webhooks/ci") + end + end + + context "with specified events" do + before do + stub_post("/projects/1/hooks", "project_hook") + @hook = Gitlab.add_project_hook(1, "https://api.example.net/v1/webhooks/ci", push_events: true, merge_requests_events: true) + end + + it "should get the correct resource" do + body = {:url => "https://api.example.net/v1/webhooks/ci", push_events: "true", merge_requests_events: "true"} + expect(a_post("/projects/1/hooks").with(:body => body)).to have_been_made + end + + it "should return information about an added hook" do + expect(@hook.url).to eq("https://api.example.net/v1/webhooks/ci") + end + end + end + + describe ".edit_project_hook" do + before do + stub_put("/projects/1/hooks/1", "project_hook") + @hook = Gitlab.edit_project_hook(1, 1, "https://api.example.net/v1/webhooks/ci") + end + + it "should get the correct resource" do + body = {:url => "https://api.example.net/v1/webhooks/ci"} + expect(a_put("/projects/1/hooks/1").with(:body => body)).to have_been_made + end + + it "should return information about an edited hook" do + expect(@hook.url).to eq("https://api.example.net/v1/webhooks/ci") + end + end + + describe ".delete_project_hook" do + before do + stub_delete("/projects/1/hooks/1", "project_hook") + @hook = Gitlab.delete_project_hook(1, 1) + end + + it "should get the correct resource" do + expect(a_delete("/projects/1/hooks/1")).to have_been_made + end + + it "should return information about a deleted hook" do + expect(@hook.url).to eq("https://api.example.net/v1/webhooks/ci") + end + end + + describe ".make_forked_from" do + before do + stub_post("/projects/42/fork/24", "project_fork_link") + @forked_project_link = Gitlab.make_forked_from(42, 24) + end + + it "should get the correct resource" do + expect(a_post("/projects/42/fork/24")).to have_been_made + end + + it "should return information about a forked project" do + expect(@forked_project_link.forked_from_project_id).to eq(24) + expect(@forked_project_link.forked_to_project_id).to eq(42) + end + end + + describe ".remove_forked" do + before do + stub_delete("/projects/42/fork", "project_fork_link") + @forked_project_link = Gitlab.remove_forked(42) + end + + it "should be sent to correct resource" do + expect(a_delete("/projects/42/fork")).to have_been_made + end + + it "should return information about an unforked project" do + expect(@forked_project_link.forked_to_project_id).to eq(42) + end + end + + describe ".deploy_keys" do + before do + stub_get("/projects/42/keys", "project_keys") + @deploy_keys = Gitlab.deploy_keys(42) + end + + it "should get the correct resource" do + expect(a_get("/projects/42/keys")).to have_been_made + end + + it "should return project deploy keys" do + expect(@deploy_keys).to be_an Array + expect(@deploy_keys.first.id).to eq 2 + expect(@deploy_keys.first.title).to eq "Key Title" + expect(@deploy_keys.first.key).to match(/ssh-rsa/) + end + end + + describe ".deploy_key" do + before do + stub_get("/projects/42/keys/2", "project_key") + @deploy_key = Gitlab.deploy_key(42, 2) + end + + it "should get the correct resource" do + expect(a_get("/projects/42/keys/2")).to have_been_made + end + + it "should return project deploy key" do + expect(@deploy_key.id).to eq 2 + expect(@deploy_key.title).to eq "Key Title" + expect(@deploy_key.key).to match(/ssh-rsa/) + end + end + + describe ".delete_deploy_key" do + before do + stub_delete("/projects/42/keys/2", "project_delete_key") + @deploy_key = Gitlab.delete_deploy_key(42, 2) + end + + it "should get the correct resource" do + expect(a_delete("/projects/42/keys/2")).to have_been_made + end + + it "should return information about a deleted key" do + expect(@deploy_key.id).to eq(2) + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab/client/repositories_spec.rb b/lib/gitlab-cli/spec/gitlab/client/repositories_spec.rb new file mode 100644 index 000000000..f58e315d0 --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/client/repositories_spec.rb @@ -0,0 +1,92 @@ +require 'spec_helper' + +describe Gitlab::Client do + it { should respond_to :repo_tags } + it { should respond_to :repo_create_tag } + it { should respond_to :repo_branches } + it { should respond_to :repo_branch } + it { should respond_to :repo_commits } + it { should respond_to :repo_commit } + it { should respond_to :repo_commit_diff } + + describe ".tags" do + before do + stub_get("/projects/3/repository/tags", "project_tags") + @tags = Gitlab.tags(3) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/repository/tags")).to have_been_made + end + + it "should return an array of repository tags" do + expect(@tags).to be_an Array + expect(@tags.first.name).to eq("v2.8.2") + end + end + + describe ".create_tag" do + before do + stub_post("/projects/3/repository/tags", "tag") + @tag = Gitlab.create_tag(3, 'v1.0.0', '2695effb5807a22ff3d138d593fd856244e155e7') + end + + it "should get the correct resource" do + expect(a_post("/projects/3/repository/tags")).to have_been_made + end + + it "should return information about a new repository tag" do + expect(@tag.name).to eq("v1.0.0") + end + end + + describe ".commits" do + before do + stub_get("/projects/3/repository/commits", "project_commits"). + with(:query => {:ref_name => "api"}) + @commits = Gitlab.commits(3, :ref_name => "api") + end + + it "should get the correct resource" do + expect(a_get("/projects/3/repository/commits"). + with(:query => {:ref_name => "api"})).to have_been_made + end + + it "should return an array of repository commits" do + expect(@commits).to be_an Array + expect(@commits.first.id).to eq("f7dd067490fe57505f7226c3b54d3127d2f7fd46") + end + end + + describe ".commit" do + before do + stub_get("/projects/3/repository/commits/6104942438c14ec7bd21c6cd5bd995272b3faff6", "project_commit") + @commit = Gitlab.commit(3, '6104942438c14ec7bd21c6cd5bd995272b3faff6') + end + + it "should get the correct resource" do + expect(a_get("/projects/3/repository/commits/6104942438c14ec7bd21c6cd5bd995272b3faff6")) + .to have_been_made + end + + it "should return a repository commit" do + expect(@commit.id).to eq("6104942438c14ec7bd21c6cd5bd995272b3faff6") + end + end + + describe ".commit_diff" do + before do + stub_get("/projects/3/repository/commits/6104942438c14ec7bd21c6cd5bd995272b3faff6/diff", "project_commit_diff") + @diff = Gitlab.commit_diff(3, '6104942438c14ec7bd21c6cd5bd995272b3faff6') + end + + it "should get the correct resource" do + expect(a_get("/projects/3/repository/commits/6104942438c14ec7bd21c6cd5bd995272b3faff6/diff")) + .to have_been_made + end + + it "should return a diff of a commit" do + expect(@diff.new_path).to eq("doc/update/5.4-to-6.0.md") + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab/client/snippets_spec.rb b/lib/gitlab-cli/spec/gitlab/client/snippets_spec.rb new file mode 100644 index 000000000..b6ceecc0d --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/client/snippets_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe Gitlab::Client do + describe ".snippets" do + before do + stub_get("/projects/3/snippets", "snippets") + @snippets = Gitlab.snippets(3) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/snippets")).to have_been_made + end + + it "should return an array of project's snippets" do + expect(@snippets).to be_an Array + expect(@snippets.first.file_name).to eq("mailer_test.rb") + end + end + + describe ".snippet" do + before do + stub_get("/projects/3/snippets/1", "snippet") + @snippet = Gitlab.snippet(3, 1) + end + + it "should get the correct resource" do + expect(a_get("/projects/3/snippets/1")).to have_been_made + end + + it "should return information about a snippet" do + expect(@snippet.file_name).to eq("mailer_test.rb") + expect(@snippet.author.name).to eq("John Smith") + end + end + + describe ".create_snippet" do + before do + stub_post("/projects/3/snippets", "snippet") + @snippet = Gitlab.create_snippet(3, {:title => 'API', :file_name => 'api.rb', :code => 'code'}) + end + + it "should get the correct resource" do + body = {:title => 'API', :file_name => 'api.rb', :code => 'code'} + expect(a_post("/projects/3/snippets").with(:body => body)).to have_been_made + end + + it "should return information about a new snippet" do + expect(@snippet.file_name).to eq("mailer_test.rb") + expect(@snippet.author.name).to eq("John Smith") + end + end + + describe ".edit_snippet" do + before do + stub_put("/projects/3/snippets/1", "snippet") + @snippet = Gitlab.edit_snippet(3, 1, :file_name => 'mailer_test.rb') + end + + it "should get the correct resource" do + expect(a_put("/projects/3/snippets/1"). + with(:body => {:file_name => 'mailer_test.rb'})).to have_been_made + end + + it "should return information about an edited snippet" do + expect(@snippet.file_name).to eq("mailer_test.rb") + expect(@snippet.author.name).to eq("John Smith") + end + end + + describe ".delete_snippet" do + before do + stub_delete("/projects/3/snippets/1", "snippet") + @snippet = Gitlab.delete_snippet(3, 1) + end + + it "should get the correct resource" do + expect(a_delete("/projects/3/snippets/1")).to have_been_made + end + + it "should return information about a deleted snippet" do + expect(@snippet.file_name).to eq("mailer_test.rb") + expect(@snippet.author.name).to eq("John Smith") + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab/client/system_hooks_spec.rb b/lib/gitlab-cli/spec/gitlab/client/system_hooks_spec.rb new file mode 100644 index 000000000..7410c3655 --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/client/system_hooks_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe Gitlab::Client do + it { should respond_to :system_hooks } + it { should respond_to :add_system_hook } + it { should respond_to :system_hook } + it { should respond_to :delete_system_hook } + + describe ".hooks" do + before do + stub_get("/hooks", "system_hooks") + @hooks = Gitlab.hooks + end + + it "should get the correct resource" do + expect(a_get("/hooks")).to have_been_made + end + + it "should return an array of system hooks" do + expect(@hooks).to be_an Array + expect(@hooks.first.url).to eq("http://example.com/hook") + end + end + + describe ".add_hook" do + before do + stub_post("/hooks", "system_hook") + @hook = Gitlab.add_hook("http://example.com/hook") + end + + it "should get the correct resource" do + expect(a_post("/hooks")).to have_been_made + end + + it "should return information about a added system hook" do + expect(@hook.url).to eq("http://example.com/hook") + end + end + + describe ".hook" do + before do + stub_get("/hooks/3", "system_hook_test") + @hook = Gitlab.hook(3) + end + it "should get the correct resource" do + expect(a_get("/hooks/3")).to have_been_made + end + + it "should return information about a added system hook" do + expect(@hook.event_name).to eq("project_create") + expect(@hook.project_id).to eq(1) + end + end + + describe ".delete_hook" do + before do + stub_delete("/hooks/3", "system_hook") + @hook = Gitlab.delete_hook(3) + end + + it "should get the correct resource" do + expect(a_delete("/hooks/3")).to have_been_made + end + + it "should return information about a deleted system hook" do + expect(@hook.url).to eq("http://example.com/hook") + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab/client/users_spec.rb b/lib/gitlab-cli/spec/gitlab/client/users_spec.rb new file mode 100644 index 000000000..ead205b47 --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/client/users_spec.rb @@ -0,0 +1,192 @@ +require 'spec_helper' + +describe Gitlab::Client do + describe ".users" do + before do + stub_get("/users", "users") + @users = Gitlab.users + end + + it "should get the correct resource" do + expect(a_get("/users")).to have_been_made + end + + it "should return an array of users" do + expect(@users).to be_an Array + expect(@users.first.email).to eq("john@example.com") + end + end + + describe ".user" do + context "with user ID passed" do + before do + stub_get("/users/1", "user") + @user = Gitlab.user(1) + end + + it "should get the correct resource" do + expect(a_get("/users/1")).to have_been_made + end + + it "should return information about a user" do + expect(@user.email).to eq("john@example.com") + end + end + + context "without user ID passed" do + before do + stub_get("/user", "user") + @user = Gitlab.user + end + + it "should get the correct resource" do + expect(a_get("/user")).to have_been_made + end + + it "should return information about an authorized user" do + expect(@user.email).to eq("john@example.com") + end + end + end + + describe ".create_user" do + context "when successful request" do + before do + stub_post("/users", "user") + @user = Gitlab.create_user("email", "pass") + end + + it "should get the correct resource" do + body = {:email => "email", :password => "pass", :name => "email"} + expect(a_post("/users").with(:body => body)).to have_been_made + end + + it "should return information about a created user" do + expect(@user.email).to eq("john@example.com") + end + end + + context "when bad request" do + it "should throw an exception" do + stub_post("/users", "error_already_exists", 409) + expect { + Gitlab.create_user("email", "pass") + }.to raise_error(Gitlab::Error::Conflict, "Server responded with code 409, message: 409 Already exists. Request URI: #{Gitlab.endpoint}/users") + end + end + end + + describe ".edit_user" do + before do + @options = { :name => "Roberto" } + stub_put("/users/1", "user").with(:body => @options) + @user = Gitlab.edit_user(1, @options) + end + + it "should get the correct resource" do + expect(a_put("/users/1").with(:body => @options)).to have_been_made + end + end + + describe ".session" do + after do + Gitlab.endpoint = 'https://api.example.com' + Gitlab.private_token = 'secret' + end + + before do + stub_request(:post, "#{Gitlab.endpoint}/session"). + to_return(:body => load_fixture('session'), :status => 200) + @session = Gitlab.session("email", "pass") + end + + context "when endpoint is not set" do + it "should raise Error::MissingCredentials" do + Gitlab.endpoint = nil + expect { + Gitlab.session("email", "pass") + }.to raise_error(Gitlab::Error::MissingCredentials, 'Please set an endpoint to API') + end + end + + context "when private_token is not set" do + it "should not raise Error::MissingCredentials" do + Gitlab.private_token = nil + expect { Gitlab.session("email", "pass") }.to_not raise_error + end + end + + context "when endpoint is set" do + it "should get the correct resource" do + expect(a_request(:post, "#{Gitlab.endpoint}/session")).to have_been_made + end + + it "should return information about a created session" do + expect(@session.email).to eq("john@example.com") + expect(@session.private_token).to eq("qEsq1pt6HJPaNciie3MG") + end + end + end + + describe ".ssh_keys" do + before do + stub_get("/user/keys", "keys") + @keys = Gitlab.ssh_keys + end + + it "should get the correct resource" do + expect(a_get("/user/keys")).to have_been_made + end + + it "should return an array of SSH keys" do + expect(@keys).to be_an Array + expect(@keys.first.title).to eq("narkoz@helium") + end + end + + describe ".ssh_key" do + before do + stub_get("/user/keys/1", "key") + @key = Gitlab.ssh_key(1) + end + + it "should get the correct resource" do + expect(a_get("/user/keys/1")).to have_been_made + end + + it "should return information about an SSH key" do + expect(@key.title).to eq("narkoz@helium") + end + end + + describe ".create_ssh_key" do + before do + stub_post("/user/keys", "key") + @key = Gitlab.create_ssh_key("title", "body") + end + + it "should get the correct resource" do + body = {:title => "title", :key => "body"} + expect(a_post("/user/keys").with(:body => body)).to have_been_made + end + + it "should return information about a created SSH key" do + expect(@key.title).to eq("narkoz@helium") + end + end + + describe ".delete_ssh_key" do + before do + stub_delete("/user/keys/1", "key") + @key = Gitlab.delete_ssh_key(1) + end + + it "should get the correct resource" do + expect(a_delete("/user/keys/1")).to have_been_made + end + + it "should return information about a deleted SSH key" do + expect(@key.title).to eq("narkoz@helium") + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab/objectified_hash_spec.rb b/lib/gitlab-cli/spec/gitlab/objectified_hash_spec.rb new file mode 100644 index 000000000..db45711df --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/objectified_hash_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Gitlab::ObjectifiedHash do + before do + @hash = {a: 1, b: 2} + @oh = Gitlab::ObjectifiedHash.new @hash + end + + it "should objectify hash" do + expect(@oh.a).to eq(@hash[:a]) + expect(@oh.b).to eq(@hash[:b]) + end + + describe "#to_hash" do + it "should return an original hash" do + expect(@oh.to_hash).to eq(@hash) + end + + it "should have an alias #to_h" do + expect(@oh.respond_to?(:to_h)).to be_truthy + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab/request_spec.rb b/lib/gitlab-cli/spec/gitlab/request_spec.rb new file mode 100644 index 000000000..869c2e964 --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab/request_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Gitlab::Request do + it { should respond_to :get } + it { should respond_to :post } + it { should respond_to :put } + it { should respond_to :delete } + + describe ".default_options" do + it "should have default values" do + default_options = Gitlab::Request.default_options + expect(default_options).to be_a Hash + expect(default_options[:parser]).to be_a Proc + expect(default_options[:format]).to eq(:json) + expect(default_options[:headers]).to eq({'Accept' => 'application/json'}) + expect(default_options[:default_params]).to be_nil + end + end + + describe ".parse" do + it "should return ObjectifiedHash" do + body = JSON.unparse({a: 1, b: 2}) + expect(Gitlab::Request.parse(body)).to be_an Gitlab::ObjectifiedHash + end + end + + describe "#set_request_defaults" do + context "when endpoint is not set" do + it "should raise Error::MissingCredentials" do + expect { + Gitlab::Request.new.set_request_defaults(nil, 1234000) + }.to raise_error(Gitlab::Error::MissingCredentials, 'Please set an endpoint to API') + end + end + + context "when endpoint is set" do + it "should set base_uri" do + Gitlab::Request.new.set_request_defaults('http://rabbit-hole.example.org', 1234000) + expect(Gitlab::Request.base_uri).to eq("http://rabbit-hole.example.org") + end + + it "should set default_params" do + Gitlab::Request.new.set_request_defaults('http://rabbit-hole.example.org', 1234000, 'sudoer') + expect(Gitlab::Request.default_params).to eq({:sudo => 'sudoer'}) + end + end + end +end diff --git a/lib/gitlab-cli/spec/gitlab_spec.rb b/lib/gitlab-cli/spec/gitlab_spec.rb new file mode 100644 index 000000000..f037e70aa --- /dev/null +++ b/lib/gitlab-cli/spec/gitlab_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe Gitlab do + after { Gitlab.reset } + + describe ".client" do + it "should be a Gitlab::Client" do + expect(Gitlab.client).to be_a Gitlab::Client + end + end + + describe ".actions" do + it "should return an array of client methods" do + actions = Gitlab.actions + expect(actions).to be_an Array + expect(actions.first).to be_a Symbol + expect(actions.sort.first).to match(/add_/) + end + end + + describe ".endpoint=" do + it "should set endpoint" do + Gitlab.endpoint = 'https://api.example.com' + expect(Gitlab.endpoint).to eq('https://api.example.com') + end + end + + describe ".private_token=" do + it "should set private_token" do + Gitlab.private_token = 'secret' + expect(Gitlab.private_token).to eq('secret') + end + end + + describe ".sudo=" do + it "should set sudo" do + Gitlab.sudo = 'user' + expect(Gitlab.sudo).to eq('user') + end + end + + describe ".user_agent" do + it "should return default user_agent" do + expect(Gitlab.user_agent).to eq(Gitlab::Configuration::DEFAULT_USER_AGENT) + end + end + + describe ".user_agent=" do + it "should set user_agent" do + Gitlab.user_agent = 'Custom User Agent' + expect(Gitlab.user_agent).to eq('Custom User Agent') + end + end + + describe ".configure" do + Gitlab::Configuration::VALID_OPTIONS_KEYS.each do |key| + it "should set #{key}" do + Gitlab.configure do |config| + config.send("#{key}=", key) + expect(Gitlab.send(key)).to eq(key) + end + end + end + end +end diff --git a/lib/gitlab-cli/spec/spec_helper.rb b/lib/gitlab-cli/spec/spec_helper.rb new file mode 100644 index 000000000..28df28be2 --- /dev/null +++ b/lib/gitlab-cli/spec/spec_helper.rb @@ -0,0 +1,74 @@ +require 'rspec' +require 'webmock/rspec' + +require File.expand_path('../../lib/gitlab', __FILE__) +require File.expand_path('../../lib/gitlab/cli', __FILE__) + +def capture_output + out = StringIO.new + $stdout = out + $stderr = out + yield + $stdout = STDOUT + $stderr = STDERR + out.string +end + +def load_fixture(name) + File.new(File.dirname(__FILE__) + "/fixtures/#{name}.json") +end + +RSpec.configure do |config| + config.before(:all) do + Gitlab.endpoint = 'https://api.example.com' + Gitlab.private_token = 'secret' + end +end + +# GET +def stub_get(path, fixture) + stub_request(:get, "#{Gitlab.endpoint}#{path}"). + with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}). + to_return(:body => load_fixture(fixture)) +end + +def a_get(path) + a_request(:get, "#{Gitlab.endpoint}#{path}"). + with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}) +end + +# POST +def stub_post(path, fixture, status_code=200) + stub_request(:post, "#{Gitlab.endpoint}#{path}"). + with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}). + to_return(:body => load_fixture(fixture), :status => status_code) +end + +def a_post(path) + a_request(:post, "#{Gitlab.endpoint}#{path}"). + with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}) +end + +# PUT +def stub_put(path, fixture) + stub_request(:put, "#{Gitlab.endpoint}#{path}"). + with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}). + to_return(:body => load_fixture(fixture)) +end + +def a_put(path) + a_request(:put, "#{Gitlab.endpoint}#{path}"). + with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}) +end + +# DELETE +def stub_delete(path, fixture) + stub_request(:delete, "#{Gitlab.endpoint}#{path}"). + with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}). + to_return(:body => load_fixture(fixture)) +end + +def a_delete(path) + a_request(:delete, "#{Gitlab.endpoint}#{path}"). + with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}) +end diff --git a/lib/redmine/scm/adapters/gitlab_adapter.rb b/lib/redmine/scm/adapters/gitlab_adapter.rb new file mode 100644 index 000000000..cad6583f6 --- /dev/null +++ b/lib/redmine/scm/adapters/gitlab_adapter.rb @@ -0,0 +1,287 @@ +#coding=utf-8 +# +require 'redmine/scm/adapters/abstract_adapter' +require 'base64' + +module Redmine + module Scm + module Adapters + + class GitlabAdapter < AbstractAdapter + + class GitBranch < Branch + attr_accessor :is_default + end + + def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil) + super + @g = Gitlab.client + @project = 11 + @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding + end + + def path_encoding + @path_encoding + end + + def info + begin + Info.new(:root_url => url, :lastrev => lastrev('',nil)) + rescue + nil + end + end + + def branches + return @branches if @branches + @branches = [] + branches = @g.branches(@project) + branches.each do |line| + name = line.name + scmid = line.commit.id + bran = GitBranch.new(name) + bran.revision = scmid + bran.scmid = name + bran.is_default = true #TODO + @branches << bran + end + @branches.sort! + rescue ScmCommandAborted + nil + end + + def tags + return @tags if @tags + tags = @g.tags(@project) + @tags = [] + tags.each do |tag| + @tags << tag.name + end + rescue ScmCommandAborted + nil + end + + def default_branch + bras = self.branches + return nil if bras.nil? + default_bras = bras.select{|x| x.is_default == true} + return default_bras.first.to_s if ! default_bras.empty? + master_bras = bras.select{|x| x.to_s == 'master'} + master_bras.empty? ? bras.first.to_s : 'master' + end + + def entry(path=nil, identifier=nil) + parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?} + search_path = parts[0..-2].join('/') + search_name = parts[-1] + if search_path.blank? && search_name.blank? + # Root entry + Entry.new(:path => '', :kind => 'dir') + else + # Search for the entry in the parent directory + es = entries(search_path, identifier, + options = {:report_last_commit => false}) + es ? es.detect {|e| e.name == search_name} : nil + end + end + + def entries(path=nil, identifier=nil, options={}) + entries = Entries.new + trees = @g.trees(@project, path: path, ref_name: identifier) + trees.each do |tree| + entries << Entry.new({ + :name => tree.name, + :path => File.join(path,tree.name), + :kind => tree.type == 'tree' ? 'dir' : 'file', + :size => nil, + :lastrev => nil + }) + end + entries.sort_by_name + rescue ScmCommandAborted + nil + end + + def lastrev(path, rev) + return nil if path.nil? + cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1| + cmd_args << rev if rev + cmd_args << "--" << path unless path.empty? + lines = [] + git_cmd(cmd_args) { |io| lines = io.readlines } + begin + id = lines[0].split[1] + author = lines[1].match('Author:\s+(.*)$')[1] + time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1]) + + Revision.new({ + :identifier => id, + :scmid => id, + :author => author, + :time => time, + :message => nil, + :paths => nil + }) + rescue NoMethodError => e + logger.error("The revision '#{path}' has a wrong format") + return nil + end + rescue ScmCommandAborted + nil + end + + def revisions(path, identifier_from, identifier_to, options={}) + revs = Revisions.new + cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller --parents --stdin| + cmd_args << "--reverse" if options[:reverse] + cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit] + cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty? + revisions = [] + if identifier_from || identifier_to + revisions << "" + revisions[0] << "#{identifier_from}.." if identifier_from + revisions[0] << "#{identifier_to}" if identifier_to + else + unless options[:includes].blank? + revisions += options[:includes] + end + unless options[:excludes].blank? + revisions += options[:excludes].map{|r| "^#{r}"} + end + end + + + commits = @g.commits(@project, {ref_name: identifier_to}) + commits.each do |commit| + revision = Revision.new({ + :identifier => commit.id, + :scmid => commit.id, + :author => commit.author_name, + :time => Time.parse(commit.created_at), + :message => commit.message, + :paths => nil, + :parents => nil + }) + revs << revision + end + + revs + rescue ScmCommandAborted => e + err_msg = "git log error: #{e.message}" + logger.error(err_msg) + if block_given? + raise CommandFailed, err_msg + else + revs + end + end + + def diff(path, identifier_from, identifier_to=nil) + path ||= '' + cmd_args = [] + if identifier_to + cmd_args << "diff" << "--no-color" << identifier_to << identifier_from + else + cmd_args << "show" << "--no-color" << identifier_from + end + cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty? + diff = [] + git_cmd(cmd_args) do |io| + io.each_line do |line| + diff << line + end + end + diff + rescue ScmCommandAborted + nil + end + + def annotate(path, identifier=nil) + identifier = 'HEAD' if identifier.blank? + cmd_args = %w|blame| + cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path) + blame = Annotate.new + content = nil + git_cmd(cmd_args) { |io| io.binmode; content = io.read } + # git annotates binary files + return nil if content.is_binary_data? + identifier = '' + # git shows commit author on the first occurrence only + authors_by_commit = {} + content.split("\n").each do |line| + if line =~ /^([0-9a-f]{39,40})\s.*/ + identifier = $1 + elsif line =~ /^author (.+)/ + authors_by_commit[identifier] = $1.strip + elsif line =~ /^\t(.*)/ + blame.add_line($1, Revision.new( + :identifier => identifier, + :revision => identifier, + :scmid => identifier, + :author => authors_by_commit[identifier] + )) + identifier = '' + author = '' + end + end + blame + rescue ScmCommandAborted + nil + end + + def cat(path, identifier=nil) + if identifier.nil? + identifier = 'HEAD' + end + file = @g.files(@project, path, identifier) + cat = Base64.decode64 file.content + rescue ScmCommandAborted + nil + end + + def parse_commit(commits) + sum = {file: 0, insertion: 0, deletion: 0} + commits.split("\n").each do |commit| + if /(\d+)\s+?file/ =~ commit + sum[:file] += $1 .to_i + end + if /(\d+)\s+?insertion/ =~ commit + sum[:insertion] += $1.to_i + end + if /(\d+)\s+?deletion/ =~ commit + sum[:deletion] += $1.to_i + end + end + sum[:insertion] + sum[:deletion] + end + + def commits(authors, start_date, end_date, branch='master') + rs = [] + authors.each do |author| + cmd_args = %W|log #{branch} --pretty=tformat: --shortstat --author=#{author} --since=#{start_date} --until=#{end_date}| + commits = '' + git_cmd(cmd_args) do |io| + commits = io.read + end + logger.info "git log output for #{author} #{commits}" + rs << {author: author, num: parse_commit(commits)} + end + rs + end + + class Revision < Redmine::Scm::Adapters::Revision + # Returns the readable identifier + def format_identifier + identifier[0,8] + end + end + + def git_cmd(args, options = {}, &block) + logger.info "git cmd: #{args.join(' ')}" + end + private :git_cmd + end + + end + end +end diff --git a/lib/trustie.rb b/lib/trustie.rb index b6cec3c86..4f1f68fe1 100644 --- a/lib/trustie.rb +++ b/lib/trustie.rb @@ -1,2 +1,3 @@ require 'trustie/utils' require 'trustie/utils/image' +require 'trustie/gitlab/api' diff --git a/lib/trustie/gitlab/api.rb b/lib/trustie/gitlab/api.rb new file mode 100644 index 000000000..6c1993721 --- /dev/null +++ b/lib/trustie/gitlab/api.rb @@ -0,0 +1,35 @@ +#coding=utf-8 +# +# +module Trustie + module Gitlab + class Api + # attr_accessor :g + + def initialize(client=nil) + @g = client || ::Gitlab.client + end + + def trees(project_id) + @g.trees(project_id) + end + + def entries(project) + entries = [] + api = Trustie::Gitlab::Api.new + trees = api.trees(11) + trees.each do |tree| + entries << Redmine::Scm::Adapters::Entry.new({ + :name => tree.name, + :path => tree.id, + :kind => tree.type == 'tree' ? 'dir' : 'file', + :size => nil, + :lastrev => nil + }) + end + entries + end + + end + end +end diff --git a/lib/trustie/gitlab/helper.rb b/lib/trustie/gitlab/helper.rb new file mode 100644 index 000000000..e69de29bb diff --git a/spec/requests/gitlab_request_spec.rb b/spec/requests/gitlab_request_spec.rb new file mode 100644 index 000000000..05a94568b --- /dev/null +++ b/spec/requests/gitlab_request_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +RSpec.describe "Gitlab request", :type => :request do + + describe "get repository files" do + it "参数正确,可以获取正确列表 " do + api = Trustie::Gitlab::Api.new + trees = api.trees(11) + expect(trees).to be_instance_of(Array) + end + end +end + From f082ff745e806585eeb92025b4f2cb8b8b4e4bd7 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sat, 1 Aug 2015 20:09:53 +0800 Subject: [PATCH 006/169] =?UTF-8?q?=E5=90=8C=E6=AD=A5members?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/member.rb | 1 + app/models/member_role.rb | 3 +++ app/models/role.rb | 21 +++++++++++++++ lib/trustie/gitlab/manage_member.rb | 42 +++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 lib/trustie/gitlab/manage_member.rb diff --git a/app/models/member.rb b/app/models/member.rb index 292dd2034..c2bf4e7d5 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -31,6 +31,7 @@ class Member < ActiveRecord::Base before_destroy :set_issue_category_nil + def role end diff --git a/app/models/member_role.rb b/app/models/member_role.rb index 67122a636..4d493cb27 100644 --- a/app/models/member_role.rb +++ b/app/models/member_role.rb @@ -35,8 +35,11 @@ class MemberRole < ActiveRecord::Base !inherited_from.nil? end + include Trustie::Gitlab::ManageMember + private + def remove_member_if_empty if member.roles.empty? member.destroy diff --git a/app/models/role.rb b/app/models/role.rb index f363b52bf..8bf5ebc05 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -77,6 +77,27 @@ class Role < ActiveRecord::Base self.givable[3..5] end + GUEST = 10 + REPORTER = 20 + DEVELOPER = 30 + MASTER = 40 + OWNER = 50 + def to_gitlab_role + case self.position + when 1,2 + GUEST + when 5 + REPORTER + when 4 + DEVELOPER + when 3 + MASTER + else + GUEST + end + end + + # Copies attributes from another role, arg can be an id or a Role def copy_from(arg, options={}) return unless arg.present? diff --git a/lib/trustie/gitlab/manage_member.rb b/lib/trustie/gitlab/manage_member.rb new file mode 100644 index 000000000..8201d1759 --- /dev/null +++ b/lib/trustie/gitlab/manage_member.rb @@ -0,0 +1,42 @@ +#coding=utf-8 +# +# +module Trustie + module Gitlab + + module ManageMember + def self.included(base) + base.class_eval { + before_create :add_gitlab_member + before_destroy :delete_gitlab_member + after_save :change_gitlab_member + } + end + + def change_gitlab_member + if self.member.project_id == 2 + @g ||= ::Gitlab.client + @g.edit_team_member(11, self.member.user.gid, self.role.to_gitlab_role ) + end + end + + def add_gitlab_member + if self.member.project_id == 2 + @g ||= ::Gitlab.client + @g.add_team_member(11, self.member.user.gid, self.role.to_gitlab_role ) + end + end + + def delete_gitlab_member + if member.roles.count <=1 + if self.member.project_id == 2 + @g ||= ::Gitlab.client + @g.remove_team_member(11, self.member.user.gid) + end + end + end + end + + end +end + From fbeb6585d25826b215917f89bc59c0203106b412 Mon Sep 17 00:00:00 2001 From: huang Date: Sat, 10 Oct 2015 16:18:05 +0800 Subject: [PATCH 007/169] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E8=BE=93=E5=87=BA=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/application.rb | 2 -- db/schema.rb | 34 ++++++++++++++-------------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/config/application.rb b/config/application.rb index fc19faf49..90cc299c6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -70,11 +70,9 @@ module RedmineApp config.action_view.sanitized_allowed_tags = 'div', 'p', 'span', 'img', 'embed' config.before_initialize do - puts "before initialize" end config.after_initialize do - puts "after initialize" end if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb')) diff --git a/db/schema.rb b/db/schema.rb index 9c874b3e8..8cc69e7f1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20150917022239) do +ActiveRecord::Schema.define(:version => 20150930011457) do create_table "activities", :force => true do |t| t.integer "act_id", :null => false @@ -575,6 +575,8 @@ ActiveRecord::Schema.define(:version => 20150917022239) do t.integer "viewed" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false + t.string "secret_key" + t.integer "status" end create_table "forums", :force => true do |t| @@ -619,11 +621,11 @@ ActiveRecord::Schema.define(:version => 20150917022239) do t.text "description" t.date "publish_time" t.date "end_time" - t.integer "homework_type", :default => 1 + t.integer "homework_type", :default => 1 t.string "late_penalty" t.integer "course_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "teacher_priority", :default => 1 end @@ -668,8 +670,8 @@ ActiveRecord::Schema.define(:version => 20150917022239) do t.text "input" t.text "output" t.integer "homework_common_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "result" t.text "error_msg" end @@ -783,16 +785,6 @@ ActiveRecord::Schema.define(:version => 20150917022239) do add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id" - create_table "journal_details_copy", :force => true do |t| - t.integer "journal_id", :default => 0, :null => false - t.string "property", :limit => 30, :default => "", :null => false - t.string "prop_key", :limit => 30, :default => "", :null => false - t.text "old_value" - t.text "value" - end - - add_index "journal_details_copy", ["journal_id"], :name => "journal_details_journal_id" - create_table "journal_replies", :id => false, :force => true do |t| t.integer "journal_id" t.integer "user_id" @@ -911,6 +903,7 @@ ActiveRecord::Schema.define(:version => 20150917022239) do t.datetime "updated_on", :null => false t.boolean "locked", :default => false t.integer "sticky", :default => 0 + t.integer "reply_id" end add_index "messages", ["author_id"], :name => "index_messages_on_author_id" @@ -1318,8 +1311,8 @@ ActiveRecord::Schema.define(:version => 20150917022239) do create_table "student_work_tests", :force => true do |t| t.integer "student_work_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "status", :default => 9 t.text "results" t.text "src" @@ -1335,8 +1328,8 @@ ActiveRecord::Schema.define(:version => 20150917022239) do t.float "student_score" t.float "teaching_asistant_score" t.integer "project_id", :default => 0 - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "late_penalty", :default => 0 t.integer "absence_penalty", :default => 0 t.float "system_score", :default => 0.0 @@ -1376,6 +1369,7 @@ ActiveRecord::Schema.define(:version => 20150917022239) do t.datetime "created_at", :null => false t.datetime "updated_at", :null => false t.text "description" + t.string "subject" end create_table "taggings", :force => true do |t| From 73898953d9f352b3f101c875f4abd953b6b24b01 Mon Sep 17 00:00:00 2001 From: huang Date: Tue, 13 Oct 2015 10:58:22 +0800 Subject: [PATCH 008/169] =?UTF-8?q?project=E8=A1=A8=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=8C=E8=AE=B0=E5=BD=95project=E5=AF=B9?= =?UTF-8?q?=E5=BA=94gitlba=E4=B8=ADproject=20id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 5 ++++- app/models/project.rb | 3 ++- db/migrate/20151013023237_add_gpid_to_project.rb | 5 +++++ db/schema.rb | 3 ++- lib/tasks/gitlab.rake | 4 +++- 5 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20151013023237_add_gpid_to_project.rb diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 024d5fdde..995c45e9b 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -254,7 +254,10 @@ update #if( !User.current.member_of?(@project) || @project.hidden_repo) @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? - + g = Gitlab.client + # r = g.get("/users?search=#{user.mail}").first + # rr = g.trees(@project.id, @path) + # r = g.get ("/projects/#{@project}/repository/tree") # :name, :path, :kind, :size, :lastrev, :changeset @entries = @repository.entries(@path, @rev) @changeset = @repository.find_changeset_by_name(@rev) diff --git a/app/models/project.rb b/app/models/project.rb index 8fe02b29f..6d24fa4ed 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -768,7 +768,8 @@ class Project < ActiveRecord::Base 'project_type', 'dts_test', 'attachmenttype', - 'enterprise_name' + 'enterprise_name', + 'gpid' diff --git a/db/migrate/20151013023237_add_gpid_to_project.rb b/db/migrate/20151013023237_add_gpid_to_project.rb new file mode 100644 index 000000000..bfa535253 --- /dev/null +++ b/db/migrate/20151013023237_add_gpid_to_project.rb @@ -0,0 +1,5 @@ +class AddGpidToProject < ActiveRecord::Migration + def change + add_column :projects, :gpid, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 8cc69e7f1..78e280e43 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20150930011457) do +ActiveRecord::Schema.define(:version => 20151013023237) do create_table "activities", :force => true do |t| t.integer "act_id", :null => false @@ -1146,6 +1146,7 @@ ActiveRecord::Schema.define(:version => 20150930011457) do t.string "enterprise_name" t.integer "organization_id" t.integer "project_new_type" + t.integer "gpid" end add_index "projects", ["lft"], :name => "index_projects_on_lft" diff --git a/lib/tasks/gitlab.rake b/lib/tasks/gitlab.rake index 5795ae4f4..0f6f78bb6 100644 --- a/lib/tasks/gitlab.rake +++ b/lib/tasks/gitlab.rake @@ -60,7 +60,9 @@ namespace :gitlab do user_id: gid, import_url: 'https://github.com/gitlabhq/gitlab-cli.git' ) - + project.gpid = gproject.id + project.save! + puts "Successfully created #{project.name}" # add team members # GUEST = 10 From 9eb498dbc4206d282f3974fea8df34eb387848cb Mon Sep 17 00:00:00 2001 From: huang Date: Tue, 13 Oct 2015 17:05:47 +0800 Subject: [PATCH 009/169] =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=BA=93=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=9B=AE=E5=BD=95=EF=BC=88=E6=9C=AA=E5=AE=8C=E6=88=90?= =?UTF-8?q?--=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 995c45e9b..5c1b54f92 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -255,11 +255,12 @@ update @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? g = Gitlab.client - # r = g.get("/users?search=#{user.mail}").first - # rr = g.trees(@project.id, @path) + project = g.project(11) + # rr = g.trees(project.id, @path) # r = g.get ("/projects/#{@project}/repository/tree") # :name, :path, :kind, :size, :lastrev, :changeset - @entries = @repository.entries(@path, @rev) + # @entries = @repository.entries(@path, @rev) + @entries = g.trees(project.id, @path) @changeset = @repository.find_changeset_by_name(@rev) #@project_path_cut = RepositoriesHelper::PROJECT_PATH_CUT From 61bcdd46439210d9f42a1477992f13812255df20 Mon Sep 17 00:00:00 2001 From: huang Date: Wed, 14 Oct 2015 17:37:22 +0800 Subject: [PATCH 010/169] =?UTF-8?q?=E5=8F=AA=E5=85=81=E8=AE=B8=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E4=B8=80=E4=B8=AA=E7=89=88=E6=9C=AC=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 21 ++- .../settings/_new_repositories.html.erb | 169 +++++++++--------- config/locales/commons/zh.yml | 3 +- 3 files changed, 102 insertions(+), 91 deletions(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 5c1b54f92..3552c0624 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -46,16 +46,21 @@ class RepositoriesController < ApplicationController rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed def new - scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first - @repository = Repository.factory(scm) - @repository.is_default = @project.repository.nil? - @repository.project = @project - @course_tag = params[:course] - if @course_tag == 1 - render :layout => 'base_courses' + if @project.repositories.count == 0 + scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first + @repository = Repository.factory(scm) + @repository.is_default = @project.repository.nil? + @repository.project = @project + @course_tag = params[:course] + if @course_tag == 1 + render :layout => 'base_courses' + else + render :layout => 'base_projects' + end else - render :layout => 'base_projects' + render_403 end + end diff --git a/app/views/projects/settings/_new_repositories.html.erb b/app/views/projects/settings/_new_repositories.html.erb index 410bd8c32..0859e5e19 100644 --- a/app/views/projects/settings/_new_repositories.html.erb +++ b/app/views/projects/settings/_new_repositories.html.erb @@ -8,93 +8,98 @@ <% project_path_cut = RepositoriesHelper::PROJECT_PATH_CUT %> <% ip = RepositoriesHelper::REPO_IP_ADDRESS %> <% if @project.repositories.any? %> - - - - - - - - - - - <% @project.repositories.sort.each do |repository| %> - - - - - <%if repository.scm_name=="Git"%> - + + + + + + + + + <% @project.repositories.sort.each do |repository| %> + + + + + <%if repository.scm_name=="Git"%> + - <%else %> - - <% end %> - - - - <% end %> + <%else %> + + <% end %> + + + + <% end %> + +
<%= l(:field_identifier) %> <%= l(:field_repository_is_default) %><%= l(:label_scm) %> <%= l(:label_repository_path) %>
- <%= link_to truncate(repository.identifier), ({:controller => 'repositories', :action => 'show', :id => @project, :repository_id => repository.identifier_param} if repository.identifier.present?) %> <%= checked_image repository.is_default? %><%=h repository.scm_name %> +
<%= l(:field_identifier) %> <%= l(:field_repository_is_default) %><%= l(:label_scm) %> <%= l(:label_repository_path) %>
+ <%= link_to truncate(repository.identifier), ({:controller => 'repositories', :action => 'show', :id => @project, :repository_id => repository.identifier_param} if repository.identifier.present?) %> <%= checked_image repository.is_default? %><%=h repository.scm_name %> <%=truncate( 'http://' << repository.login.to_s << '_'<< repository.identifier.to_s << '@'<< ip.to_s << h( repository.url.slice(project_path_cut, repository.url.length)),:length=>20)%><%=h truncate(repository.url,:length=>10) %> - <% if repository.scm_name=="Git"%> - <%if User.current.allowed_to?(:manage_repository, @project) %> - <%= link_to(l(:label_user_plural), committers_repository_path(repository)) %> - <% end %> - <% end %> - - <% if repository.login.to_s==User.current.login.to_s %> - <%= delete_new_link repository_path(repository) %> - <% end %>
<%=h truncate(repository.url,:length=>10) %> + <% if repository.scm_name=="Git"%> + <%if User.current.allowed_to?(:manage_repository, @project) %> + <%= link_to(l(:label_user_plural), committers_repository_path(repository)) %> + <% end %> + <% end %> + + <% if repository.login.to_s==User.current.login.to_s %> + <%= delete_new_link repository_path(repository) %> + <% end %>
<% else %> -

<%= l(:label_no_data) %>

+

<%= l(:label_repository_no_data) %>

<% end %> - - - - - <% course_tag = @project.project_type %> - <% if User.current.allowed_to?(:manage_repository, @project) %> - - <%= link_to l(:label_repository_new_repos),"#" , :onclick=>"pro_st_show_ku();", :class => 'c_blue fl' %>

- <% end %> -
-
+<%# 新建版本库 %> +<% if @project.repositories.count == 0 %> + + + <% course_tag = @project.project_type %> + <% if User.current.allowed_to?(:manage_repository, @project) %> + + <%= link_to l(:label_repository_new_repos),"#" , :onclick=>"pro_st_show_ku();", :class => 'c_blue fl' %>

+ <% end %> +
+
-<%= labelled_form_for :repository, @repository, :url =>project_repositories_path(@project),:html => {:id => 'repository-form',:method=>"post",:autocomplete=>'off'} do |f| %> -
-
    -
  • - - <%= select_tag('repository_scm', - options_for_select(["Git"],@repository.class.name.demodulize), - :data => {:remote => true, :method => 'get'})%> - <% if @repository && ! @repository.class.scm_available %> - <%= l(:text_scm_command_not_available) %> - <% end %> -
  • + <%= labelled_form_for :repository, @repository, :url =>project_repositories_path(@project),:html => {:id => 'repository-form',:method=>"post",:autocomplete=>'off'} do |f| %> +
    +
      +
    • + + <%= select_tag('repository_scm', + options_for_select(["Git"],@repository.class.name.demodulize), + :data => {:remote => true, :method => 'get'})%> + <% if @repository && ! @repository.class.scm_available %> + <%= l(:text_scm_command_not_available) %> + <% end %> +
    • - <% unless judge_main_repository(@project) %> -
    • - - <%= f.check_box :is_default, :label => "", :no_label => true %>

      -
    • + <% unless judge_main_repository(@project) %> +
    • + + <%= f.check_box :is_default, :label => "", :no_label => true %>

      +
    • + <% end %> +
    • + + + + <%= f.text_field :identifier, :disabled =>@repository.nil? || @repository.identifier_frozen? ? true:false,:label=>"", :no_label => true %> + <% unless @repository.identifier_frozen? %> + <%=l(:text_length_between,:min=>1,:max=>254)< + <% end %> +
    • +
    • + + <%= f.password_field :upassword, :label=> "", :no_label => true%> + <%= l(:label_upassword_info)%> +
    • +
      +
    + <%=l(:button_save)%> + <%=l(:button_cancel)%> +
    <% end %> -
  • - - - - <%= f.text_field :identifier, :disabled =>@repository.nil? || @repository.identifier_frozen? ? true:false,:label=>"", :no_label => true %> - <% unless @repository.identifier_frozen? %> - <%=l(:text_length_between,:min=>1,:max=>254)< - <% end %> -
  • -
  • - - <%= f.password_field :upassword, :label=> "", :no_label => true%> - <%= l(:label_upassword_info)%> -
  • -
    -
- <%=l(:button_save)%> - <%=l(:button_cancel)%> -
+ <% end %> + diff --git a/config/locales/commons/zh.yml b/config/locales/commons/zh.yml index 7cbb223c6..61bad2a00 100644 --- a/config/locales/commons/zh.yml +++ b/config/locales/commons/zh.yml @@ -199,7 +199,8 @@ zh: label_descripition_blank: 描述不能为空 label_subject_empty: 主题不能为空 - label_no_data: 没有任何数据可供显示 + label_no_data: 没有任何数据可供显示 + label_repository_no_data: 您还没有创建版本库,每个项目只允许创建一个版本库! # 项目、课程、用户公用 label_settings: 配置 label_information_plural: 信息 From 9e8e77f3e1595188d5ab5f42523feff925c1f9c4 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Fri, 16 Oct 2015 16:57:45 +0800 Subject: [PATCH 011/169] =?UTF-8?q?=E6=96=B9=E6=B3=95=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/schema.rb | 27 ++---------- lib/tasks/gitlab.rake | 80 ++++++++++------------------------- lib/trustie/gitlab/sync.rb | 86 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 83 deletions(-) create mode 100644 lib/trustie/gitlab/sync.rb diff --git a/db/schema.rb b/db/schema.rb index 78d0c5fd6..e1d43a74d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -497,27 +497,6 @@ ActiveRecord::Schema.define(:version => 20151013023237) do add_index "documents", ["created_on"], :name => "index_documents_on_created_on" add_index "documents", ["project_id"], :name => "documents_project_id" - create_table "dts", :primary_key => "Num", :force => true do |t| - t.string "Defect", :limit => 50 - t.string "Category", :limit => 50 - t.string "File" - t.string "Method" - t.string "Module", :limit => 20 - t.string "Variable", :limit => 50 - t.integer "StartLine" - t.integer "IPLine" - t.string "IPLineCode", :limit => 200 - t.string "Judge", :limit => 15 - t.integer "Review", :limit => 1 - t.string "Description" - t.text "PreConditions", :limit => 2147483647 - t.text "TraceInfo", :limit => 2147483647 - t.text "Code", :limit => 2147483647 - t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "id", :null => false -======= create_table "dts", :force => true do |t| t.string "IPLineCode" t.string "Description" @@ -1330,9 +1309,9 @@ ActiveRecord::Schema.define(:version => 20151013023237) do create_table "student_work_tests", :force => true do |t| t.integer "student_work_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "status", :default => 9 + t.integer "status" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.text "results" t.text "src" end diff --git a/lib/tasks/gitlab.rake b/lib/tasks/gitlab.rake index 0f6f78bb6..e21b5f329 100644 --- a/lib/tasks/gitlab.rake +++ b/lib/tasks/gitlab.rake @@ -1,80 +1,42 @@ +require 'trustie/gitlab/sync' + namespace :gitlab do namespace :sync do - - module Helper - def self.change_password(uid, en_pwd, salt) - g = Gitlab.client - options = {:encrypted_password=>en_pwd, :password_salt=>salt} - g.put("/users/ext/#{uid}", :body => options) - # g.edit_user(uid, :encrypted_password=>en_pwd, :password_salt=>salt) - end - end - - desc "sync users to gitlab" task :users => :environment do # User.where(username: 'root').find_each do |user| + s = Trustie::Gitlab::Sync.new User.where(login: 'guange1').find_each do |user| - begin - g = Gitlab.client - u = g.get("/users?search=#{user.mail}").first - unless u - u = g.create_user(user.mail, user.hashed_password, name: user.show_name, username: user.login, confirm: "true") - user.gid = u.id - user.save! - puts "create user #{user.login}" - end - Helper.change_password(u.id, user.hashed_password, user.salt) - rescue => e - puts e - end + s.sync_user(user) end end desc "update user password" task :password => :environment do - Helper.change_password(1,'5188b7a65acf294ee7deceb397b6f9c62214ea50','dcb8d9fffabec60c2d0d1030b679fbbb') + s = Trustie::Gitlab::Sync.new + s.change_password(1,'5188b7a65acf294ee7deceb397b6f9c62214ea50','dcb8d9fffabec60c2d0d1030b679fbbb') end - desc "sync projects to gitlab" task :projects => :environment do + s = Trustie::Gitlab::Sync.new Project.where(id: 505).find_each do |project| - g = Gitlab.client - gid = project.owner.gid - raise "unknow gid" unless gid - path = project.repositories.where(:is_default => true).first.root_url.split('/').last - path = path.split('.').first - raise "unknow path" unless path - - # import url http://xianbo_trustie2:1234@repository.trustie.net/xianbo/trustie2.git - # can use password - gproject = g.create_project(project.identifier, - path: path, - description: project.description, - wiki_enabled: false, - wall_enabled: false, - issues_enabled: false, - snippets_enabled: false, - public: false, - user_id: gid, - import_url: 'https://github.com/gitlabhq/gitlab-cli.git' - ) - project.gpid = gproject.id - project.save! - puts "Successfully created #{project.name}" - # add team members - # - GUEST = 10 - REPORTER = 20 - DEVELOPER = 30 - MASTER = 40 - OWNER = 50 + s.sync_project(project, path: 'trustie', import_url: 'http://xianbo_trustie2:1234@repository.trustie.net/xianbo/trustie2.git') + end + end - project.members.each do |m| - g.add_team_member(gproject.id, m.user.gid, DEVELOPER) + desc "remove all projects" + task :remove_all_projects => :environment do + g = Gitlab.client + 100.times do + g.projects(scope: 'all').each do |p| + puts p.id + begin + g.delete_project(p.id) + rescue => e + puts e end - + end end end diff --git a/lib/trustie/gitlab/sync.rb b/lib/trustie/gitlab/sync.rb new file mode 100644 index 000000000..91c241cea --- /dev/null +++ b/lib/trustie/gitlab/sync.rb @@ -0,0 +1,86 @@ +module Trustie + module Gitlab + module UserLevel + GUEST = 10 + REPORTER = 20 + DEVELOPER = 30 + MASTER = 40 + OWNER = 50 + end + + class Sync + attr :g + + def initialize + @g = ::Gitlab.client + end + + def change_password(uid, en_pwd, salt) + options = {:encrypted_password=>en_pwd, :password_salt=>salt} + self.g.put("/users/ext/#{uid}", :body => options) + # g.edit_user(uid, :encrypted_password=>en_pwd, :password_salt=>salt) + end + + def sync_user(user) + begin + u = self.g.get("/users?search=#{user.mail}").first + unless u + u = self.g.create_user(user.mail, user.hashed_password, name: user.show_name, username: user.login, confirm: "true") + user.gid = u.id + user.save! + puts "create user #{user.login}" + end + Helper.change_password(u.id, user.hashed_password, user.salt) + rescue => e + puts e + end + end + + + def sync_project(project, opt={}) + gid = project.owner.gid + raise "unknow gid" unless gid + path = opt[:path] + raise "unknow path" unless path + import_url = opt[:import_url] + raise "unknow import_url" unless import_url + + if opt[:password] + import_url.sub('@', ":#{opt[:password]}@") + end + + + # import url http://xianbo_trustie2:1234@repository.trustie.net/xianbo/trustie2.git + # can use password + gproject = self.g.create_project(path, + path: path, + description: project.description, + wiki_enabled: false, + wall_enabled: false, + issues_enabled: false, + snippets_enabled: false, + public: false, + user_id: gid, + import_url: import_url + ) + project.gpid = gproject.id + project.save! + puts "Successfully created #{project.name}" + # add team members + # + + project.members.each do |m| + begin + self.g.add_team_member(gproject.id, m.user.gid, UserLevel::DEVELOPER) + rescue => e + puts e + end + end + end + + def remove_project + end + + end + end +end \ No newline at end of file From 765d2b875d5779a2bee611c694185e4a8901a919 Mon Sep 17 00:00:00 2001 From: huang Date: Fri, 16 Oct 2015 20:06:03 +0800 Subject: [PATCH 012/169] =?UTF-8?q?=E8=BF=98=E5=8E=9F=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 3552c0624..72af09448 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -259,13 +259,13 @@ update #if( !User.current.member_of?(@project) || @project.hidden_repo) @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? - g = Gitlab.client - project = g.project(11) + #g = Gitlab.client + #project = g.project(11) # rr = g.trees(project.id, @path) # r = g.get ("/projects/#{@project}/repository/tree") # :name, :path, :kind, :size, :lastrev, :changeset - # @entries = @repository.entries(@path, @rev) - @entries = g.trees(project.id, @path) + @entries = @repository.entries(@path, @rev) + #@entries = g.trees(project.id, @path) @changeset = @repository.find_changeset_by_name(@rev) #@project_path_cut = RepositoriesHelper::PROJECT_PATH_CUT From 3a21e9f2b035552da23217275b75503213083cab Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sun, 18 Oct 2015 13:16:58 +0800 Subject: [PATCH 013/169] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tasks/gitlab.rake | 2 +- lib/trustie/gitlab/sync.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tasks/gitlab.rake b/lib/tasks/gitlab.rake index e21b5f329..9529ce4c2 100644 --- a/lib/tasks/gitlab.rake +++ b/lib/tasks/gitlab.rake @@ -6,7 +6,7 @@ namespace :gitlab do task :users => :environment do # User.where(username: 'root').find_each do |user| s = Trustie::Gitlab::Sync.new - User.where(login: 'guange1').find_each do |user| + User.find_each do |user| s.sync_user(user) end end diff --git a/lib/trustie/gitlab/sync.rb b/lib/trustie/gitlab/sync.rb index 91c241cea..597e01a36 100644 --- a/lib/trustie/gitlab/sync.rb +++ b/lib/trustie/gitlab/sync.rb @@ -30,7 +30,7 @@ module Trustie user.save! puts "create user #{user.login}" end - Helper.change_password(u.id, user.hashed_password, user.salt) + change_password(u.id, user.hashed_password, user.salt) rescue => e puts e end From b80a0d6366b44431a1a3fb41f7bf5bf6d86d07b9 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sun, 18 Oct 2015 13:25:10 +0800 Subject: [PATCH 014/169] . --- lib/tasks/gitlab.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/gitlab.rake b/lib/tasks/gitlab.rake index 9529ce4c2..348e7ffed 100644 --- a/lib/tasks/gitlab.rake +++ b/lib/tasks/gitlab.rake @@ -20,7 +20,7 @@ namespace :gitlab do desc "sync projects to gitlab" task :projects => :environment do s = Trustie::Gitlab::Sync.new - Project.where(id: 505).find_each do |project| + Project.where(id: ENV["PROJECT_ID"]).find_each do |project| s.sync_project(project, path: 'trustie', import_url: 'http://xianbo_trustie2:1234@repository.trustie.net/xianbo/trustie2.git') end end From 07084a94c5796e9f6e253d0ddb2190414486a0f5 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sun, 18 Oct 2015 13:50:59 +0800 Subject: [PATCH 015/169] . --- lib/tasks/gitlab.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/gitlab.rake b/lib/tasks/gitlab.rake index 348e7ffed..ffa81e912 100644 --- a/lib/tasks/gitlab.rake +++ b/lib/tasks/gitlab.rake @@ -21,7 +21,7 @@ namespace :gitlab do task :projects => :environment do s = Trustie::Gitlab::Sync.new Project.where(id: ENV["PROJECT_ID"]).find_each do |project| - s.sync_project(project, path: 'trustie', import_url: 'http://xianbo_trustie2:1234@repository.trustie.net/xianbo/trustie2.git') + s.sync_project(project, path: ENV["REP_NAME"], import_url: project.repository.url) end end From f1af041f24388400f7b98e3769d5254f2fe59a20 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sun, 18 Oct 2015 13:56:10 +0800 Subject: [PATCH 016/169] =?UTF-8?q?gitlab=20key=20=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/initializers/gitlab_config.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/gitlab_config.rb b/config/initializers/gitlab_config.rb index 61fc12287..75edc8eff 100644 --- a/config/initializers/gitlab_config.rb +++ b/config/initializers/gitlab_config.rb @@ -2,7 +2,7 @@ Gitlab.configure do |config| # config.endpoint = 'http://192.168.41.130:3000/trustie/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] # config.private_token = 'cK15gUDwvt8EEkzwQ_63' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN'] config.endpoint = 'http://git.trustie.net/trustie/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT'] - config.private_token = 'fxm19wjRAs4REgTJwgtn' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN'] + config.private_token = 'fPc_gBmEiSANve8TCfxW' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN'] # Optional # config.user_agent = 'Custom User Agent' # user agent, default: 'Gitlab Ruby Gem [version]' # config.sudo = 'user' # username for sudo mode, default: nil From acbd690bc651e266066a1c82a8a0f7d796c8f780 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sun, 18 Oct 2015 21:04:04 +0800 Subject: [PATCH 017/169] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 18 +++++++++++++++++- app/views/repositories/to_gitlab.html.erb | 4 ++++ config/routes.rb | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 app/views/repositories/to_gitlab.html.erb diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 72af09448..492efbf75 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -32,7 +32,7 @@ class RepositoriesController < ApplicationController before_filter :find_project_by_project_id, :only => [:new, :create, :newrepo] before_filter :find_repository, :only => [:edit, :update, :destroy, :committers] - before_filter :find_project_repository, :except => [:new, :create, :newcreate, :edit, :update, :destroy, :committers, :newrepo] + before_filter :find_project_repository, :except => [:new, :create, :newcreate, :edit, :update, :destroy, :committers, :newrepo,:to_gitlab] before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue] before_filter :authorize , :except => [:newrepo,:newcreate,:fork] accept_rss_auth :revisions @@ -247,6 +247,16 @@ update redirect_to settings_project_url(@project, :tab => 'repositories') end + def to_gitlab + @project = Project.find(params[:project_id]) + @repository = Repository.find(params[:id]) + s = Trustie::Gitlab::Sync.new + s.sync_project(@project, path: params[:repo_name], import_url: @repository.url) + @repository.type = 'Repository::Gitlab' + @repository.save + redirect_to :controller => 'repositories', :action => 'show', :id => @project.id, to: 'gitlab' + end + def show ## TODO: the below will move to filter, done. if !User.current.member_of?(@project) @@ -256,6 +266,12 @@ update end end + unless @repository && @repository.type == 'Repository::Gitlab' + # redirect_to to_gitlab_project_repository_path(@project, @repository) + render :to_gitlab + return + end + #if( !User.current.member_of?(@project) || @project.hidden_repo) @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? diff --git a/app/views/repositories/to_gitlab.html.erb b/app/views/repositories/to_gitlab.html.erb new file mode 100644 index 000000000..eccf7259d --- /dev/null +++ b/app/views/repositories/to_gitlab.html.erb @@ -0,0 +1,4 @@ +<%= form_for(@repository, url: to_gitlab_project_repository_path(@project, @repository)) do |f| %> + + +<% end %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 810c22e7a..7a07c86db 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -533,6 +533,7 @@ RedmineApp::Application.routes.draw do resources :repositories, :except => [:index, :show] do member do get 'newrepo', :via => [:get, :post] + put 'to_gitlab' # get 'create', :via=>[:get, :post] end end From 74fd461facbc06d95b8583f855ddbf26e994f97f Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sun, 18 Oct 2015 21:16:03 +0800 Subject: [PATCH 018/169] . --- app/controllers/repositories_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 492efbf75..442f6d22c 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -34,7 +34,7 @@ class RepositoriesController < ApplicationController before_filter :find_repository, :only => [:edit, :update, :destroy, :committers] before_filter :find_project_repository, :except => [:new, :create, :newcreate, :edit, :update, :destroy, :committers, :newrepo,:to_gitlab] before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue] - before_filter :authorize , :except => [:newrepo,:newcreate,:fork] + before_filter :authorize , :except => [:newrepo,:newcreate,:fork, :to_gitlab] accept_rss_auth :revisions # hidden repositories filter // 隐藏代码过滤器 before_filter :check_hidden_repo, :only => [:show, :stats, :revisions, :revision, :diff ] From 98bd46ceb4b7f975c39e174f2b79b09c00c4bbe9 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sun, 18 Oct 2015 21:24:59 +0800 Subject: [PATCH 019/169] =?UTF-8?q?=E9=80=9A=E8=BF=87url=E8=AF=86=E5=88=AB?= =?UTF-8?q?project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/redmine/scm/adapters/gitlab_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/redmine/scm/adapters/gitlab_adapter.rb b/lib/redmine/scm/adapters/gitlab_adapter.rb index cad6583f6..449de7ede 100644 --- a/lib/redmine/scm/adapters/gitlab_adapter.rb +++ b/lib/redmine/scm/adapters/gitlab_adapter.rb @@ -16,7 +16,7 @@ module Redmine def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil) super @g = Gitlab.client - @project = 11 + @project = Project.find_by_url(url).gpid @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding end From 65e6444272547292621ae60e52c2295457f00380 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sun, 18 Oct 2015 21:31:00 +0800 Subject: [PATCH 020/169] . --- lib/redmine/scm/adapters/gitlab_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/redmine/scm/adapters/gitlab_adapter.rb b/lib/redmine/scm/adapters/gitlab_adapter.rb index 449de7ede..404243c89 100644 --- a/lib/redmine/scm/adapters/gitlab_adapter.rb +++ b/lib/redmine/scm/adapters/gitlab_adapter.rb @@ -16,7 +16,7 @@ module Redmine def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil) super @g = Gitlab.client - @project = Project.find_by_url(url).gpid + @project = Repository.find_by_url(url).project.gpid @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding end From f189e3cde898acd366e4ab739b7e59832d13c77c Mon Sep 17 00:00:00 2001 From: huang Date: Mon, 19 Oct 2015 14:03:08 +0800 Subject: [PATCH 021/169] 0 --- app/controllers/repositories_controller.rb | 144 ++++++++++----------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 3552c0624..8f90dd65b 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -29,7 +29,7 @@ class RepositoriesController < ApplicationController menu_item :repository menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers] default_search_scope :changesets - + before_filter :find_project_by_project_id, :only => [:new, :create, :newrepo] before_filter :find_repository, :only => [:edit, :update, :destroy, :committers] before_filter :find_project_repository, :except => [:new, :create, :newcreate, :edit, :update, :destroy, :committers, :newrepo] @@ -42,7 +42,7 @@ class RepositoriesController < ApplicationController include RepositoriesHelper helper :project_score #@root_path = RepositoriesHelper::ROOT_PATH - + rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed def new @@ -62,8 +62,8 @@ class RepositoriesController < ApplicationController end end - - + + def newrepo scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first @repository = Repository.factory(scm) @@ -76,23 +76,23 @@ class RepositoriesController < ApplicationController render :layout => 'base_projects' end end - + def fork @repository_url = params[:repository_url] - + # @repository.url # system "htpasswd -mb "+@root_path+"user.passwd "+params[:repository][:identifier]+" "+@upasswd # system "echo -e '"+params[:project_id]+"-"+params[:repository][:identifier]+"-write:"+ - # " "+params[:repository][:identifier]+"' >> "+@root_path+"group.passwd" - system "git clone --bare "+@repository_url + # " "+params[:repository][:identifier]+"' >> "+@root_path+"group.passwd" + system "git clone --bare "+@repository_url # system "mv "+@project_path+"/hooks/post-update{.sample,}" # system "chmod a+x "+@project_path+"/hooks/post-update" # system "."+@project_path+"/hooks/post-update" # system "echo -e 'Allow from all \n Order Deny,Allow \n "+ - # " \n"+ - # "Require group "+params[:project_id]+"-"+params[:repository][:identifier]+"-write \n "+ - # " \n ' >>"+ - # @project_path+"/.htaccess" + # " \n"+ + # "Require group "+params[:project_id]+"-"+params[:repository][:identifier]+"-write \n "+ + # " \n ' >>"+ + # @project_path+"/.htaccess" flash[:notice] = l(:label_notice_fork_successed) @repositories = @project.repositories render :action => 'show', :layout => 'base_projects' @@ -141,7 +141,7 @@ update logger.info "the value of create repository"+@root_path+": "+@repository_name+": "+@project_path+": "+@repo_name attrs = pickup_extra_info if((@repository_tag!="")&¶ms[:repository_scm]=="Git") - params[:repository][:url]=@project_path + params[:repository][:url]=@project_path end ###xianbo @repository = Repository.factory(params[:repository_scm]) @@ -175,7 +175,7 @@ update end redirect_to settings_project_url(@project, :tab => 'repositories',:repository_error_message=>@repository.errors.full_messages) else if(@repository_tag.blank?) - #render :action => 'newrepo', :layout =>'base_projects' + #render :action => 'newrepo', :layout =>'base_projects' redirect_to settings_project_url(@project, :tab => 'repositories',:repository => "pswd_is_null",:repository_error_message=>@repository.errors.full_messages) else redirect_to settings_project_url(@project, :tab => 'repositories',:repository => @repository,:repository_error_message=>@repository.errors.full_messages) @@ -185,7 +185,7 @@ update end end - + def edit end @@ -230,11 +230,11 @@ update flash[:notice] = l(:notice_successful_update) redirect_to settings_project_url(@project, :tab => 'repositories') elsif request.get? - respond_to do |format| - format.html{ - render :layout => "base_projects" - } - end + respond_to do |format| + format.html{ + render :layout => "base_projects" + } + end end @@ -275,7 +275,7 @@ update @entries ? render(:partial => 'dir_list_content') : render(:nothing => true) else #Modified by young - # (show_error_not_found; return) unless @entries + # (show_error_not_found; return) unless @entries @changesets = @repository.latest_changesets(@path, @rev) @properties = @repository.properties(@path, @rev) @repositories = @project.repositories @@ -283,7 +283,7 @@ update project_path_cut = RepositoriesHelper::PROJECT_PATH_CUT ip = RepositoriesHelper::REPO_IP_ADDRESS @repos_url = "http://"+@repository.login.to_s+"_"+@repository.identifier.to_s+"@"+ip.to_s+ - @repository.url.slice(project_path_cut, @repository.url.length).to_s + @repository.url.slice(project_path_cut, @repository.url.length).to_s if @course_tag == 1 render :action => 'show', :layout => 'base_courses' else @@ -309,10 +309,10 @@ update per_page_option, params['page'] @changesets = @repository.changesets. - limit(@changeset_pages.per_page). - offset(@changeset_pages.offset). - includes(:user, :repository, :parents). - all + limit(@changeset_pages.per_page). + offset(@changeset_pages.offset). + includes(:user, :repository, :parents). + all respond_to do |format| format.html { render :layout => 'base_projects' } @@ -338,8 +338,8 @@ update @content = @repository.cat(@path, @rev) (show_error_not_found; return) unless @content if is_raw || - (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || - ! is_entry_text_data?(@content, @path) + (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || + ! is_entry_text_data?(@content, @path) # Force the download send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) } send_type = Redmine::MimeType.of(@path) @@ -422,8 +422,8 @@ update filename = "changeset_r#{@rev}" filename << "_r#{@rev_to}" if @rev_to send_data @diff.join, :filename => "#{filename}.diff", - :type => 'text/x-patch', - :disposition => 'attachment' + :type => 'text/x-patch', + :disposition => 'attachment' else @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) @@ -434,7 +434,7 @@ update User.current.preference.save end @cache_key = "repositories/diff/#{@repository.id}/" + - Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}") + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}") unless read_fragment(@cache_key) @diff = @repository.diff(@path, @rev, @rev_to) unless @diff @@ -461,16 +461,16 @@ update def graph data = nil case params[:graph] - when "commits_per_month" - data = graph_commits_per_month(@repository) - when "commits_per_author" - data = graph_commits_per_author(@repository) - when "author_commits_per_month" - data = graph_author_commits_per_month(@repository) - when "author_commits_six_month" - data = author_commits_six_month(@repository) - when "author_code_six_months" - data = author_code_six_month(@repository) + when "commits_per_month" + data = graph_commits_per_month(@repository) + when "commits_per_author" + data = graph_commits_per_author(@repository) + when "author_commits_per_month" + data = graph_author_commits_per_month(@repository) + when "author_commits_six_month" + data = author_commits_six_month(@repository) + when "author_code_six_months" + data = author_code_six_month(@repository) end if data headers["Content-Type"] = "image/svg+xml" @@ -550,14 +550,14 @@ update @date_from = @date_to << 11 @date_from = Date.civil(@date_from.year, @date_from.month, 1) commits_by_day = Changeset.count( - :all, :group => :commit_date, - :conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) + :all, :group => :commit_date, + :conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) commits_by_month = [0] * 12 commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } changes_by_day = Change.count( - :all, :group => :commit_date, :include => :changeset, - :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) + :all, :group => :commit_date, :include => :changeset, + :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) changes_by_month = [0] * 12 changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } @@ -565,26 +565,26 @@ update 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)} graph = SVG::Graph::Bar.new( - :height => 300, - :width => 600, - :fields => fields.reverse, - :stack => :side, - :scale_integers => true, - :step_x_labels => 2, - :show_data_values => true, - :graph_title => l(:label_commits_per_month), - :show_graph_title => true + :height => 300, + :width => 600, + :fields => fields.reverse, + :stack => :side, + :scale_integers => true, + :step_x_labels => 2, + :show_data_values => true, + :graph_title => l(:label_commits_per_month), + :show_graph_title => true ) # 具状图 graph.add_data( - :data => commits_by_month[0..11].reverse, - :title => l(:label_revision_plural) + :data => commits_by_month[0..11].reverse, + :title => l(:label_revision_plural) ) graph.add_data( - :data => changes_by_month[0..11].reverse, - :title => l(:label_change_plural) + :data => changes_by_month[0..11].reverse, + :title => l(:label_change_plural) ) graph.burn @@ -609,23 +609,23 @@ update fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } graph = SVG::Graph::BarHorizontal.new( - :height => 400, - :width => 600, - :fields => fields, - :stack => :side, - :scale_integers => true, - :show_data_values => true, - :rotate_y_labels => false, - :graph_title => l(:label_commits_per_author), - :show_graph_title => true + :height => 400, + :width => 600, + :fields => fields, + :stack => :side, + :scale_integers => true, + :show_data_values => true, + :rotate_y_labels => false, + :graph_title => l(:label_commits_per_author), + :show_graph_title => true ) graph.add_data( - :data => commits_data, - :title => l(:label_revision_plural) + :data => commits_data, + :title => l(:label_revision_plural) ) graph.add_data( - :data => changes_data, - :title => l(:label_change_plural) + :data => changes_data, + :title => l(:label_change_plural) ) graph.burn end @@ -636,7 +636,7 @@ update @date_from = @date_to << 12 @date_from = Date.civil(@date_from.year, @date_from.month, @date_from.day) commits_by_author = Changeset.count(:all, :group => :committer, - :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) + :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) commits_by_author = commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}.last(25) fields = commits_by_author.collect {|r| r.first} From 11d1cac40da69e01582a0bf691becd96c3db67c3 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Wed, 21 Oct 2015 17:13:07 +0800 Subject: [PATCH 022/169] =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E9=80=9A=E8=BF=87git?= =?UTF-8?q?lab=E5=88=9B=E5=BB=BA=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/application_controller.rb | 3 +- app/controllers/repositories_controller.rb | 96 ++++++------------- app/models/repository.rb | 5 + .../settings/_new_repositories.html.erb | 12 --- app/views/repositories/_form.html.erb | 7 +- app/views/repositories/_form_create.html.erb | 3 - lib/trustie/gitlab/helper.rb | 37 +++++++ lib/trustie/gitlab/manage_member.rb | 30 ++++-- lib/trustie/gitlab/manage_user.rb | 37 +++++++ lib/trustie/gitlab/sync.rb | 29 ++---- 10 files changed, 140 insertions(+), 119 deletions(-) create mode 100644 lib/trustie/gitlab/manage_user.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 86c220bd2..a7b6ac114 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -38,7 +38,8 @@ class ApplicationController < ActionController::Base protect_from_forgery def handle_unverified_request super - cookies.delete(autologin_cookie_name) + raise(ActionController::InvalidAuthenticityToken) + # cookies.delete(autologin_cookie_name) end before_filter :find_first_page diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 442f6d22c..9d1284fde 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -115,74 +115,34 @@ update } def create - if params[:repository_scm].to_s == 'Gitlab' - # add by nwb - # 增加对gitlab版本库的支持 - attrs = pickup_extra_info - @repository = Repository.factory('Git') - @repository.safe_attributes = params[:repository] - if attrs[:attrs_extra].keys.any? - @repository.merge_extra_info(attrs[:attrs_extra]) - end - @repository.project = @project - if request.post? && @repository.save - redirect_to settings_project_url(@project, :tab => 'repositories') - else - redirect_to settings_project_url(@project, :tab => 'repositories') - end - else # 原逻辑 - ##xianbo - @root_path=RepositoriesHelper::ROOT_PATH - @repository_name=User.current.login.to_s+"/"+params[:repository][:identifier]+".git" - @project_path=@root_path+"htdocs/"+@repository_name - @repository_tag=params[:repository][:upassword] || params[:repository][:password] - @repo_name=User.current.login.to_s+"_"+params[:repository][:identifier] - logger.info "htpasswd -mb "+@root_path+"htdocs/user.passwd "+@repo_name+": "+@repository_tag - logger.info "the value of create repository"+@root_path+": "+@repository_name+": "+@project_path+": "+@repo_name - attrs = pickup_extra_info - if((@repository_tag!="")&¶ms[:repository_scm]=="Git") - params[:repository][:url]=@project_path - end - ###xianbo - @repository = Repository.factory(params[:repository_scm]) - @repository.safe_attributes = params[:repository] - if attrs[:attrs_extra].keys.any? - @repository.merge_extra_info(attrs[:attrs_extra]) - end - - @repository.project = @project - if request.post? && @repository.save - if(params[:repository_scm]=="Git") - system "htpasswd -mb "+@root_path+"htdocs/user.passwd "+@repo_name+" "+@repository_tag - system "echo -e '"+@repo_name+"-write:"+ - " "+@repo_name+"' >> "+@root_path+"htdocs/group.passwd" - system "mkdir "+@root_path+"htdocs/"+User.current.login.to_s - system "git init --bare "+@project_path - system "mv "+@project_path+"/hooks/post-update{.sample,}" - system "chmod a+x "+@project_path+"/hooks/post-update" - system "echo -e 'Allow from all \n Order Deny,Allow \n "+ - " \n"+ - "Require group "+@repo_name+"-write \n "+ - " \n ' >> "+ - @root_path+"htdocs/"+ @repository_name+"/.htaccess" - system "cd "+@project_path+" ;git update-server-info" - - File.open(@project_path+"/hooks/post-update", "w+") do |f| - f.write(HOOK_TEMPLATE) - end - - @repository.update_attributes(:login => User.current.login.to_s) - end - redirect_to settings_project_url(@project, :tab => 'repositories',:repository_error_message=>@repository.errors.full_messages) - else if(@repository_tag.blank?) - #render :action => 'newrepo', :layout =>'base_projects' - redirect_to settings_project_url(@project, :tab => 'repositories',:repository => "pswd_is_null",:repository_error_message=>@repository.errors.full_messages) - else - redirect_to settings_project_url(@project, :tab => 'repositories',:repository => @repository,:repository_error_message=>@repository.errors.full_messages) - end - end - + attrs = pickup_extra_info + @repository = Repository.factory('Git') + @repository.safe_attributes = params[:repository] + if attrs[:attrs_extra].keys.any? + @repository.merge_extra_info(attrs[:attrs_extra]) + end + @repository.project = @project + @repository.type = 'Repository::Gitlab' + @repository.url = @repository.identifier + if request.post? && @repository.save + g = ::Gitlab.client + gid = @project.owner.gid + gproject = g.create_project(@repository.identifier, + path: @repository.identifier, + description: @project.description, + wiki_enabled: false, + wall_enabled: false, + issues_enabled: false, + snippets_enabled: false, + public: false, + user_id: gid + ) + @project.gpid = gproject.id + @project.save! + redirect_to settings_project_url(@project, :tab => 'repositories') + else + redirect_to settings_project_url(@project, :tab => 'repositories',:repository_error_message=>@repository.errors.full_messages) end end @@ -266,7 +226,7 @@ update end end - unless @repository && @repository.type == 'Repository::Gitlab' + unless @repository.gitlab? # redirect_to to_gitlab_project_repository_path(@project, @repository) render :to_gitlab return diff --git a/app/models/repository.rb b/app/models/repository.rb index 5680f05a7..94b7905c6 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -57,6 +57,11 @@ class Repository < ActiveRecord::Base safe_attributes 'url', :if => lambda {|repository, user| repository.new_record?} + + def gitlab? + self.type == 'Repository::Gitlab' + end + def repo_create_validation unless Setting.enabled_scm.include?(self.class.name.demodulize) errors.add(:type, :invalid) diff --git a/app/views/projects/settings/_new_repositories.html.erb b/app/views/projects/settings/_new_repositories.html.erb index 0859e5e19..bfb87b16a 100644 --- a/app/views/projects/settings/_new_repositories.html.erb +++ b/app/views/projects/settings/_new_repositories.html.erb @@ -73,13 +73,6 @@ <%= l(:text_scm_command_not_available) %> <% end %> - - <% unless judge_main_repository(@project) %> -
  • - - <%= f.check_box :is_default, :label => "", :no_label => true %>

    -
  • - <% end %>
  • @@ -89,11 +82,6 @@ <%=l(:text_length_between,:min=>1,:max=>254)< <% end %>
  • -
  • - - <%= f.password_field :upassword, :label=> "", :no_label => true%> - <%= l(:label_upassword_info)%> -
  • <%=l(:button_save)%> diff --git a/app/views/repositories/_form.html.erb b/app/views/repositories/_form.html.erb index bc5a060db..8c169d7be 100644 --- a/app/views/repositories/_form.html.erb +++ b/app/views/repositories/_form.html.erb @@ -31,12 +31,7 @@ <%= f.text_field :login, :size => 30 %>

    -

    - <%= f.password_field :password, :size => 30, :name => 'ignore', - :value => ((@repository.new_record? || @repository.password.blank?) ? '' : ('x'*15)), - :onfocus => "this.value=''; this.name='repository[password]';", - :onchange => "this.name='repository[password]';" %> -

    +

    diff --git a/app/views/repositories/_form_create.html.erb b/app/views/repositories/_form_create.html.erb index d8c30143e..11dce9cd4 100644 --- a/app/views/repositories/_form_create.html.erb +++ b/app/views/repositories/_form_create.html.erb @@ -26,7 +26,6 @@ border:none <%= l(:text_scm_command_not_available) %> <% end %>

    -

    <%= f.check_box :is_default, :label => :field_repository_is_default %>

    @@ -36,8 +35,6 @@ border:none <%= l(:text_repository_identifier_info).html_safe %> <% end %>

    -

    <%= f.password_field :upassword, :required =>true, :label=> :field_password %> - <%= l(:label_upassword_info)%>

    <%= submit_tag(@repository.new_record? ? l(:button_create) : l(:button_save)) %> diff --git a/lib/trustie/gitlab/helper.rb b/lib/trustie/gitlab/helper.rb index e69de29bb..5ea4c13e1 100644 --- a/lib/trustie/gitlab/helper.rb +++ b/lib/trustie/gitlab/helper.rb @@ -0,0 +1,37 @@ +#coding=utf-8 + +module Trustie + module Gitlab + module Helper + def change_password(uid, en_pwd, salt) + options = {:encrypted_password=>en_pwd, :password_salt=>salt} + self.g.put("/users/ext/#{uid}", :body => options) + # g.edit_user(uid, :encrypted_password=>en_pwd, :password_salt=>salt) + end + + def add_user(user) + u = nil + begin + u = self.g.get("/users?search=#{user.mail}").first + unless u + u = self.g.create_user(user.mail, + user.hashed_password, + name: user.show_name, + username: user.login, + confirm: "true") + user.gid = u.id + end + change_password(u.id, user.hashed_password, user.salt) + rescue => e + puts e + end + return u + end + + def del_user(user) + ## gitlab unimplement + end + + end + end +end \ No newline at end of file diff --git a/lib/trustie/gitlab/manage_member.rb b/lib/trustie/gitlab/manage_member.rb index 8201d1759..d0f74ad5e 100644 --- a/lib/trustie/gitlab/manage_member.rb +++ b/lib/trustie/gitlab/manage_member.rb @@ -14,27 +14,41 @@ module Trustie end def change_gitlab_member - if self.member.project_id == 2 + if isGitlabProject? @g ||= ::Gitlab.client - @g.edit_team_member(11, self.member.user.gid, self.role.to_gitlab_role ) + @g.edit_team_member(project.gpid, self.member.user.gid, self.role.to_gitlab_role ) end end def add_gitlab_member - if self.member.project_id == 2 + if isGitlabProject? @g ||= ::Gitlab.client - @g.add_team_member(11, self.member.user.gid, self.role.to_gitlab_role ) + @g.add_team_member(project.gpid, self.member.user.gid, self.role.to_gitlab_role ) end end def delete_gitlab_member - if member.roles.count <=1 - if self.member.project_id == 2 - @g ||= ::Gitlab.client - @g.remove_team_member(11, self.member.user.gid) + if isGitlabProject? + if member.roles.count <=1 + @g ||= ::Gitlab.client + @g.remove_team_member(project.gpid, self.member.user.gid) end end end + + private + def project + self.member.project + end + + def repository + project.repository + end + + def isGitlabProject? + repository && repository.gitlab? + end + end end diff --git a/lib/trustie/gitlab/manage_user.rb b/lib/trustie/gitlab/manage_user.rb new file mode 100644 index 000000000..76528739c --- /dev/null +++ b/lib/trustie/gitlab/manage_user.rb @@ -0,0 +1,37 @@ +#coding=utf-8 +# +# +require_relative 'helper' +module Trustie + module Gitlab + module ManageUser + include Helper + + def self.included(base) + base.class_eval { + before_create :add_gitlab_user + before_destroy :delete_gitlab_user + before_save :change_gitlab_user + } + end + + def add_gitlab_user + add_user(self) + end + + def delete_gitlab_user + del_user(self) + end + + def change_gitlab_user + change_password(self.gid, self.hashed_password, self.salt) + end + + private + def g + @g ||= ::Gitlab.client + end + + end + end +end diff --git a/lib/trustie/gitlab/sync.rb b/lib/trustie/gitlab/sync.rb index 597e01a36..d941795ee 100644 --- a/lib/trustie/gitlab/sync.rb +++ b/lib/trustie/gitlab/sync.rb @@ -1,3 +1,7 @@ +#coding=utf-8 + +require_relative 'helper' + module Trustie module Gitlab module UserLevel @@ -10,33 +14,17 @@ module Trustie class Sync attr :g + include Helper def initialize @g = ::Gitlab.client end - def change_password(uid, en_pwd, salt) - options = {:encrypted_password=>en_pwd, :password_salt=>salt} - self.g.put("/users/ext/#{uid}", :body => options) - # g.edit_user(uid, :encrypted_password=>en_pwd, :password_salt=>salt) - end - def sync_user(user) - begin - u = self.g.get("/users?search=#{user.mail}").first - unless u - u = self.g.create_user(user.mail, user.hashed_password, name: user.show_name, username: user.login, confirm: "true") - user.gid = u.id - user.save! - puts "create user #{user.login}" - end - change_password(u.id, user.hashed_password, user.salt) - rescue => e - puts e - end + u = add_user(user) + user.save! if u end - def sync_project(project, opt={}) gid = project.owner.gid raise "unknow gid" unless gid @@ -48,7 +36,6 @@ module Trustie if opt[:password] import_url.sub('@', ":#{opt[:password]}@") end - # import url http://xianbo_trustie2:1234@repository.trustie.net/xianbo/trustie2.git # can use password @@ -80,7 +67,7 @@ module Trustie def remove_project end - end + end end \ No newline at end of file From 7e21c00aed2d82f75be0f53cfe2036c55c4c6996 Mon Sep 17 00:00:00 2001 From: cxt Date: Thu, 22 Oct 2015 09:20:19 +0800 Subject: [PATCH 023/169] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BD=9C=E4=B8=9A?= =?UTF-8?q?=E7=9A=84=E5=9B=9E=E5=A4=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/words_controller.rb | 38 +++++ app/models/homework_common.rb | 13 ++ app/views/users/_course_homework.html.erb | 72 ++++++++- .../users/_user_homework_detail.html.erb | 150 ++++++++++++++++++ app/views/users/_user_homework_list.html.erb | 114 +++++-------- app/views/words/leave_homework_message.js.erb | 7 + config/routes.rb | 1 + 7 files changed, 316 insertions(+), 79 deletions(-) create mode 100644 app/views/users/_user_homework_detail.html.erb create mode 100644 app/views/words/leave_homework_message.js.erb diff --git a/app/controllers/words_controller.rb b/app/controllers/words_controller.rb index 6304055ed..6ad1a66b4 100644 --- a/app/controllers/words_controller.rb +++ b/app/controllers/words_controller.rb @@ -243,7 +243,45 @@ class WordsController < ApplicationController flash[:error] = feedback.errors.full_messages[0] redirect_to course_feedback_url(params[:id]) end + end + + #作业的回复 + def leave_homework_message + if User.current.logged? + @user = User.current + @homework_common = HomeworkCommon.find(params[:id]); + if params[:homework_message].size>0 && User.current.logged? && @user + feedback = HomeworkCommon.add_homework_jour(@user, params[:homework_message], params[:id]) + if (feedback.errors.empty?) + if params[:asset_id] + ids = params[:asset_id].split(',') + update_kindeditor_assets_owner ids,feedback[:id],OwnerTypeHelper::JOURNALSFORMESSAGE + end + course_activity = CourseActivity.where("course_act_type='HomeworkCommon' and course_act_id =#{@homework_common.id}").first + if course_activity + course_activity.updated_at = Time.now + course_activity.save + end + user_activity = UserActivity.where("act_type='HomeworkCommon' and act_id =#{@homework_common.id}").first + if user_activity + user_activity.updated_at = Time.now + user_activity.save + end + respond_to do |format| + format.js{ + @user_activity_id = params[:user_activity_id] + @is_in_course = params[:is_in_course] + @homework_common_id = params[:homework_common_id] + } + end + else + flash[:error] = feedback.errors.full_messages[0] + end + end + else + render_403 + end end def add_brief_introdution diff --git a/app/models/homework_common.rb b/app/models/homework_common.rb index 796080645..03a7644a2 100644 --- a/app/models/homework_common.rb +++ b/app/models/homework_common.rb @@ -12,6 +12,7 @@ class HomeworkCommon < ActiveRecord::Base has_many :homework_tests, :dependent => :destroy has_many :student_works, :dependent => :destroy, :conditions => "is_test=0" has_many :student_works_evaluation_distributions, :through => :student_works #一个作业的分配的匿评列表 + has_many :journals_for_messages, :as => :jour, :dependent => :destroy has_many :acts, :class_name => 'Activity', :as => :act, :dependent => :destroy #用户活动 # 课程动态 has_many :course_acts, :class_name => 'CourseActivity',:as =>:course_act ,:dependent => :destroy @@ -60,6 +61,18 @@ class HomeworkCommon < ActiveRecord::Base self.homework_type == 2 && self.homework_detail_programing end + ###添加回复 + def self.add_homework_jour(user, notes, id , options = {}) + homework = HomeworkCommon.find(id) + if options.count == 0 + jfm = homework.journals_for_messages.build(:user_id => user.id, :notes => notes, :reply_id => 0) + else + jfm = homework.journals_for_messages.build(options) + end + jfm.save + jfm + end + delegate :language_name, :language, :to => :homework_detail_programing end diff --git a/app/views/users/_course_homework.html.erb b/app/views/users/_course_homework.html.erb index 2d3bc68de..df42f6ee9 100644 --- a/app/views/users/_course_homework.html.erb +++ b/app/views/users/_course_homework.html.erb @@ -1,5 +1,5 @@ <% is_teacher = User.current.allowed_to?(:as_teacher,activity.course) %> -

    +
    <%= link_to image_tag(url_to_avatar(activity.user), :width => "50", :height => "50"), user_path(activity.user_id), :alt => "用户头像" %> @@ -71,4 +71,74 @@
    + + <% count=activity.journals_for_messages.count %> +
    +
    +
    +
    + 回复(<%= count %>) +
    +
    + <%if count>3 %> + + <% end %> +
    + + <% replies_all_i = 0 %> + <% if count > 0 %> +
    +
      + <% activity.journals_for_messages.reorder("created_on desc").each do |comment| %> + + <% replies_all_i = replies_all_i + 1 %> +
    • +
      + <%= link_to image_tag(url_to_avatar(comment.user), :width => "33", :height => "33", :class =>"mt8"), user_path(comment.user_id), :alt => "用户头像" %> +
      +
      +
      + <% if comment.try(:user).try(:realname) == ' ' %> + <%= link_to comment.try(:user), user_path(comment.user_id), :class => "newsBlue mr10 f14" %> + <% else %> + <%= link_to comment.try(:user).try(:realname), user_path(comment.user_id), :class => "newsBlue mr10 f14" %> + <% end %> + <%= format_time(comment.created_on) %> +
      +
      + <%= comment.notes.html_safe %>
      +
      +
      +
    • + <% end %> +
    +
    + <% end %> + +
    +
    <%= link_to image_tag(url_to_avatar(User.current), :width => "33", :height => "33"), :alt => "用户头像" %>
    +
    +
    + <%= form_for('new_form',:url => {:controller => 'words', :action => 'leave_homework_message', :id => activity.id},:method => "post", :remote => true) do |f|%> + <%= hidden_field_tag 'user_activity_id',params[:user_activity_id],:value =>user_activity_id %> + +
    + +
    +

    + <% end%> +
    +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/app/views/users/_user_homework_detail.html.erb b/app/views/users/_user_homework_detail.html.erb new file mode 100644 index 000000000..40400c71e --- /dev/null +++ b/app/views/users/_user_homework_detail.html.erb @@ -0,0 +1,150 @@ +<% is_teacher = User.current.allowed_to?(:as_teacher,homework_common.course) %> +
    +
    +
    + <%=link_to image_tag(url_to_avatar(homework_common.user),width:"50px", height: "50px"), user_activities_path(homework_common.user.id)%> +
    +
    +
    + <%= link_to homework_common.user.show_name, user_activities_path(homework_common.user_id), :class => "newsBlue mr15"%> + TO + <%= link_to homework_common.course.name, course_path(homework_common.course_id), :class => "newsBlue ml15"%> +
    + + + <% if homework_common.homework_detail_manual%> + <% if homework_common.homework_detail_manual.comment_status == 1%> + 未开启匿评 + <% elsif homework_common.homework_detail_manual.comment_status == 2%> + 匿评中 + <% elsif homework_common.homework_detail_manual.comment_status == 3%> + 匿评已结束 + <% end%> + <% end%> + +
    +
    + <%= user_for_homework_common homework_common,is_teacher %> +
    + <% if homework_common.homework_type == 2 && is_teacher%> +
    + <%= link_to "模拟答题", new_user_commit_homework_users_path(homework_id: homework_common.id, is_test: true), class: 'c_blue test-program-btn', title: '教师可以通过模拟答题设置作业的标准答案' %> +
    + <% end %> + <% if homework_common.homework_type == 2%> +
    + 语言: + <%= homework_common.language_name%> +
    + <% end %> +
    + <%= l(:label_end_time)%>:<%= homework_common.end_time%> +
    +
    +
    + <%= homework_common.description.html_safe %> +
    +
    + <%= render :partial => 'student_work/work_attachments', :locals => {:attachments => homework_common.attachments} %> +
    +
    + <% if is_teacher%> + <%# if false%> +
    +
      +
    • +
        +
      • + <%= link_to l(:button_edit),edit_homework_common_path(homework_common,:is_in_course => is_in_course), :class => "postOptionLink"%> +
      • +
      • + <%= link_to(l(:label_bid_respond_delete), homework_common_path(homework_common,:is_in_course => is_in_course),:method => 'delete', :confirm => l(:text_are_you_sure), :class => "postOptionLink") %> +
      • +
      • + <%= link_to("匿评设置", start_evaluation_set_homework_common_path(homework_common),:class => "postOptionLink", :remote => true) if homework_common.homework_detail_manual.comment_status == 1%> +
      • +
      • + <%= homework_anonymous_comment homework_common %> +
      • +
      +
    • +
    +
    + <% end%> +
    +
    +
    + + <% count=homework_common.journals_for_messages.count %> +
    +
    +
    +
    + 回复(<%= count %>) +
    +
    + <%if count>3 %> + + <% end %> +
    + + <% replies_all_i = 0 %> + <% if count > 0 %> +
    +
      + <% homework_common.journals_for_messages.reorder("created_on desc").each do |comment| %> + + <% replies_all_i = replies_all_i + 1 %> +
    • +
      + <%= link_to image_tag(url_to_avatar(comment.user), :width => "33", :height => "33", :class =>"mt8"), user_path(comment.user_id), :alt => "用户头像" %> +
      +
      +
      + <% if comment.try(:user).try(:realname) == ' ' %> + <%= link_to comment.try(:user), user_path(comment.user_id), :class => "newsBlue mr10 f14" %> + <% else %> + <%= link_to comment.try(:user).try(:realname), user_path(comment.user_id), :class => "newsBlue mr10 f14" %> + <% end %> + <%= format_time(comment.created_on) %> +
      +
      + <%= comment.notes.html_safe %>
      +
      +
      +
    • + <% end %> +
    +
    + <% end %> + +
    +
    <%= link_to image_tag(url_to_avatar(User.current), :width => "33", :height => "33"), :alt => "用户头像" %>
    +
    +
    + <%= form_for('new_form',:url => {:controller => 'words', :action => 'leave_homework_message', :id => homework_common.id},:method => "post", :remote => true) do |f|%> + <%= hidden_field_tag 'homework_common_id',params[:homework_common_id],:value =>homework_common.id %> + <%= hidden_field_tag 'is_in_course',params[:is_in_course],:value =>is_in_course %> + +
    + +
    +

    + <% end%> +
    +
    +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/app/views/users/_user_homework_list.html.erb b/app/views/users/_user_homework_list.html.erb index 44f992bb4..ad39ad14f 100644 --- a/app/views/users/_user_homework_list.html.erb +++ b/app/views/users/_user_homework_list.html.erb @@ -1,83 +1,41 @@ +<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg',"init_activity_KindEditor" %> + <% homework_commons.each do |homework_common|%> - <% is_teacher = User.current.allowed_to?(:as_teacher,homework_common.course) %> -
    -
    -
    - <%=link_to image_tag(url_to_avatar(homework_common.user),width:"50px", height: "50px"), user_activities_path(homework_common.user.id)%> -
    -
    -
    - <%= link_to homework_common.user.show_name, user_activities_path(homework_common.user_id), :class => "newsBlue mr15"%> - TO - <%= link_to homework_common.course.name, course_path(homework_common.course_id), :class => "newsBlue ml15"%> -
    - + + <%= render :partial => 'users/user_homework_detail', :locals => {:homework_common => homework_common,:is_in_course => is_in_course} %> <% end%> <% if homework_commons.count == 10%> <% if is_in_course == 1%> diff --git a/app/views/words/leave_homework_message.js.erb b/app/views/words/leave_homework_message.js.erb new file mode 100644 index 000000000..91525c889 --- /dev/null +++ b/app/views/words/leave_homework_message.js.erb @@ -0,0 +1,7 @@ +<% if @user_activity_id %> + $("#user_activity_<%= @user_activity_id%>").replaceWith("<%= escape_javascript(render :partial => 'users/course_homework', :locals => {:activity => @homework_common,:user_activity_id =>@user_activity_id}) %>"); + init_activity_KindEditor_data(<%= @user_activity_id%>,"","87%"); +<% elsif @homework_common_id && @is_in_course %> + $("#homework_common_<%= @homework_common_id %>").replaceWith("<%= escape_javascript(render :partial => 'users/user_homework_detail', :locals => {:homework_common => @homework_common,:is_in_course => @is_in_course}) %>"); + init_activity_KindEditor_data(<%= @homework_common_id%>,"","87%"); +<% end %> diff --git a/config/routes.rb b/config/routes.rb index 810c22e7a..d140c240a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -855,6 +855,7 @@ RedmineApp::Application.routes.draw do match 'projects/:id/feedback', :to => 'projects#feedback', :via => :get, :as => 'project_feedback' match 'project/:id/share', :to => 'projects#share', :as => 'share_show' #share post 'words/:id/leave_user_message', :to => 'words#leave_user_message', :as => "leave_user_message" + post 'words/:id/leave_homework_message', :to => 'words#leave_homework_message', :as => "leave_homework_message" post 'join_in/join', :to => 'courses#join', :as => 'join' delete 'join_in/join', :to => 'courses#unjoin' From 9cea468401a43691ccd845ce1884cc8ea87a54d6 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Thu, 22 Oct 2015 09:52:01 +0800 Subject: [PATCH 024/169] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E7=A9=BA=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=97=B6=E5=8F=96trees=E4=BC=9A=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/redmine/scm/adapters/gitlab_adapter.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/redmine/scm/adapters/gitlab_adapter.rb b/lib/redmine/scm/adapters/gitlab_adapter.rb index 404243c89..32429fe35 100644 --- a/lib/redmine/scm/adapters/gitlab_adapter.rb +++ b/lib/redmine/scm/adapters/gitlab_adapter.rb @@ -100,6 +100,8 @@ module Redmine entries.sort_by_name rescue ScmCommandAborted nil + rescue Exception + nil end def lastrev(path, rev) From 1157dcaa06dd0c42a7358d9d23a47dd7c2c5cca2 Mon Sep 17 00:00:00 2001 From: huang Date: Thu, 22 Oct 2015 13:44:35 +0800 Subject: [PATCH 025/169] =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=BA=93summary=20?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=9B=AE=E5=BD=95=20=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=BA=93=E5=9C=B0=E5=9D=80=20=E6=8C=89=E5=88=86?= =?UTF-8?q?=E6=94=AF=E6=98=BE=E7=A4=BA=E6=8F=90=E4=BA=A4=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=20=E7=9B=B8=E5=85=B3=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 10 +- app/helpers/repositories_helper.rb | 5 + app/views/layouts/base_projects.html.erb | 2 +- app/views/repositories/_breadcrumbs.html.erb | 40 +- .../repositories/_dir_list_content.html.erb | 4 +- .../repositories/_link_to_functions.html.erb | 8 +- app/views/repositories/_navigation.html.erb | 30 +- app/views/repositories/_summary.html.erb | 37 + app/views/repositories/show.html.erb | 61 +- config/locales/en.yml | 1 + config/locales/zh.yml | 3 +- db/schema.rb | 1726 +---------------- public/stylesheets/repository.css | 56 + 13 files changed, 152 insertions(+), 1831 deletions(-) create mode 100644 app/views/repositories/_summary.html.erb create mode 100644 public/stylesheets/repository.css diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index e9c6df64e..fa7c27b1b 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -283,7 +283,6 @@ update @entries = @repository.entries(@path, @rev) #@entries = g.trees(project.id, @path) @changeset = @repository.find_changeset_by_name(@rev) - #@project_path_cut = RepositoriesHelper::PROJECT_PATH_CUT #@ip = RepositoriesHelper::REPO_IP_ADDRESS @@ -298,8 +297,13 @@ update @course_tag = params[:course] project_path_cut = RepositoriesHelper::PROJECT_PATH_CUT ip = RepositoriesHelper::REPO_IP_ADDRESS - @repos_url = "http://"+@repository.login.to_s+"_"+@repository.identifier.to_s+"@"+ip.to_s+ - @repository.url.slice(project_path_cut, @repository.url.length).to_s + gitlab_address = RepositoriesHelper::REPO_GITLAB_ADDRESS + if @repository.type.to_s=="Repository::Gitlab" + @repos_url = "http://"+gitlab_address.to_s+"/"+repository_creater(@repository).lastname.to_s+"/"+@repository.identifier+"."+"git" + else + @repos_url = "http://"+@repository.login.to_s+"_"+@repository.identifier.to_s+"@"+ip.to_s+ + @repository.url.slice(project_path_cut, @repository.url.length).to_s + end if @course_tag == 1 render :action => 'show', :layout => 'base_courses' else diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 710b7488f..ba99fe454 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -25,6 +25,7 @@ module RepositoriesHelper end PROJECT_PATH_CUT = 40 REPO_IP_ADDRESS = Setting.host_repository + REPO_GITLAB_ADDRESS = "git.trustie.net" def format_revision(revision) if revision.respond_to? :format_identifier @@ -34,6 +35,10 @@ module RepositoriesHelper end end + def repository_creater rep + repository_creater = User.find_by_login(rep.login) unless rep.login.nil? + end + def truncate_at_line_break(text, length = 255) if text text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...') diff --git a/app/views/layouts/base_projects.html.erb b/app/views/layouts/base_projects.html.erb index 02085b95a..0f622d831 100644 --- a/app/views/layouts/base_projects.html.erb +++ b/app/views/layouts/base_projects.html.erb @@ -12,7 +12,7 @@ <%= favicon %> <%= javascript_heads %> <%= heads_for_theme %> - <%= stylesheet_link_tag 'public', 'pleft', 'project','prettify','jquery/jquery-ui-1.9.2','header' %> + <%= stylesheet_link_tag 'public', 'pleft', 'project','prettify','jquery/jquery-ui-1.9.2','header','repository' %> <%= javascript_include_tag 'cookie','project', 'header','prettify','select_list_move' %> <%= call_hook :view_layouts_base_html_head %> diff --git a/app/views/repositories/_breadcrumbs.html.erb b/app/views/repositories/_breadcrumbs.html.erb index db570d948..5c54fc490 100644 --- a/app/views/repositories/_breadcrumbs.html.erb +++ b/app/views/repositories/_breadcrumbs.html.erb @@ -1,32 +1,12 @@ -<%= link_to @repository.identifier.present? ? h(@repository.identifier) : 'root', - {:action => 'show', :id => @project, - :repository_id => @repository.identifier_param, - :path => nil, :rev => @rev }, - :class=>"fl c_blue f14 fb" %> -<% - dirs = path.split('/') - if 'file' == kind - filename = dirs.pop - end - link_path = '' - dirs.each do |dir| - next if dir.blank? - link_path << '/' unless link_path.empty? - link_path << "#{dir}" -%> - / <%= link_to h(dir), :action => 'show', :id => @project, :repository_id => @repository.identifier_param, - :path => to_path_param(link_path), :rev => @rev %> -<% end %> -<% if filename %> - / <%= link_to h(filename), - :action => 'changes', :id => @project, :repository_id => @repository.identifier_param, - :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %> -<% end %> -<% - # @rev is revsion or Git and Mercurial branch or tag. - # For Mercurial *tip*, @rev and @changeset are nil. - rev_text = @changeset.nil? ? @rev : format_revision(@changeset) -%> -

    <%= "@ #{h rev_text}" unless rev_text.blank? %>

    +
    + <%= link_to @repository.identifier.present? ? h(@repository.identifier) : 'root', + {:action => 'show', :id => @project, + :repository_id => @repository.identifier_param, + :path => nil, :rev => @rev } + %> + / + <%=link_to repository_creater(@repository).show_name, user_path(repository_creater(@repository)) %> + +
    <% html_title(with_leading_slash(path)) -%> diff --git a/app/views/repositories/_dir_list_content.html.erb b/app/views/repositories/_dir_list_content.html.erb index e595a77dd..abee097c5 100644 --- a/app/views/repositories/_dir_list_content.html.erb +++ b/app/views/repositories/_dir_list_content.html.erb @@ -7,6 +7,7 @@ "> <% if entry.is_dir? %> +<%# 展开文件目录 %>   <% end %> <%= link_to h(ent_name), - {:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(ent_path), :rev => @rev}, + {:action => (entry.is_dir? ? 'show' : 'entry'), :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(ent_path), :rev => @rev}, :class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(ent_name)}")%> <%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %> + <% if @repository.report_last_commit %> <%= link_to_revision(entry.changeset, @repository) if entry.changeset %> <%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %> diff --git a/app/views/repositories/_link_to_functions.html.erb b/app/views/repositories/_link_to_functions.html.erb index fc3784d46..aaefd2dbc 100644 --- a/app/views/repositories/_link_to_functions.html.erb +++ b/app/views/repositories/_link_to_functions.html.erb @@ -1,10 +1,10 @@ <% if @entry && @entry.kind == 'file' %>

    -<%= link_to_if action_name != 'changes', l(:label_history), {:action => 'changes', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> | -<% if @repository.supports_cat? %> - <%= link_to_if action_name != 'entry', l(:button_view), {:action => 'entry', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> | -<% end %> +<%= link_to_if action_name != 'changes', l(:label_history), {:action => 'changes', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> +<%# if @repository.supports_cat? %> + <%#= link_to_if action_name != 'entry', l(:button_view), {:action => 'entry', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> | +<%# end %> <% if @repository.supports_annotate? %> <%= link_to_if action_name != 'annotate', l(:button_annotate), {:action => 'annotate', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> | <% end %> diff --git a/app/views/repositories/_navigation.html.erb b/app/views/repositories/_navigation.html.erb index d8ce218eb..024fad34c 100644 --- a/app/views/repositories/_navigation.html.erb +++ b/app/views/repositories/_navigation.html.erb @@ -1,34 +1,30 @@ <% content_for :header_tags do %> <%= javascript_include_tag 'repository_navigation' %> <% end %> - -<%= link_to l(:label_statistics), + +<%#= link_to l(:label_statistics), {:action => 'stats', :id => @project, :repository_id => @repository.identifier_param}, :class => 'mt3 c_blue fl' if @repository.supports_all_revisions? %> <%= form_tag({:action => controller.action_name, - :id => @project, - :repository_id => @repository.identifier_param, - :path => to_path_param(@path), - :rev => nil}, + :id => @project, + :repository_id => @repository.identifier_param, + :path => to_path_param(@path), + :rev => nil}, {:method => :get, :id => 'revision_selector', :class => "fl c_grey02 ml5"}) do -%> <% if !@repository.branches.nil? && @repository.branches.length > 0 -%> - | <%= l(:label_branch) %>: - <%= select_tag :branch, - options_for_select([''] + @repository.branches, @rev), - :id => 'branch' %> + <%= l(:label_branch) %>: + <%= select_tag :branch, options_for_select([''] + @repository.branches, @rev), :id => 'branch' %> <% end -%> <% if !@repository.tags.nil? && @repository.tags.length > 0 -%> | <%= l(:label_tag) %>: - <%= select_tag :tag, - options_for_select([''] + @repository.tags, @rev), - :id => 'tag' %> + <%= select_tag :tag, options_for_select([''] + @repository.tags, @rev), :id => 'tag' %> <% end -%> - <% if @repository.supports_all_revisions? %> - | <%= l(:label_revision) %>: - <%= text_field_tag 'rev', @rev, :size => 8 %> - <% end %> + <%# if @repository.supports_all_revisions? %> + | <%#= l(:label_revision) %>: + <%#= text_field_tag 'rev', @rev, :size => 8 %> + <%# end %> <% end -%> diff --git a/app/views/repositories/_summary.html.erb b/app/views/repositories/_summary.html.erb new file mode 100644 index 000000000..1527abf49 --- /dev/null +++ b/app/views/repositories/_summary.html.erb @@ -0,0 +1,37 @@ +

    + +
    +
    + + + +
    +
    + +
    \ No newline at end of file diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index 28a260178..65609afc5 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -4,76 +4,35 @@
    - <%= render :partial => 'breadcrumbs', - :locals => {:path => @path, :kind => 'dir', :revision => @rev} %> + <%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %> <%= render :partial => 'navigation' %>
    -
    +

    - <% if @repository.type.to_s=="Repository::Git" %> + <% if @repository.type.to_s=="Repository::Gitlab" %> <%= @repos_url %> <% else %> <%= h @repository.url %> <% end %>

    - -

    - (<%= l(:label_all_revisions) %><%= @repositories.sort.collect { |repo| - link_to h(repo.name), - {:controller => 'repositories', :action => 'show', - :id => @project, :repository_id => repo.identifier_param, :rev => nil, :path => nil}, - :class => 'repository' + (repo == @repository ? ' selected' : ''), - :class => "mb10 break_word c_orange" }.join(' | ').html_safe %>) -

    + +<%# 各类信息入口 %> +<% if !@repository.nil? %> + <%= render :partial => 'summary' %> +<% end %> +<%# end %> + <% if !@entries.nil? && authorize_for('repositories', 'browse') %> <%= render :partial => 'dir_list' %> <% end %> <%= render_properties(@properties) %> -<% if authorize_for('repositories', 'revisions') %> - <%# if @changesets && !@changesets.empty? %> -

    - <%= l(:label_latest_revision_plural) %> -

    - <%= render :partial => 'revisions', - :locals => {:project => @project, :path => @path, - :revisions => @changesets, :entry => nil} %> - <%# end %> -

    - <% has_branches = (!@repository.branches.nil? && @repository.branches.length > 0) - sep = '' %> - <% if @repository.supports_all_revisions? && @path.blank? %> - <%= link_to l(:label_view_all_revisions), {:action => 'revisions', :id => @project, - :repository_id => @repository.identifier_param}, - :class => "orange_u_btn" %> - <% sep = '|' %> - <% end %> - <% if @repository.supports_directory_revisions? && (has_branches || !@path.blank? || !@rev.blank?) %> - <%= sep %> - <%= link_to l(:label_view_revisions), - {:action => 'changes', - :path => to_path_param(@path), - :id => @project, - :repository_id => @repository.identifier_param, - :rev => @rev}, - :class => "orange_u_btn" %> - <% end %> -

    - <% if @repository.supports_all_revisions? %> - <% content_for :header_tags do %> - <%= auto_discovery_link_tag( - :atom, params.merge( - {:format => 'atom', :action => 'revisions', - :id => @project, :page => nil, :key => User.current.rss_key})) %> - <% end %> - <% end %> -<% end %>

    点击查看如何提交代码

    diff --git a/config/locales/en.yml b/config/locales/en.yml index 2071529f0..2bed45103 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -481,6 +481,7 @@ en: label_attribute_plural: Attributes label_change_status: Change status label_history: History + label_commit_history: Commit History label_attachment: Files label_attachment_delete: Delete file diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 5efe4a2cc..2b95c917d 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -580,6 +580,7 @@ zh: label_change_status: 变更状态 label_history: 历史记录 + label_commit_history: 历史变更记录 label_attachment: 文件 label_file_upload: 上传资料 @@ -924,7 +925,7 @@ zh: button_change_password: 修改密码 button_copy: 复制 button_copy_and_follow: 复制并转到新问题 - button_annotate: 追溯 + button_annotate: 代码定位 button_configure: 配置 button_quote: 引用 diff --git a/db/schema.rb b/db/schema.rb index de210ce22..053bf1e79 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20151013023237) do +ActiveRecord::Schema.define(:version => 20151020021234) do create_table "activities", :force => true do |t| t.integer "act_id", :null => false @@ -1086,1728 +1086,6 @@ ActiveRecord::Schema.define(:version => 20151013023237) do t.datetime "updated_at", :null => false end - create_table "project_infos", :force => true do |t| - t.integer "project_id" - t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "project_scores", :force => true do |t| - t.string "project_id" - t.integer "score" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "issue_num", :default => 0 - t.integer "issue_journal_num", :default => 0 - t.integer "news_num", :default => 0 - t.integer "documents_num", :default => 0 - t.integer "changeset_num", :default => 0 - t.integer "board_message_num", :default => 0 - end - - create_table "project_statuses", :force => true do |t| - t.integer "changesets_count" - t.integer "watchers_count" - t.integer "project_id" - t.integer "project_type" - t.float "grade", :default => 0.0 - t.integer "course_ac_para", :default => 0 - end - - add_index "project_statuses", ["grade"], :name => "index_project_statuses_on_grade" - - create_table "projecting_softapplictions", :force => true do |t| - t.integer "user_id" - t.integer "softapplication_id" - t.integer "project_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "projects", :force => true do |t| - t.string "name", :default => "", :null => false - t.text "description" - t.string "homepage", :default => "" - t.boolean "is_public", :default => true, :null => false - t.integer "parent_id" - t.datetime "created_on" - t.datetime "updated_on" - t.string "identifier" - t.integer "status", :default => 1, :null => false - t.integer "lft" - t.integer "rgt" - t.boolean "inherit_members", :default => false, :null => false - t.integer "project_type" - t.boolean "hidden_repo", :default => false, :null => false - t.integer "attachmenttype", :default => 1 - t.integer "user_id" - t.integer "dts_test", :default => 0 - t.string "enterprise_name" - t.integer "organization_id" - t.integer "project_new_type" - t.integer "gpid" - end - - add_index "projects", ["lft"], :name => "index_projects_on_lft" - add_index "projects", ["rgt"], :name => "index_projects_on_rgt" - - create_table "projects_trackers", :id => false, :force => true do |t| - t.integer "project_id", :default => 0, :null => false - t.integer "tracker_id", :default => 0, :null => false - end - - add_index "projects_trackers", ["project_id", "tracker_id"], :name => "projects_trackers_unique", :unique => true - add_index "projects_trackers", ["project_id"], :name => "projects_trackers_project_id" - - create_table "queries", :force => true do |t| - t.integer "project_id" - t.string "name", :default => "", :null => false - t.text "filters" - t.integer "user_id", :default => 0, :null => false - t.boolean "is_public", :default => false, :null => false - t.text "column_names" - t.text "sort_criteria" - t.string "group_by" - t.string "type" - end - - add_index "queries", ["project_id"], :name => "index_queries_on_project_id" - add_index "queries", ["user_id"], :name => "index_queries_on_user_id" - - create_table "relative_memo_to_open_source_projects", :force => true do |t| - t.integer "osp_id" - t.integer "relative_memo_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "relative_memos", :force => true do |t| - t.integer "osp_id" - t.integer "parent_id" - t.string "subject", :null => false - t.text "content", :limit => 16777215, :null => false - t.integer "author_id" - t.integer "replies_count", :default => 0 - t.integer "last_reply_id" - t.boolean "lock", :default => false - t.boolean "sticky", :default => false - t.boolean "is_quote", :default => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "viewed_count_crawl", :default => 0 - t.integer "viewed_count_local", :default => 0 - t.string "url" - t.string "username" - t.string "userhomeurl" - t.date "date_collected" - t.string "topic_resource" - end - - create_table "repositories", :force => true do |t| - t.integer "project_id", :default => 0, :null => false - t.string "url", :default => "", :null => false - t.string "login", :limit => 60, :default => "" - t.string "password", :default => "" - t.string "root_url", :default => "" - t.string "type" - t.string "path_encoding", :limit => 64 - t.string "log_encoding", :limit => 64 - t.text "extra_info" - t.string "identifier" - t.boolean "is_default", :default => false - t.boolean "hidden", :default => false - end - - add_index "repositories", ["project_id"], :name => "index_repositories_on_project_id" - - create_table "rich_rich_files", :force => true do |t| - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "rich_file_file_name" - t.string "rich_file_content_type" - t.integer "rich_file_file_size" - t.datetime "rich_file_updated_at" - t.string "owner_type" - t.integer "owner_id" - t.text "uri_cache" - t.string "simplified_type", :default => "file" - end - - create_table "roles", :force => true do |t| - t.string "name", :limit => 30, :default => "", :null => false - t.integer "position", :default => 1 - t.boolean "assignable", :default => true - t.integer "builtin", :default => 0, :null => false - t.text "permissions" - t.string "issues_visibility", :limit => 30, :default => "default", :null => false - end - - create_table "schools", :force => true do |t| - t.string "name" - t.string "province" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "logo_link" - end - - create_table "seems_rateable_cached_ratings", :force => true do |t| - t.integer "cacheable_id", :limit => 8 - t.string "cacheable_type" - t.float "avg", :null => false - t.integer "cnt", :null => false - t.string "dimension" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "seems_rateable_rates", :force => true do |t| - t.integer "rater_id", :limit => 8 - t.integer "rateable_id" - t.string "rateable_type" - t.float "stars", :null => false - t.string "dimension" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "is_teacher_score", :default => 0 - end - - create_table "settings", :force => true do |t| - t.string "name", :default => "", :null => false - t.text "value" - t.datetime "updated_on" - end - - add_index "settings", ["name"], :name => "index_settings_on_name" - - create_table "shares", :force => true do |t| - t.date "created_on" - t.string "url" - t.string "title" - t.integer "share_type" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "project_id" - t.integer "user_id" - t.string "description" - end - - create_table "softapplications", :force => true do |t| - t.string "name" - t.text "description" - t.integer "app_type_id" - t.string "app_type_name" - t.string "android_min_version_available" - t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "contest_id" - t.integer "softapplication_id" - t.integer "is_public" - t.string "application_developers" - t.string "deposit_project_url" - t.string "deposit_project" - t.integer "project_id" - end - - create_table "student_work_tests", :force => true do |t| - t.integer "student_work_id" - t.integer "status" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.text "results" - t.text "src" - end - - create_table "student_works", :force => true do |t| - t.string "name" - t.text "description", :limit => 2147483647 - t.integer "homework_common_id" - t.integer "user_id" - t.float "final_score" - t.float "teacher_score" - t.float "student_score" - t.float "teaching_asistant_score" - t.integer "project_id", :default => 0 - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "late_penalty", :default => 0 - t.integer "absence_penalty", :default => 0 - t.float "system_score", :default => 0.0 - t.boolean "is_test", :default => false - end - - create_table "student_works_evaluation_distributions", :force => true do |t| - t.integer "student_work_id" - t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "student_works_scores", :force => true do |t| - t.integer "student_work_id" - t.integer "user_id" - t.integer "score" - t.text "comment" - t.integer "reviewer_role" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "students_for_courses", :force => true do |t| - t.integer "student_id" - t.integer "course_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - add_index "students_for_courses", ["course_id"], :name => "index_students_for_courses_on_course_id" - add_index "students_for_courses", ["student_id"], :name => "index_students_for_courses_on_student_id" - - create_table "system_messages", :force => true do |t| - t.integer "user_id" - t.string "content" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.text "description" - t.string "subject" - end - - create_table "taggings", :force => true do |t| - t.integer "tag_id" - t.integer "taggable_id" - t.string "taggable_type" - t.integer "tagger_id" - t.string "tagger_type" - t.string "context", :limit => 128 - t.datetime "created_at" - end - - add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id" - add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context" - add_index "taggings", ["taggable_type"], :name => "index_taggings_on_taggable_type" - - create_table "tags", :force => true do |t| - t.string "name" - end - - create_table "teachers", :force => true do |t| - t.string "tea_name" - t.string "location" - t.integer "couurse_time" - t.integer "course_code" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "extra" - end - - create_table "time_entries", :force => true do |t| - t.integer "project_id", :null => false - t.integer "user_id", :null => false - t.integer "issue_id" - t.float "hours", :null => false - t.string "comments" - t.integer "activity_id", :null => false - t.date "spent_on", :null => false - t.integer "tyear", :null => false - t.integer "tmonth", :null => false - t.integer "tweek", :null => false - t.datetime "created_on", :null => false - t.datetime "updated_on", :null => false - end - - add_index "time_entries", ["activity_id"], :name => "index_time_entries_on_activity_id" - add_index "time_entries", ["created_on"], :name => "index_time_entries_on_created_on" - add_index "time_entries", ["issue_id"], :name => "time_entries_issue_id" - add_index "time_entries", ["project_id"], :name => "time_entries_project_id" - add_index "time_entries", ["user_id"], :name => "index_time_entries_on_user_id" - - create_table "tokens", :force => true do |t| - t.integer "user_id", :default => 0, :null => false - t.string "action", :limit => 30, :default => "", :null => false - t.string "value", :limit => 40, :default => "", :null => false - t.datetime "created_on", :null => false - end - - add_index "tokens", ["user_id"], :name => "index_tokens_on_user_id" - add_index "tokens", ["value"], :name => "tokens_value", :unique => true - - create_table "trackers", :force => true do |t| - t.string "name", :limit => 30, :default => "", :null => false - t.boolean "is_in_chlog", :default => false, :null => false - t.integer "position", :default => 1 - t.boolean "is_in_roadmap", :default => true, :null => false - t.integer "fields_bits", :default => 0 - end - - create_table "user_activities", :force => true do |t| - t.string "act_type" - t.integer "act_id" - t.string "container_type" - t.integer "container_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "user_id" - end - - create_table "user_extensions", :force => true do |t| - t.integer "user_id", :null => false - t.date "birthday" - t.string "brief_introduction" - t.integer "gender" - t.string "location" - t.string "occupation" - t.integer "work_experience" - t.integer "zip_code" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "technical_title" - t.integer "identity" - t.string "student_id" - t.string "teacher_realname" - t.string "student_realname" - t.string "location_city" - t.integer "school_id" - t.string "description", :default => "" - end - - create_table "user_feedback_messages", :force => true do |t| - t.integer "user_id" - t.integer "journals_for_message_id" - t.string "journals_for_message_type" - t.integer "viewed" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "user_grades", :force => true do |t| - t.integer "user_id", :null => false - t.integer "project_id", :null => false - t.float "grade", :default => 0.0 - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - add_index "user_grades", ["grade"], :name => "index_user_grades_on_grade" - add_index "user_grades", ["project_id"], :name => "index_user_grades_on_project_id" - add_index "user_grades", ["user_id"], :name => "index_user_grades_on_user_id" - - create_table "user_levels", :force => true do |t| - t.integer "user_id" - t.integer "level" - end - - create_table "user_preferences", :force => true do |t| - t.integer "user_id", :default => 0, :null => false - t.text "others" - t.boolean "hide_mail", :default => false - t.string "time_zone" - end - - add_index "user_preferences", ["user_id"], :name => "index_user_preferences_on_user_id" - - create_table "user_score_details", :force => true do |t| - t.integer "current_user_id" - t.integer "target_user_id" - t.string "score_type" - t.string "score_action" - t.integer "user_id" - t.integer "old_score" - t.integer "new_score" - t.integer "current_user_level" - t.integer "target_user_level" - t.integer "score_changeable_obj_id" - t.string "score_changeable_obj_type" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "user_scores", :force => true do |t| - t.integer "user_id", :null => false - t.integer "collaboration" - t.integer "influence" - t.integer "skill" - t.integer "active" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "user_statuses", :force => true do |t| - t.integer "changesets_count" - t.integer "watchers_count" - t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.float "grade", :default => 0.0 - end - - add_index "user_statuses", ["changesets_count"], :name => "index_user_statuses_on_changesets_count" - add_index "user_statuses", ["grade"], :name => "index_user_statuses_on_grade" - add_index "user_statuses", ["watchers_count"], :name => "index_user_statuses_on_watchers_count" - - create_table "users", :force => true do |t| - t.string "login", :default => "", :null => false - t.string "hashed_password", :limit => 40, :default => "", :null => false - t.string "firstname", :limit => 30, :default => "", :null => false - t.string "lastname", :default => "", :null => false - t.string "mail", :limit => 60, :default => "", :null => false - t.boolean "admin", :default => false, :null => false - t.integer "status", :default => 1, :null => false - t.datetime "last_login_on" - t.string "language", :limit => 5, :default => "" - t.integer "auth_source_id" - t.datetime "created_on" - t.datetime "updated_on" - t.string "type" - t.string "identity_url" - t.string "mail_notification", :default => "", :null => false - t.string "salt", :limit => 64 - t.integer "gid" - end - - add_index "users", ["auth_source_id"], :name => "index_users_on_auth_source_id" - add_index "users", ["id", "type"], :name => "index_users_on_id_and_type" - add_index "users", ["type"], :name => "index_users_on_type" - - create_table "versions", :force => true do |t| - t.integer "project_id", :default => 0, :null => false - t.string "name", :default => "", :null => false - t.string "description", :default => "" - t.date "effective_date" - t.datetime "created_on" - t.datetime "updated_on" - t.string "wiki_page_title" - t.string "status", :default => "open" - t.string "sharing", :default => "none", :null => false - end - - add_index "versions", ["project_id"], :name => "versions_project_id" - add_index "versions", ["sharing"], :name => "index_versions_on_sharing" - - create_table "visitors", :force => true do |t| - t.integer "user_id" - t.integer "master_id" - t.datetime "updated_on" - t.datetime "created_on" - end - - add_index "visitors", ["master_id"], :name => "index_visitors_master_id" - add_index "visitors", ["updated_on"], :name => "index_visitors_updated_on" - add_index "visitors", ["user_id"], :name => "index_visitors_user_id" - - create_table "watchers", :force => true do |t| - t.string "watchable_type", :default => "", :null => false - t.integer "watchable_id", :default => 0, :null => false - t.integer "user_id" - end - - add_index "watchers", ["user_id", "watchable_type"], :name => "watchers_user_id_type" - add_index "watchers", ["user_id"], :name => "index_watchers_on_user_id" - add_index "watchers", ["watchable_id", "watchable_type"], :name => "index_watchers_on_watchable_id_and_watchable_type" - - create_table "web_footer_companies", :force => true do |t| - t.string "name" - t.string "logo_size" - t.string "url" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "web_footer_oranizers", :force => true do |t| - t.string "name" - t.text "description" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "wiki_content_versions", :force => true do |t| - t.integer "wiki_content_id", :null => false - t.integer "page_id", :null => false - t.integer "author_id" - t.binary "data", :limit => 2147483647 - t.string "compression", :limit => 6, :default => "" - t.string "comments", :default => "" - t.datetime "updated_on", :null => false - t.integer "version", :null => false - end - - add_index "wiki_content_versions", ["updated_on"], :name => "index_wiki_content_versions_on_updated_on" - add_index "wiki_content_versions", ["wiki_content_id"], :name => "wiki_content_versions_wcid" - - create_table "wiki_contents", :force => true do |t| - t.integer "page_id", :null => false - t.integer "author_id" - t.text "text", :limit => 2147483647 - t.string "comments", :default => "" - t.datetime "updated_on", :null => false - t.integer "version", :null => false - end - - add_index "wiki_contents", ["author_id"], :name => "index_wiki_contents_on_author_id" - add_index "wiki_contents", ["page_id"], :name => "wiki_contents_page_id" - - create_table "wiki_pages", :force => true do |t| - t.integer "wiki_id", :null => false - t.string "title", :null => false - t.datetime "created_on", :null => false - t.boolean "protected", :default => false, :null => false - t.integer "parent_id" - end - - add_index "wiki_pages", ["parent_id"], :name => "index_wiki_pages_on_parent_id" - add_index "wiki_pages", ["wiki_id", "title"], :name => "wiki_pages_wiki_id_title" - add_index "wiki_pages", ["wiki_id"], :name => "index_wiki_pages_on_wiki_id" - - create_table "wiki_redirects", :force => true do |t| - t.integer "wiki_id", :null => false - t.string "title" - t.string "redirects_to" - t.datetime "created_on", :null => false - end - - add_index "wiki_redirects", ["wiki_id", "title"], :name => "wiki_redirects_wiki_id_title" - add_index "wiki_redirects", ["wiki_id"], :name => "index_wiki_redirects_on_wiki_id" - - create_table "wikis", :force => true do |t| - t.integer "project_id", :null => false - t.string "start_page", :null => false - t.integer "status", :default => 1, :null => false - end - - add_index "wikis", ["project_id"], :name => "wikis_project_id" - - create_table "workflows", :force => true do |t| - t.integer "tracker_id", :default => 0, :null => false - t.integer "old_status_id", :default => 0, :null => false - t.integer "new_status_id", :default => 0, :null => false - t.integer "role_id", :default => 0, :null => false - t.boolean "assignee", :default => false, :null => false - t.boolean "author", :default => false, :null => false - t.string "type", :limit => 30 - t.string "field_name", :limit => 30 - t.string "rule", :limit => 30 - end - - add_index "workflows", ["new_status_id"], :name => "index_workflows_on_new_status_id" - add_index "workflows", ["old_status_id"], :name => "index_workflows_on_old_status_id" - add_index "workflows", ["role_id", "tracker_id", "old_status_id"], :name => "wkfs_role_tracker_old_status" - add_index "workflows", ["role_id"], :name => "index_workflows_on_role_id" - - create_table "works_categories", :force => true do |t| - t.string "category" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "zip_packs", :force => true do |t| - t.integer "user_id" - t.integer "homework_id" - t.string "file_digest" - t.string "file_path" - t.integer "pack_times", :default => 1 - t.integer "pack_size", :default => 0 - t.text "file_digests" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - -end -======= -# encoding: UTF-8 -# This file is auto-generated from the current state of the database. Instead -# of editing this file, please use the migrations feature of Active Record to -# incrementally modify your database, and then regenerate this schema definition. -# -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). -# -# It's strongly recommended to check this file into your version control system. - -ActiveRecord::Schema.define(:version => 20151014023806) do - - create_table "activities", :force => true do |t| - t.integer "act_id", :null => false - t.string "act_type", :null => false - t.integer "user_id", :null => false - t.integer "activity_container_id" - t.string "activity_container_type", :default => "" - t.datetime "created_at" - end - - add_index "activities", ["act_id", "act_type"], :name => "index_activities_on_act_id_and_act_type" - add_index "activities", ["user_id", "act_type"], :name => "index_activities_on_user_id_and_act_type" - add_index "activities", ["user_id"], :name => "index_activities_on_user_id" - - create_table "activity_notifies", :force => true do |t| - t.integer "activity_container_id" - t.string "activity_container_type" - t.integer "activity_id" - t.string "activity_type" - t.integer "notify_to" - t.datetime "created_on" - t.integer "is_read" - end - - add_index "activity_notifies", ["activity_container_id", "activity_container_type"], :name => "index_an_activity_container_id" - add_index "activity_notifies", ["created_on"], :name => "index_an_created_on" - add_index "activity_notifies", ["notify_to"], :name => "index_an_notify_to" - - create_table "api_keys", :force => true do |t| - t.string "access_token" - t.datetime "expires_at" - t.integer "user_id" - t.boolean "active", :default => true - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - add_index "api_keys", ["access_token"], :name => "index_api_keys_on_access_token" - add_index "api_keys", ["user_id"], :name => "index_api_keys_on_user_id" - - create_table "applied_projects", :force => true do |t| - t.integer "project_id", :null => false - t.integer "user_id", :null => false - end - - create_table "apply_project_masters", :force => true do |t| - t.integer "user_id" - t.string "apply_type" - t.integer "apply_id" - t.integer "status" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "attachments", :force => true do |t| - t.integer "container_id" - t.string "container_type", :limit => 30 - t.string "filename", :default => "", :null => false - t.string "disk_filename", :default => "", :null => false - t.integer "filesize", :default => 0, :null => false - t.string "content_type", :default => "" - t.string "digest", :limit => 40, :default => "", :null => false - t.integer "downloads", :default => 0, :null => false - t.integer "author_id", :default => 0, :null => false - t.datetime "created_on" - t.string "description" - t.string "disk_directory" - t.integer "attachtype", :default => 1 - t.integer "is_public", :default => 1 - t.integer "copy_from" - t.integer "quotes" - end - - add_index "attachments", ["author_id"], :name => "index_attachments_on_author_id" - add_index "attachments", ["container_id", "container_type"], :name => "index_attachments_on_container_id_and_container_type" - add_index "attachments", ["created_on"], :name => "index_attachments_on_created_on" - - create_table "attachmentstypes", :force => true do |t| - t.integer "typeId", :null => false - t.string "typeName", :limit => 50 - end - - create_table "auth_sources", :force => true do |t| - t.string "type", :limit => 30, :default => "", :null => false - t.string "name", :limit => 60, :default => "", :null => false - t.string "host", :limit => 60 - t.integer "port" - t.string "account" - t.string "account_password", :default => "" - t.string "base_dn" - t.string "attr_login", :limit => 30 - t.string "attr_firstname", :limit => 30 - t.string "attr_lastname", :limit => 30 - t.string "attr_mail", :limit => 30 - t.boolean "onthefly_register", :default => false, :null => false - t.boolean "tls", :default => false, :null => false - t.string "filter" - t.integer "timeout" - end - - add_index "auth_sources", ["id", "type"], :name => "index_auth_sources_on_id_and_type" - - create_table "biding_projects", :force => true do |t| - t.integer "project_id" - t.integer "bid_id" - t.integer "user_id" - t.string "description" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "reward" - end - - create_table "bids", :force => true do |t| - t.string "name" - t.string "budget", :null => false - t.integer "author_id" - t.date "deadline" - t.text "description" - t.datetime "created_on", :null => false - t.datetime "updated_on", :null => false - t.integer "commit" - t.integer "reward_type" - t.integer "homework_type" - t.integer "parent_id" - t.string "password" - t.integer "is_evaluation" - t.integer "proportion", :default => 60 - t.integer "comment_status", :default => 0 - t.integer "evaluation_num", :default => 3 - t.integer "open_anonymous_evaluation", :default => 1 - end - - create_table "boards", :force => true do |t| - t.integer "project_id", :null => false - t.string "name", :default => "", :null => false - t.string "description" - t.integer "position", :default => 1 - t.integer "topics_count", :default => 0, :null => false - t.integer "messages_count", :default => 0, :null => false - t.integer "last_message_id" - t.integer "parent_id" - t.integer "course_id" - end - - add_index "boards", ["last_message_id"], :name => "index_boards_on_last_message_id" - add_index "boards", ["project_id"], :name => "boards_project_id" - - create_table "bug_to_osps", :force => true do |t| - t.integer "osp_id" - t.integer "relative_memo_id" - t.string "description" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "changes", :force => true do |t| - t.integer "changeset_id", :null => false - t.string "action", :limit => 1, :default => "", :null => false - t.text "path", :null => false - t.text "from_path" - t.string "from_revision" - t.string "revision" - t.string "branch" - end - - add_index "changes", ["changeset_id"], :name => "changesets_changeset_id" - - create_table "changeset_parents", :id => false, :force => true do |t| - t.integer "changeset_id", :null => false - t.integer "parent_id", :null => false - end - - add_index "changeset_parents", ["changeset_id"], :name => "changeset_parents_changeset_ids" - add_index "changeset_parents", ["parent_id"], :name => "changeset_parents_parent_ids" - - create_table "changesets", :force => true do |t| - t.integer "repository_id", :null => false - t.string "revision", :null => false - t.string "committer" - t.datetime "committed_on", :null => false - t.text "comments" - t.date "commit_date" - t.string "scmid" - t.integer "user_id" - end - - add_index "changesets", ["committed_on"], :name => "index_changesets_on_committed_on" - add_index "changesets", ["repository_id", "revision"], :name => "changesets_repos_rev", :unique => true - add_index "changesets", ["repository_id", "scmid"], :name => "changesets_repos_scmid" - add_index "changesets", ["repository_id"], :name => "index_changesets_on_repository_id" - add_index "changesets", ["user_id"], :name => "index_changesets_on_user_id" - - create_table "changesets_issues", :id => false, :force => true do |t| - t.integer "changeset_id", :null => false - t.integer "issue_id", :null => false - end - - add_index "changesets_issues", ["changeset_id", "issue_id"], :name => "changesets_issues_ids", :unique => true - - create_table "code_review_assignments", :force => true do |t| - t.integer "issue_id" - t.integer "change_id" - t.integer "attachment_id" - t.string "file_path" - t.string "rev" - t.string "rev_to" - t.string "action_type" - t.integer "changeset_id" - end - - create_table "code_review_project_settings", :force => true do |t| - t.integer "project_id" - t.integer "tracker_id" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "updated_by" - t.boolean "hide_code_review_tab", :default => false - t.integer "auto_relation", :default => 1 - t.integer "assignment_tracker_id" - t.text "auto_assign" - t.integer "lock_version", :default => 0, :null => false - t.boolean "tracker_in_review_dialog", :default => false - end - - create_table "code_review_user_settings", :force => true do |t| - t.integer "user_id", :default => 0, :null => false - t.integer "mail_notification", :default => 0, :null => false - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "code_reviews", :force => true do |t| - t.integer "project_id" - t.integer "change_id" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "line" - t.integer "updated_by_id" - t.integer "lock_version", :default => 0, :null => false - t.integer "status_changed_from" - t.integer "status_changed_to" - t.integer "issue_id" - t.string "action_type" - t.string "file_path" - t.string "rev" - t.string "rev_to" - t.integer "attachment_id" - t.integer "file_count", :default => 0, :null => false - t.boolean "diff_all" - end - - create_table "comments", :force => true do |t| - t.string "commented_type", :limit => 30, :default => "", :null => false - t.integer "commented_id", :default => 0, :null => false - t.integer "author_id", :default => 0, :null => false - t.text "comments" - t.datetime "created_on", :null => false - t.datetime "updated_on", :null => false - end - - add_index "comments", ["author_id"], :name => "index_comments_on_author_id" - add_index "comments", ["commented_id", "commented_type"], :name => "index_comments_on_commented_id_and_commented_type" - - create_table "contest_notifications", :force => true do |t| - t.text "title" - t.text "content" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "contesting_projects", :force => true do |t| - t.integer "project_id" - t.string "contest_id" - t.integer "user_id" - t.string "description" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "reward" - end - - create_table "contesting_softapplications", :force => true do |t| - t.integer "softapplication_id" - t.integer "contest_id" - t.integer "user_id" - t.string "description" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "reward" - end - - create_table "contestnotifications", :force => true do |t| - t.integer "contest_id" - t.string "title" - t.string "summary" - t.text "description" - t.integer "author_id" - t.integer "notificationcomments_count" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "contests", :force => true do |t| - t.string "name" - t.string "budget", :default => "" - t.integer "author_id" - t.date "deadline" - t.string "description" - t.integer "commit" - t.string "password" - t.datetime "created_on", :null => false - t.datetime "updated_on", :null => false - end - - create_table "course_activities", :force => true do |t| - t.integer "user_id" - t.integer "course_id" - t.integer "course_act_id" - t.string "course_act_type" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "course_attachments", :force => true do |t| - t.string "filename" - t.string "disk_filename" - t.integer "filesize" - t.string "content_type" - t.string "digest" - t.integer "downloads" - t.string "author_id" - t.string "integer" - t.string "description" - t.string "disk_directory" - t.integer "attachtype" - t.integer "is_public" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "container_id", :default => 0 - end - - create_table "course_groups", :force => true do |t| - t.string "name" - t.integer "course_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "course_infos", :force => true do |t| - t.integer "course_id" - t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "course_messages", :force => true do |t| - t.integer "user_id" - t.integer "course_id" - t.integer "course_message_id" - t.string "course_message_type" - t.integer "viewed" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "content" - t.integer "status" - end - - create_table "course_statuses", :force => true do |t| - t.integer "changesets_count" - t.integer "watchers_count" - t.integer "course_id" - t.float "grade", :default => 0.0 - t.integer "course_ac_para", :default => 0 - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "courses", :force => true do |t| - t.integer "tea_id" - t.string "name" - t.integer "state" - t.string "code" - t.integer "time" - t.string "extra" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "location" - t.string "term" - t.string "string" - t.string "password" - t.string "setup_time" - t.string "endup_time" - t.string "class_period" - t.integer "school_id" - t.text "description" - t.integer "status", :default => 1 - t.integer "attachmenttype", :default => 2 - t.integer "lft" - t.integer "rgt" - t.integer "is_public", :limit => 1, :default => 1 - t.integer "inherit_members", :limit => 1, :default => 1 - t.integer "open_student", :default => 0 - end - - create_table "custom_fields", :force => true do |t| - t.string "type", :limit => 30, :default => "", :null => false - t.string "name", :limit => 30, :default => "", :null => false - t.string "field_format", :limit => 30, :default => "", :null => false - t.text "possible_values" - t.string "regexp", :default => "" - t.integer "min_length", :default => 0, :null => false - t.integer "max_length", :default => 0, :null => false - t.boolean "is_required", :default => false, :null => false - t.boolean "is_for_all", :default => false, :null => false - t.boolean "is_filter", :default => false, :null => false - t.integer "position", :default => 1 - t.boolean "searchable", :default => false - t.text "default_value" - t.boolean "editable", :default => true - t.boolean "visible", :default => true, :null => false - t.boolean "multiple", :default => false - end - - add_index "custom_fields", ["id", "type"], :name => "index_custom_fields_on_id_and_type" - - create_table "custom_fields_projects", :id => false, :force => true do |t| - t.integer "custom_field_id", :default => 0, :null => false - t.integer "project_id", :default => 0, :null => false - end - - add_index "custom_fields_projects", ["custom_field_id", "project_id"], :name => "index_custom_fields_projects_on_custom_field_id_and_project_id", :unique => true - - create_table "custom_fields_trackers", :id => false, :force => true do |t| - t.integer "custom_field_id", :default => 0, :null => false - t.integer "tracker_id", :default => 0, :null => false - end - - add_index "custom_fields_trackers", ["custom_field_id", "tracker_id"], :name => "index_custom_fields_trackers_on_custom_field_id_and_tracker_id", :unique => true - - create_table "custom_values", :force => true do |t| - t.string "customized_type", :limit => 30, :default => "", :null => false - t.integer "customized_id", :default => 0, :null => false - t.integer "custom_field_id", :default => 0, :null => false - t.text "value" - end - - add_index "custom_values", ["custom_field_id"], :name => "index_custom_values_on_custom_field_id" - add_index "custom_values", ["customized_type", "customized_id"], :name => "custom_values_customized" - - create_table "delayed_jobs", :force => true do |t| - t.integer "priority", :default => 0, :null => false - t.integer "attempts", :default => 0, :null => false - t.text "handler", :null => false - t.text "last_error" - t.datetime "run_at" - t.datetime "locked_at" - t.datetime "failed_at" - t.string "locked_by" - t.string "queue" - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority" - - create_table "discuss_demos", :force => true do |t| - t.string "title" - t.text "body" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "documents", :force => true do |t| - t.integer "project_id", :default => 0, :null => false - t.integer "category_id", :default => 0, :null => false - t.string "title", :limit => 60, :default => "", :null => false - t.text "description" - t.datetime "created_on" - t.integer "user_id", :default => 0 - t.integer "is_public", :default => 1 - end - - add_index "documents", ["category_id"], :name => "index_documents_on_category_id" - add_index "documents", ["created_on"], :name => "index_documents_on_created_on" - add_index "documents", ["project_id"], :name => "documents_project_id" - - create_table "dts", :force => true do |t| - t.string "IPLineCode" - t.string "Description" - t.string "Num" - t.string "Variable" - t.string "TraceInfo" - t.string "Method" - t.string "File" - t.string "IPLine" - t.string "Review" - t.string "Category" - t.string "Defect" - t.string "PreConditions" - t.string "StartLine" - t.integer "project_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "enabled_modules", :force => true do |t| - t.integer "project_id" - t.string "name", :null => false - t.integer "course_id" - end - - add_index "enabled_modules", ["project_id"], :name => "enabled_modules_project_id" - - create_table "enumerations", :force => true do |t| - t.string "name", :limit => 30, :default => "", :null => false - t.integer "position", :default => 1 - t.boolean "is_default", :default => false, :null => false - t.string "type" - t.boolean "active", :default => true, :null => false - t.integer "project_id" - t.integer "parent_id" - t.string "position_name", :limit => 30 - end - - add_index "enumerations", ["id", "type"], :name => "index_enumerations_on_id_and_type" - add_index "enumerations", ["project_id"], :name => "index_enumerations_on_project_id" - - create_table "first_pages", :force => true do |t| - t.string "web_title" - t.string "title" - t.text "description" - t.string "page_type" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "sort_type" - t.integer "image_width", :default => 107 - t.integer "image_height", :default => 63 - t.integer "show_course", :default => 1 - t.integer "show_contest", :default => 1 - end - - create_table "forge_activities", :force => true do |t| - t.integer "user_id" - t.integer "project_id" - t.integer "forge_act_id" - t.string "forge_act_type" - t.integer "org_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - add_index "forge_activities", ["forge_act_id"], :name => "index_forge_activities_on_forge_act_id" - - create_table "forge_messages", :force => true do |t| - t.integer "user_id" - t.integer "project_id" - t.integer "forge_message_id" - t.string "forge_message_type" - t.integer "viewed" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "secret_key" - t.integer "status" - end - - create_table "forums", :force => true do |t| - t.string "name", :null => false - t.text "description" - t.integer "topic_count", :default => 0 - t.integer "memo_count", :default => 0 - t.integer "last_memo_id", :default => 0 - t.integer "creator_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "sticky" - t.integer "locked" - end - - create_table "groups_users", :id => false, :force => true do |t| - t.integer "group_id", :null => false - t.integer "user_id", :null => false - end - - add_index "groups_users", ["group_id", "user_id"], :name => "groups_users_ids", :unique => true - - create_table "homework_attaches", :force => true do |t| - t.integer "bid_id" - t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "reward" - t.string "name" - t.text "description" - t.integer "state" - t.integer "project_id", :default => 0 - t.float "score", :default => 0.0 - t.integer "is_teacher_score", :default => 0 - end - - add_index "homework_attaches", ["bid_id"], :name => "index_homework_attaches_on_bid_id" - - create_table "homework_commons", :force => true do |t| - t.string "name" - t.integer "user_id" - t.text "description" - t.date "publish_time" - t.date "end_time" - t.integer "homework_type", :default => 1 - t.string "late_penalty" - t.integer "course_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "teacher_priority", :default => 1 - end - - create_table "homework_detail_manuals", :force => true do |t| - t.float "ta_proportion" - t.integer "comment_status" - t.date "evaluation_start" - t.date "evaluation_end" - t.integer "evaluation_num" - t.integer "absence_penalty", :default => 1 - t.integer "homework_common_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "homework_detail_programings", :force => true do |t| - t.string "language" - t.text "standard_code", :limit => 2147483647 - t.integer "homework_common_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.float "ta_proportion", :default => 0.1 - t.integer "question_id" - end - - create_table "homework_evaluations", :force => true do |t| - t.string "user_id" - t.string "homework_attach_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "homework_for_courses", :force => true do |t| - t.integer "course_id" - t.integer "bid_id" - end - - add_index "homework_for_courses", ["bid_id"], :name => "index_homework_for_courses_on_bid_id" - add_index "homework_for_courses", ["course_id"], :name => "index_homework_for_courses_on_course_id" - - create_table "homework_tests", :force => true do |t| - t.text "input" - t.text "output" - t.integer "homework_common_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "result" - t.text "error_msg" - end - - create_table "homework_users", :force => true do |t| - t.string "homework_attach_id" - t.string "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "invite_lists", :force => true do |t| - t.integer "project_id" - t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "mail" - end - - create_table "issue_categories", :force => true do |t| - t.integer "project_id", :default => 0, :null => false - t.string "name", :limit => 30, :default => "", :null => false - t.integer "assigned_to_id" - end - - add_index "issue_categories", ["assigned_to_id"], :name => "index_issue_categories_on_assigned_to_id" - add_index "issue_categories", ["project_id"], :name => "issue_categories_project_id" - - create_table "issue_relations", :force => true do |t| - t.integer "issue_from_id", :null => false - t.integer "issue_to_id", :null => false - t.string "relation_type", :default => "", :null => false - t.integer "delay" - end - - add_index "issue_relations", ["issue_from_id", "issue_to_id"], :name => "index_issue_relations_on_issue_from_id_and_issue_to_id", :unique => true - add_index "issue_relations", ["issue_from_id"], :name => "index_issue_relations_on_issue_from_id" - add_index "issue_relations", ["issue_to_id"], :name => "index_issue_relations_on_issue_to_id" - - create_table "issue_statuses", :force => true do |t| - t.string "name", :limit => 30, :default => "", :null => false - t.boolean "is_closed", :default => false, :null => false - t.boolean "is_default", :default => false, :null => false - t.integer "position", :default => 1 - t.integer "default_done_ratio" - end - - add_index "issue_statuses", ["is_closed"], :name => "index_issue_statuses_on_is_closed" - add_index "issue_statuses", ["is_default"], :name => "index_issue_statuses_on_is_default" - add_index "issue_statuses", ["position"], :name => "index_issue_statuses_on_position" - - create_table "issues", :force => true do |t| - t.integer "tracker_id", :null => false - t.integer "project_id", :null => false - t.string "subject", :default => "", :null => false - t.text "description" - t.date "due_date" - t.integer "category_id" - t.integer "status_id", :null => false - t.integer "assigned_to_id" - t.integer "priority_id", :null => false - t.integer "fixed_version_id" - t.integer "author_id", :null => false - t.integer "lock_version", :default => 0, :null => false - t.datetime "created_on" - t.datetime "updated_on" - t.date "start_date" - t.integer "done_ratio", :default => 0, :null => false - t.float "estimated_hours" - t.integer "parent_id" - t.integer "root_id" - t.integer "lft" - t.integer "rgt" - t.boolean "is_private", :default => false, :null => false - t.datetime "closed_on" - t.integer "project_issues_index" - end - - add_index "issues", ["assigned_to_id"], :name => "index_issues_on_assigned_to_id" - add_index "issues", ["author_id"], :name => "index_issues_on_author_id" - add_index "issues", ["category_id"], :name => "index_issues_on_category_id" - add_index "issues", ["created_on"], :name => "index_issues_on_created_on" - add_index "issues", ["fixed_version_id"], :name => "index_issues_on_fixed_version_id" - add_index "issues", ["priority_id"], :name => "index_issues_on_priority_id" - add_index "issues", ["project_id"], :name => "issues_project_id" - add_index "issues", ["root_id", "lft", "rgt"], :name => "index_issues_on_root_id_and_lft_and_rgt" - add_index "issues", ["status_id"], :name => "index_issues_on_status_id" - add_index "issues", ["tracker_id"], :name => "index_issues_on_tracker_id" - - create_table "join_in_competitions", :force => true do |t| - t.integer "user_id" - t.integer "competition_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "join_in_contests", :force => true do |t| - t.integer "user_id" - t.integer "bid_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "journal_details", :force => true do |t| - t.integer "journal_id", :default => 0, :null => false - t.string "property", :limit => 30, :default => "", :null => false - t.string "prop_key", :limit => 30, :default => "", :null => false - t.text "old_value" - t.text "value" - end - - add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id" - - create_table "journal_details_copy", :force => true do |t| - t.integer "journal_id", :default => 0, :null => false - t.string "property", :limit => 30, :default => "", :null => false - t.string "prop_key", :limit => 30, :default => "", :null => false - t.text "old_value" - t.text "value" - end - - add_index "journal_details_copy", ["journal_id"], :name => "journal_details_journal_id" - - create_table "journal_replies", :id => false, :force => true do |t| - t.integer "journal_id" - t.integer "user_id" - t.integer "reply_id" - end - - add_index "journal_replies", ["journal_id"], :name => "index_journal_replies_on_journal_id" - add_index "journal_replies", ["reply_id"], :name => "index_journal_replies_on_reply_id" - add_index "journal_replies", ["user_id"], :name => "index_journal_replies_on_user_id" - - create_table "journals", :force => true do |t| - t.integer "journalized_id", :default => 0, :null => false - t.string "journalized_type", :limit => 30, :default => "", :null => false - t.integer "user_id", :default => 0, :null => false - t.text "notes" - t.datetime "created_on", :null => false - t.boolean "private_notes", :default => false, :null => false - end - - add_index "journals", ["created_on"], :name => "index_journals_on_created_on" - add_index "journals", ["journalized_id", "journalized_type"], :name => "journals_journalized_id" - add_index "journals", ["journalized_id"], :name => "index_journals_on_journalized_id" - add_index "journals", ["user_id"], :name => "index_journals_on_user_id" - - create_table "journals_for_messages", :force => true do |t| - t.integer "jour_id" - t.string "jour_type" - t.integer "user_id" - t.text "notes" - t.integer "status" - t.integer "reply_id" - t.datetime "created_on", :null => false - t.datetime "updated_on", :null => false - t.string "m_parent_id" - t.boolean "is_readed" - t.integer "m_reply_count" - t.integer "m_reply_id" - t.integer "is_comprehensive_evaluation" - end - - create_table "kindeditor_assets", :force => true do |t| - t.string "asset" - t.integer "file_size" - t.string "file_type" - t.integer "owner_id" - t.string "asset_type" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "owner_type", :default => 0 - end - - create_table "member_roles", :force => true do |t| - t.integer "member_id", :null => false - t.integer "role_id", :null => false - t.integer "inherited_from" - end - - add_index "member_roles", ["member_id"], :name => "index_member_roles_on_member_id" - add_index "member_roles", ["role_id"], :name => "index_member_roles_on_role_id" - - create_table "members", :force => true do |t| - t.integer "user_id", :default => 0, :null => false - t.integer "project_id", :default => 0 - t.datetime "created_on" - t.boolean "mail_notification", :default => false, :null => false - t.integer "course_id", :default => -1 - t.integer "course_group_id", :default => 0 - end - - add_index "members", ["project_id"], :name => "index_members_on_project_id" - add_index "members", ["user_id", "project_id", "course_id"], :name => "index_members_on_user_id_and_project_id", :unique => true - add_index "members", ["user_id"], :name => "index_members_on_user_id" - - create_table "memo_messages", :force => true do |t| - t.integer "user_id" - t.integer "forum_id" - t.integer "memo_id" - t.string "memo_type" - t.integer "viewed" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "memos", :force => true do |t| - t.integer "forum_id", :null => false - t.integer "parent_id" - t.string "subject", :null => false - t.text "content", :null => false - t.integer "author_id", :null => false - t.integer "replies_count", :default => 0 - t.integer "last_reply_id" - t.boolean "lock", :default => false - t.boolean "sticky", :default => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "viewed_count", :default => 0 - end - - create_table "message_alls", :force => true do |t| - t.integer "user_id" - t.integer "message_id" - t.string "message_type" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "messages", :force => true do |t| - t.integer "board_id", :null => false - t.integer "parent_id" - t.string "subject", :default => "", :null => false - t.text "content" - t.integer "author_id" - t.integer "replies_count", :default => 0, :null => false - t.integer "last_reply_id" - t.datetime "created_on", :null => false - t.datetime "updated_on", :null => false - t.boolean "locked", :default => false - t.integer "sticky", :default => 0 - t.integer "reply_id" - end - - add_index "messages", ["author_id"], :name => "index_messages_on_author_id" - add_index "messages", ["board_id"], :name => "messages_board_id" - add_index "messages", ["created_on"], :name => "index_messages_on_created_on" - add_index "messages", ["last_reply_id"], :name => "index_messages_on_last_reply_id" - add_index "messages", ["parent_id"], :name => "messages_parent_id" - - create_table "news", :force => true do |t| - t.integer "project_id" - t.string "title", :limit => 60, :default => "", :null => false - t.string "summary", :default => "" - t.text "description" - t.integer "author_id", :default => 0, :null => false - t.datetime "created_on" - t.integer "comments_count", :default => 0, :null => false - t.integer "course_id" - end - - add_index "news", ["author_id"], :name => "index_news_on_author_id" - add_index "news", ["created_on"], :name => "index_news_on_created_on" - add_index "news", ["project_id"], :name => "news_project_id" - - create_table "no_uses", :force => true do |t| - t.integer "user_id", :null => false - t.string "no_use_type" - t.integer "no_use_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "notificationcomments", :force => true do |t| - t.string "notificationcommented_type" - t.integer "notificationcommented_id" - t.integer "author_id" - t.text "notificationcomments" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "onclick_times", :force => true do |t| - t.integer "user_id" - t.datetime "onclick_time" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "open_id_authentication_associations", :force => true do |t| - t.integer "issued" - t.integer "lifetime" - t.string "handle" - t.string "assoc_type" - t.binary "server_url" - t.binary "secret" - end - - create_table "open_id_authentication_nonces", :force => true do |t| - t.integer "timestamp", :null => false - t.string "server_url" - t.string "salt", :null => false - end - - create_table "open_source_projects", :force => true do |t| - t.string "name" - t.text "description" - t.integer "commit_count", :default => 0 - t.integer "code_line", :default => 0 - t.integer "users_count", :default => 0 - t.date "last_commit_time" - t.string "url" - t.date "date_collected" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "option_numbers", :force => true do |t| - t.integer "user_id" - t.integer "memo" - t.integer "messages_for_issues" - t.integer "issues_status" - t.integer "replay_for_message" - t.integer "replay_for_memo" - t.integer "follow" - t.integer "tread" - t.integer "praise_by_one" - t.integer "praise_by_two" - t.integer "praise_by_three" - t.integer "tread_by_one" - t.integer "tread_by_two" - t.integer "tread_by_three" - t.integer "changeset" - t.integer "document" - t.integer "attachment" - t.integer "issue_done_ratio" - t.integer "post_issue" - t.integer "score_type" - t.integer "total_score" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "project_id" - end - - create_table "organizations", :force => true do |t| - t.string "name" - t.string "logo_link" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "phone_app_versions", :force => true do |t| - t.string "version" - t.text "description" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "poll_answers", :force => true do |t| - t.integer "poll_question_id" - t.text "answer_text" - t.integer "answer_position" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "poll_questions", :force => true do |t| - t.string "question_title" - t.integer "question_type" - t.integer "is_necessary" - t.integer "poll_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "question_number" - end - - create_table "poll_users", :force => true do |t| - t.integer "user_id" - t.integer "poll_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "poll_votes", :force => true do |t| - t.integer "user_id" - t.integer "poll_question_id" - t.integer "poll_answer_id" - t.text "vote_text" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "polls", :force => true do |t| - t.string "polls_name" - t.string "polls_type" - t.integer "polls_group_id" - t.integer "polls_status" - t.integer "user_id" - t.datetime "published_at" - t.datetime "closed_at" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.text "polls_description" - t.integer "show_result", :default => 1 - end - - create_table "praise_tread_caches", :force => true do |t| - t.integer "object_id", :null => false - t.string "object_type" - t.integer "praise_num" - t.integer "tread_num" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "praise_treads", :force => true do |t| - t.integer "user_id", :null => false - t.integer "praise_tread_object_id" - t.string "praise_tread_object_type" - t.integer "praise_or_tread" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - create_table "principal_activities", :force => true do |t| t.integer "user_id" t.integer "principal_id" @@ -2877,6 +1155,7 @@ ActiveRecord::Schema.define(:version => 20151014023806) do t.string "enterprise_name" t.integer "organization_id" t.integer "project_new_type" + t.integer "gpid" end add_index "projects", ["lft"], :name => "index_projects_on_lft" @@ -3293,6 +1572,7 @@ ActiveRecord::Schema.define(:version => 20151014023806) do t.string "identity_url" t.string "mail_notification", :default => "", :null => false t.string "salt", :limit => 64 + t.integer "gid" end add_index "users", ["auth_source_id"], :name => "index_users_on_auth_source_id" diff --git a/public/stylesheets/repository.css b/public/stylesheets/repository.css new file mode 100644 index 000000000..432ad1d9a --- /dev/null +++ b/public/stylesheets/repository.css @@ -0,0 +1,56 @@ +.git_usr_title{ + margin: 0px; + overflow: hidden; + font-size: 18px; + font-weight: bold; + color: #444; + text-overflow: ellipsis; + vertical-align: top; + white-space: nowrap; +} +.overall-summary{ + position: relative; + margin-bottom: 10px; + border: 1px solid #DDD; + border-radius: 3px; +} +.overall-summary .overall-summary-bottomless{ + margin-bottom: 0px; + border-bottom: 0px none; + border-radius: 3px 3px 0px 0px; +} +.stats-switcher-viewport{ + height: 38px; + overflow: hidden; +} +.stats-switcher-viewport .stats-switcher-wrapper{ + position: relative; + top: 0px; + transition: top 0.25s ease-in-out 0s; +} +.numbers-summary{ + display: table; + width: 100%; + table-layout: fixed; +} +.numbers-summary li{ + display: table-cell; + padding: 0px; + margin: 0px; + text-align: center; + white-space: nowrap; +} +.numbers-summary .octicon { + color: #999; +} +.text-emphasized { + font-weight: bold; + color: #333; +} +.octicon .octicon-history { + font: 16px/1 octicons; + display: inline-block; + text-decoration: none; + text-rendering: auto; + -moz-user-select: none; +} \ No newline at end of file From c7204576cc5bb69e5d39d0e7d0154165f2708eff Mon Sep 17 00:00:00 2001 From: huang Date: Thu, 22 Oct 2015 14:25:57 +0800 Subject: [PATCH 026/169] =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=BA=93=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E4=BA=BA=E4=B8=BAprojectowner=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E7=89=88=E6=9C=AC=E5=BA=93=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 2 +- app/views/repositories/_breadcrumbs.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 11e949444..96ade096d 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -258,7 +258,7 @@ update ip = RepositoriesHelper::REPO_IP_ADDRESS gitlab_address = RepositoriesHelper::REPO_GITLAB_ADDRESS if @repository.type.to_s=="Repository::Gitlab" - @repos_url = "http://"+gitlab_address.to_s+"/"+repository_creater(@repository).lastname.to_s+"/"+@repository.identifier+"."+"git" + @repos_url = "http://"+gitlab_address.to_s+"/"+@project.owner.to_s+"/"+@repository.identifier+"."+"git" else @repos_url = "http://"+@repository.login.to_s+"_"+@repository.identifier.to_s+"@"+ip.to_s+ @repository.url.slice(project_path_cut, @repository.url.length).to_s diff --git a/app/views/repositories/_breadcrumbs.html.erb b/app/views/repositories/_breadcrumbs.html.erb index 5c54fc490..ce6719d62 100644 --- a/app/views/repositories/_breadcrumbs.html.erb +++ b/app/views/repositories/_breadcrumbs.html.erb @@ -5,7 +5,7 @@ :path => nil, :rev => @rev } %> / - <%=link_to repository_creater(@repository).show_name, user_path(repository_creater(@repository)) %> + <%=link_to @project.owner, user_path(@project.owner) %>
    From 4b8c65d12e84d4ac0fb850e74b6de961614162be Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Thu, 22 Oct 2015 14:31:14 +0800 Subject: [PATCH 027/169] =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/users_controller.rb | 40 +++++++++++++++++++ app/views/users/dealwith_apply_request.js.erb | 11 +++++ 2 files changed, 51 insertions(+) create mode 100644 app/views/users/dealwith_apply_request.js.erb diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 7adba7cbf..7425692c0 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -239,6 +239,46 @@ class UsersController < ApplicationController end end + #处理加入课程成为教辅教师的请求 + #status 1 同意 2 拒绝 + def dealwith_apply_request + @msg = CourseMessage.find(params[:msg_id]) + + case params[:agree] + when 'Y' + apply_user = User.find(@msg.course_message_id) + + if apply_user.member_of_course?(Course.find(@msg.course_id)) + #将角色改为老师或者教辅 + member = Course.find(@msg.course_id).members.where(:user_id=>apply_user.id).all[0] + member.role_ids = [@msg.content] # msg content保存的是申请的职位角色 + #删除为学生的记录 + joined = StudentsForCourse.where('student_id = ? and course_id = ?', member.user_id,@msg.course_id) + joined.each do |join| + join.delete + end + + member.course_group_id = 0 + member.save + CourseMessage.create(:user_id => @msg.course_message_id, :course_id => @msg.course_id, :viewed => false,:content=> @msg.content,:course_message_id=>User.current.id,:content=>@msg.content,:course_message_type=>'CourseRequestDealResult',:status=>1) + @msg.update_attributes(:status=>1,:viewed=>1) + else + members = [] + members << Member.new(:role_ids => [@msg.content.to_i], :user_id => @msg.course_message_id) + Course.find(@msg.course_id).members << members + CourseMessage.create(:user_id => @msg.course_message_id, :course_id => @msg.course_id, :viewed => false,:content=> @msg.content,:course_message_id=>User.current.id,:content=>@msg.content,:course_message_type=>'CourseRequestDealResult',:status=>1) + @msg.update_attributes(:status=>1,:viewed=>1) + end + + when 'N' + CourseMessage.create(:user_id => @msg.course_message_id, :course_id => @msg.course_id, :viewed => false,:content=> @msg.content,:course_message_id=>User.current.id,:content=>@msg.content,:course_message_type=>'CourseRequestDealResult',:status=>2) + @msg.update_attributes(:status=>2,:viewed=>1) + end + respond_to do |format| + format.js + end + end + # added by bai def show_score diff --git a/app/views/users/dealwith_apply_request.js.erb b/app/views/users/dealwith_apply_request.js.erb new file mode 100644 index 000000000..e31c0be85 --- /dev/null +++ b/app/views/users/dealwith_apply_request.js.erb @@ -0,0 +1,11 @@ +$("#deal_info_<%=@msg.id%>").html( +<% if @msg.status == 0 || @msg.status.nil?%> +<%= link_to '同意',dealwith_apply_request_user_path(User.current,:agree=>'Y',:msg_id=>@msg.id),:remote=>'true'%> +'|' +<%= link_to '拒绝',dealwith_apply_request_user_path(User.current,:agree=>'N',:msg_id=>@msg.id),:remote=>'true'%> +<% elsif @msg.status == 1%> + '您已经同意了该申请' +<% elsif @msg.status == 2%> + '您已经拒绝了该申请' +<%end %> +); \ No newline at end of file From 35719f9c28de94dfd6f47d78cf14e36b5162d026 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Thu, 22 Oct 2015 15:41:20 +0800 Subject: [PATCH 028/169] =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/routes.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/routes.rb b/config/routes.rb index d140c240a..649cfbb75 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -387,6 +387,7 @@ RedmineApp::Application.routes.draw do get 'user_resource_type' get 'user_ref_resource_search' post 'import_resources_to_homework' + get 'dealwith_apply_request' get 'store_selected_resource' # end end From f7545e623b332c467b850c253e5512254fdd4eca Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Thu, 22 Oct 2015 15:41:52 +0800 Subject: [PATCH 029/169] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E4=BD=9C=E4=B8=9A?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=88=AA=E6=AD=A2=E6=97=A5=E6=9C=9F=E5=BF=AB?= =?UTF-8?q?=E5=88=B0=E6=97=B6=EF=BC=8C=E5=A4=9A=E5=8F=91=E9=80=81=E4=B8=80?= =?UTF-8?q?=E6=9D=A1=E6=B6=88=E6=81=AF=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/_user_message_course.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/users/_user_message_course.html.erb b/app/views/users/_user_message_course.html.erb index b98e3be53..720e5f9c5 100644 --- a/app/views/users/_user_message_course.html.erb +++ b/app/views/users/_user_message_course.html.erb @@ -37,7 +37,7 @@
  • <%= time_tag(ma.created_at).html_safe %>
  • <% end %> - <% if ma.course_message_type == "HomeworkCommon" %> + <% if ma.course_message_type == "HomeworkCommon" && ma.status.nil?%>
    • <%=link_to image_tag(url_to_avatar(ma.course_message.user), :width => "30", :height => "30"), user_path(ma.course_message.user) %>
    • <%=link_to ma.course_message.user.lastname + ma.course_message.user.firstname + "老师", user_path(ma.course_message.user), :class => "newsBlue homepageNewsPublisher" %> From 4c5843a1694b9d6de483ebecfb72689a14eda011 Mon Sep 17 00:00:00 2001 From: huang Date: Thu, 22 Oct 2015 16:03:22 +0800 Subject: [PATCH 030/169] =?UTF-8?q?1=E3=80=81title=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=202=E3=80=81summary=E8=B0=83=E6=95=B4=203?= =?UTF-8?q?=E3=80=81=E6=9C=AC=E7=89=88=E5=BA=93=E6=98=BE=E7=A4=BA=EF=BC=8C?= =?UTF-8?q?=E5=8F=98=E6=9B=B4=E9=A1=B5=E9=9D=A2=204=E3=80=81=E7=9B=B8?= =?UTF-8?q?=E5=85=B3CSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/repositories/_breadcrumbs.html.erb | 5 +- app/views/repositories/_navigation.html.erb | 42 +++++++-------- app/views/repositories/_summary.html.erb | 6 +-- app/views/repositories/changes.html.erb | 11 ++-- app/views/repositories/show.html.erb | 54 ++++++++++++++++---- public/stylesheets/project.css | 1 + public/stylesheets/public.css | 3 ++ public/stylesheets/repository.css | 30 ++++++++++- 8 files changed, 111 insertions(+), 41 deletions(-) diff --git a/app/views/repositories/_breadcrumbs.html.erb b/app/views/repositories/_breadcrumbs.html.erb index ce6719d62..84111afeb 100644 --- a/app/views/repositories/_breadcrumbs.html.erb +++ b/app/views/repositories/_breadcrumbs.html.erb @@ -2,10 +2,11 @@ <%= link_to @repository.identifier.present? ? h(@repository.identifier) : 'root', {:action => 'show', :id => @project, :repository_id => @repository.identifier_param, - :path => nil, :rev => @rev } + :path => nil, :rev => @rev }, + :class => "repository-title-dec" %> / - <%=link_to @project.owner, user_path(@project.owner) %> + <%=link_to @project.owner, user_path(@project.owner), :class => "repository-title-dec" %>
    diff --git a/app/views/repositories/_navigation.html.erb b/app/views/repositories/_navigation.html.erb index 024fad34c..2fdf00cef 100644 --- a/app/views/repositories/_navigation.html.erb +++ b/app/views/repositories/_navigation.html.erb @@ -5,26 +5,28 @@ <%#= link_to l(:label_statistics), {:action => 'stats', :id => @project, :repository_id => @repository.identifier_param}, :class => 'mt3 c_blue fl' if @repository.supports_all_revisions? %> +
    + <%= form_tag({:action => controller.action_name, + :id => @project, + :repository_id => @repository.identifier_param, + :path => to_path_param(@path), + :rev => nil}, + {:method => :get, :id => 'revision_selector', :class => "fl c_grey02"}) do -%> + + <% if !@repository.branches.nil? && @repository.branches.length > 0 -%> + <%= l(:label_branch) %>: + <%= select_tag :branch, options_for_select([''] + @repository.branches, @rev), :id => 'branch' %> + <% end -%> -<%= form_tag({:action => controller.action_name, - :id => @project, - :repository_id => @repository.identifier_param, - :path => to_path_param(@path), - :rev => nil}, - {:method => :get, :id => 'revision_selector', :class => "fl c_grey02 ml5"}) do -%> - - <% if !@repository.branches.nil? && @repository.branches.length > 0 -%> - <%= l(:label_branch) %>: - <%= select_tag :branch, options_for_select([''] + @repository.branches, @rev), :id => 'branch' %> - <% end -%> + <% if !@repository.tags.nil? && @repository.tags.length > 0 -%> + | <%= l(:label_tag) %>: + <%= select_tag :tag, options_for_select([''] + @repository.tags, @rev), :id => 'tag' %> + <% end -%> - <% if !@repository.tags.nil? && @repository.tags.length > 0 -%> - | <%= l(:label_tag) %>: - <%= select_tag :tag, options_for_select([''] + @repository.tags, @rev), :id => 'tag' %> - <% end -%> + <%# if @repository.supports_all_revisions? %> + <%#= l(:label_revision) %> + <%#= text_field_tag 'rev', @rev, :size => 8 %> + <%# end %> + <% end -%> +
    - <%# if @repository.supports_all_revisions? %> - | <%#= l(:label_revision) %>: - <%#= text_field_tag 'rev', @rev, :size => 8 %> - <%# end %> -<% end -%> diff --git a/app/views/repositories/_summary.html.erb b/app/views/repositories/_summary.html.erb index 1527abf49..3bd86cd45 100644 --- a/app/views/repositories/_summary.html.erb +++ b/app/views/repositories/_summary.html.erb @@ -7,7 +7,7 @@ - <%=link_to @changesets.count, {:action => 'changes', :path => to_path_param(@path), :id => @project, :repository_id => @repository.identifier_param, :rev => @rev}, :class => "num text-emphasized" %> + <%=link_to @changesets.count, {:action => 'changes', :path => to_path_param(@path), :id => @project, :repository_id => @repository.identifier_param, :rev => @rev}, :class => "num text-emphasized c_blue" %> commits @@ -15,7 +15,7 @@
  • - + <%= @repository.branches.count %> branches @@ -24,7 +24,7 @@
  • - <%=link_to @repository.committers.count, committers_repository_path(@repository) %> + <%=link_to @repository.committers.count, committers_repository_path(@repository), :class => "c_blue" %> contributors
  • diff --git a/app/views/repositories/changes.html.erb b/app/views/repositories/changes.html.erb index 302c839ba..d3834bb41 100644 --- a/app/views/repositories/changes.html.erb +++ b/app/views/repositories/changes.html.erb @@ -1,16 +1,19 @@ <%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> -
    - <%= render :partial => 'navigation' %> +
    +

    <%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %>

    -

    <%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %>

    +
    + <%= render :partial => 'navigation' %> +
    +
    <%= render :partial => 'link_to_functions' %> <%= render_properties(@properties) %> -
    +
    <%= render(:partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %>
    diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index 65609afc5..80f9ec979 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -1,22 +1,16 @@ <%= call_hook(:view_repositories_show_contextual, {:repository => @repository, :project => @project}) %>
    -

    版本库

    +

    <%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %>

    -
    - <%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %> - <%= render :partial => 'navigation' %> -
    + <%= render :partial => 'navigation' %>
    -
    -

    - <% if @repository.type.to_s=="Repository::Gitlab" %> + <% if @repository.type.to_s=="Repository::Gitlab" %> <%= @repos_url %> - <% else %> + <% else %> <%= h @repository.url %> - <% end %> -

    + <% end %>
    @@ -32,7 +26,45 @@ <%= render_properties(@properties) %> +<% if authorize_for('repositories', 'revisions') %> + <%# if @changesets && !@changesets.empty? %> +

    + <%= l(:label_latest_revision_plural) %> +

    + <%= render :partial => 'revisions', + :locals => {:project => @project, :path => @path, + :revisions => @changesets, :entry => nil} %> + <%# end %> +

    + <% has_branches = (!@repository.branches.nil? && @repository.branches.length > 0) + sep = '' %> + <% if @repository.supports_all_revisions? && @path.blank? %> + <%= link_to l(:label_view_all_revisions), {:action => 'revisions', :id => @project, + :repository_id => @repository.identifier_param}, + :class => "orange_u_btn" %> + <% sep = '|' %> + <% end %> + <% if @repository.supports_directory_revisions? && (has_branches || !@path.blank? || !@rev.blank?) %> + <%= sep %> + <%= link_to l(:label_view_revisions), + {:action => 'changes', + :path => to_path_param(@path), + :id => @project, + :repository_id => @repository.identifier_param, + :rev => @rev}, + :class => "orange_u_btn" %> + <% end %> +

    + <% if @repository.supports_all_revisions? %> + <% content_for :header_tags do %> + <%= auto_discovery_link_tag( + :atom, params.merge( + {:format => 'atom', :action => 'revisions', + :id => @project, :page => nil, :key => User.current.rss_key})) %> + <% end %> + <% end %> +<% end %>

    点击查看如何提交代码

    diff --git a/public/stylesheets/project.css b/public/stylesheets/project.css index 27bb66bbb..f383835bc 100644 --- a/public/stylesheets/project.css +++ b/public/stylesheets/project.css @@ -13,6 +13,7 @@ a:hover.lg-foot{ color:#787b7e;} /*右侧内容--动态*/ .project_r_h{ width:670px; height:40px; background:#eaeaea; margin-bottom:10px;} .project_h2{ background:#64bdd9; color:#fff; height:33px; width:90px; text-align:center; font-weight:normal; padding-top:7px; font-size:16px;} +.project_h2_repository{ background:#64bdd9; color:#fff; height:33px; width:auto; text-align:center; font-weight:normal; padding-top:7px; font-size:16px;} .project_h22{ background:#64bdd9; color:#fff; height:33px; width:124px; text-align:center; font-weight:normal; padding-top:7px; font-size:16px;} .project_r_box{ border:1px solid #e2e1e1; width:670px; margin-top:10px;} .project_h3 { color:#646464; font-size:14px; padding:0 10px; border-bottom:1px solid #e2e1e1;} diff --git a/public/stylesheets/public.css b/public/stylesheets/public.css index 45580e95e..b254569f0 100644 --- a/public/stylesheets/public.css +++ b/public/stylesheets/public.css @@ -104,6 +104,7 @@ h4{ font-size:14px; color:#3b3b3b;} .mt8{ margin-top:8px;} .mt10{ margin-top:10px !important;} .mt30{ margin-top: 30px;} +.mt40{ margin-top: 40px;} .mt12 { margin-top:12px !important;} .mt15 {margin-top:15px;} .mt19 {margin-top:19px !important;} @@ -115,6 +116,8 @@ h4{ font-size:14px; color:#3b3b3b;} .mb20{ margin-bottom:20px;} .pl15{ padding-left:15px;} .pt5{ padding-top:5px;} +.pt10{ padding-top:10px;} +.pb5{ padding-bottom: 5px;} .w20{ width:20px;} .w40{width: 40px;} .w45{ width: 45px;} diff --git a/public/stylesheets/repository.css b/public/stylesheets/repository.css index 432ad1d9a..1a542d579 100644 --- a/public/stylesheets/repository.css +++ b/public/stylesheets/repository.css @@ -3,10 +3,10 @@ overflow: hidden; font-size: 18px; font-weight: bold; - color: #444; text-overflow: ellipsis; vertical-align: top; white-space: nowrap; + padding: 0px 10px; } .overall-summary{ position: relative; @@ -32,6 +32,7 @@ display: table; width: 100%; table-layout: fixed; + margin-top: 9px; } .numbers-summary li{ display: table-cell; @@ -53,4 +54,31 @@ text-decoration: none; text-rendering: auto; -moz-user-select: none; +} +.select2-container { + margin: 0px; + position: relative; + display: inline-block; + vertical-align: middle; +} + +.select2-container .select2-choice { + display: block; + height: 26px; + padding: 0px 0px 0px 8px; + overflow: hidden; + position: relative; + border: 1px solid #AAA; + white-space: nowrap; + line-height: 26px; + color: #444; + text-decoration: none; + border-radius: 4px; + background-clip: padding-box; + -moz-user-select: none; + background-color: #FFF; + background-image: linear-gradient(to top, #EEE 0%, #FFF 50%); +} +.repository-title-dec{ + color: #fff !important; } \ No newline at end of file From bd793a00fe25a763af238e46206a1ddae0a25f48 Mon Sep 17 00:00:00 2001 From: huang Date: Thu, 22 Oct 2015 16:16:23 +0800 Subject: [PATCH 031/169] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 4 ---- app/views/repositories/committers.html.erb | 6 +++++- config/locales/zh.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 96ade096d..bf3875835 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -187,15 +187,11 @@ update # Build a hash with repository usernames as keys and corresponding user ids as values @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h} flash[:notice] = l(:notice_successful_update) - redirect_to settings_project_url(@project, :tab => 'repositories') - elsif request.get? respond_to do |format| format.html{ render :layout => "base_projects" } end - - end end diff --git a/app/views/repositories/committers.html.erb b/app/views/repositories/committers.html.erb index ccb037e09..93350889d 100644 --- a/app/views/repositories/committers.html.erb +++ b/app/views/repositories/committers.html.erb @@ -1,4 +1,8 @@ -

    <%= l(:label_repository) %>

    +
    +
    +

    <%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %>

    +
    +
    <%= simple_format(l(:text_repository_usernames_mapping)) %> diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 2b95c917d..f53389969 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -988,7 +988,7 @@ zh: text_enumeration_destroy_question: "%{count} 个对象被关联到了这个枚举值。" text_enumeration_category_reassign_to: '将它们关联到新的枚举值:' text_email_delivery_not_configured: "邮件参数尚未配置,因此邮件通知功能已被禁用。\n请在config/configuration.yml中配置您的SMTP服务器信息并重新启动以使其生效。" - text_repository_usernames_mapping: "选择或更新与版本库中的用户名对应的Trustie用户。\n版本库中与Trustie中的同名用户将被自动对应。" + text_repository_usernames_mapping: "选择或更新与版本库中的用户名对应的Trustie用户,版本库中与Trustie中的同名用户将被自动对应。" text_diff_truncated: '... 差别内容超过了可显示的最大行数并已被截断' text_custom_field_possible_values_info: '每项数值一行' text_wiki_page_destroy_question: 此页面有 %{descendants} 个子页面和下级页面。您想进行那种操作? From 13a157939587d8d94729f0ce5a0a6ddfec765fe3 Mon Sep 17 00:00:00 2001 From: huang Date: Thu, 22 Oct 2015 16:44:35 +0800 Subject: [PATCH 032/169] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repositories/_link_to_functions.html.erb | 8 ++-- app/views/repositories/entry.html.erb | 20 +++++----- app/views/repositories/show.html.erb | 39 ------------------- 3 files changed, 15 insertions(+), 52 deletions(-) diff --git a/app/views/repositories/_link_to_functions.html.erb b/app/views/repositories/_link_to_functions.html.erb index aaefd2dbc..fc3784d46 100644 --- a/app/views/repositories/_link_to_functions.html.erb +++ b/app/views/repositories/_link_to_functions.html.erb @@ -1,10 +1,10 @@ <% if @entry && @entry.kind == 'file' %>

    -<%= link_to_if action_name != 'changes', l(:label_history), {:action => 'changes', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> -<%# if @repository.supports_cat? %> - <%#= link_to_if action_name != 'entry', l(:button_view), {:action => 'entry', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> | -<%# end %> +<%= link_to_if action_name != 'changes', l(:label_history), {:action => 'changes', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> | +<% if @repository.supports_cat? %> + <%= link_to_if action_name != 'entry', l(:button_view), {:action => 'entry', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> | +<% end %> <% if @repository.supports_annotate? %> <%= link_to_if action_name != 'annotate', l(:button_annotate), {:action => 'annotate', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> | <% end %> diff --git a/app/views/repositories/entry.html.erb b/app/views/repositories/entry.html.erb index 5aea99dcc..00eb66638 100644 --- a/app/views/repositories/entry.html.erb +++ b/app/views/repositories/entry.html.erb @@ -1,15 +1,17 @@ <%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> +

    +
    + <%= render :partial => 'navigation' %> +
    -
    - <%= render :partial => 'navigation' %> -
    +

    <%= @path %>

    -

    <%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %>

    + <%= render :partial => 'link_to_functions' %> -<%= render :partial => 'link_to_functions' %> + <%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %> -<%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %> + <% content_for :header_tags do %> + <%= stylesheet_link_tag "scm" %> + <% end %> +
    -<% content_for :header_tags do %> -<%= stylesheet_link_tag "scm" %> -<% end %> diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index 80f9ec979..b1f6ba6ce 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -26,45 +26,6 @@ <%= render_properties(@properties) %> -<% if authorize_for('repositories', 'revisions') %> - <%# if @changesets && !@changesets.empty? %> -

    - <%= l(:label_latest_revision_plural) %> -

    - <%= render :partial => 'revisions', - :locals => {:project => @project, :path => @path, - :revisions => @changesets, :entry => nil} %> - <%# end %> - -

    - <% has_branches = (!@repository.branches.nil? && @repository.branches.length > 0) - sep = '' %> - <% if @repository.supports_all_revisions? && @path.blank? %> - <%= link_to l(:label_view_all_revisions), {:action => 'revisions', :id => @project, - :repository_id => @repository.identifier_param}, - :class => "orange_u_btn" %> - <% sep = '|' %> - <% end %> - <% if @repository.supports_directory_revisions? && (has_branches || !@path.blank? || !@rev.blank?) %> - <%= sep %> - <%= link_to l(:label_view_revisions), - {:action => 'changes', - :path => to_path_param(@path), - :id => @project, - :repository_id => @repository.identifier_param, - :rev => @rev}, - :class => "orange_u_btn" %> - <% end %> -

    - <% if @repository.supports_all_revisions? %> - <% content_for :header_tags do %> - <%= auto_discovery_link_tag( - :atom, params.merge( - {:format => 'atom', :action => 'revisions', - :id => @project, :page => nil, :key => User.current.rss_key})) %> - <% end %> - <% end %> -<% end %>

    点击查看如何提交代码

    From 03c2948a7ccfab161cf83ef9e716592db1cf37bf Mon Sep 17 00:00:00 2001 From: huang Date: Thu, 22 Oct 2015 16:53:36 +0800 Subject: [PATCH 033/169] =?UTF-8?q?=E6=9F=A5=E7=9C=8B=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E5=AF=B9=E6=AF=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/repositories/diff.html.erb | 5 ++++- app/views/repositories/entry.html.erb | 2 +- config/locales/zh.yml | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/views/repositories/diff.html.erb b/app/views/repositories/diff.html.erb index a3afebfff..4e40bd5e8 100644 --- a/app/views/repositories/diff.html.erb +++ b/app/views/repositories/diff.html.erb @@ -1,4 +1,7 @@ -

    <%= l(:label_revision) %> <%= @diff_format_revisions %> <%=h @path %>

    +
    +

    <%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %>

    +
    +

    <%= l(:label_revision_path) %> :<%=h @path %>

    <%= form_tag({:action => 'diff', :id => @project, diff --git a/app/views/repositories/entry.html.erb b/app/views/repositories/entry.html.erb index 00eb66638..bb2fa6dae 100644 --- a/app/views/repositories/entry.html.erb +++ b/app/views/repositories/entry.html.erb @@ -4,7 +4,7 @@ <%= render :partial => 'navigation' %>
    -

    <%= @path %>

    +

    <%= l(:label_revision_path) %> :<%= @path %>

    <%= render :partial => 'link_to_functions' %> diff --git a/config/locales/zh.yml b/config/locales/zh.yml index f53389969..a7274c3c2 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -675,6 +675,7 @@ zh: label_branch: 分支 label_tag: 标签 label_revision: 修订 + label_revision_path: 当前路径 label_revision_plural: 修订 lable_revision_code_count: 代码量 label_revision_commit_count: 提交次数 From 4b6f8efc5a1bbf3fdd0673743adf33def8597bbc Mon Sep 17 00:00:00 2001 From: cxt Date: Thu, 22 Oct 2015 16:57:36 +0800 Subject: [PATCH 034/169] =?UTF-8?q?=E4=BD=9C=E5=93=81=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BD=9C=E4=B8=9A=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../student_work/_student_work_list.html.erb | 1 + app/views/student_work/index.html.erb | 76 +++++++++++++++++++ app/views/users/_user_homework_list.html.erb | 18 ++--- public/stylesheets/courses.css | 10 +++ 4 files changed, 96 insertions(+), 9 deletions(-) diff --git a/app/views/student_work/_student_work_list.html.erb b/app/views/student_work/_student_work_list.html.erb index 197fecac9..2d0e9220c 100644 --- a/app/views/student_work/_student_work_list.html.erb +++ b/app/views/student_work/_student_work_list.html.erb @@ -12,6 +12,7 @@
    <%= select_tag(:student_work_in_group,options_for_select(course_group_list(@course),@group), {:class => "classSplit"}) unless course_group_list(@course).empty? %> <% end%> +
    diff --git a/app/views/student_work/index.html.erb b/app/views/student_work/index.html.erb index e28cdb1e2..72ca36ca9 100644 --- a/app/views/student_work/index.html.erb +++ b/app/views/student_work/index.html.erb @@ -28,6 +28,33 @@ $('#ajax-modal').parent().css("top","25%").css("left","35%").css("position","fixed"); } + $(function(){ + $("#homework_info_hidden").click(function(){ + $("#homeworkInformation").hide(); + $("#homework_info_hidden").hide(); + $("#homework_info_show").show(); + }); + $("#homework_info_show").click(function(){ + $("#homework_info_show").hide(); + $("#homeworkInformation").show(); + $("#homework_info_hidden").show(); + }); + + if($("#homework_description").height() > 54) { + $("#homeworkDetailShow").show(); + } + $("#homeworkDetailShow").click(function(){ + $("#homeworkDetail").toggleClass("max_h54"); + $("#homeworkDetailShow").hide(); + $("#homeworkDetailHide").show(); + }); + $("#homeworkDetailHide").click(function(){ + $("#homeworkDetail").toggleClass("max_h54"); + $("#homeworkDetailHide").hide(); + $("#homeworkDetailShow").show(); + }); + }); +
    @@ -91,6 +118,55 @@
    +
    +
    + + <% if @homework.homework_detail_manual%> + <% if @homework.homework_detail_manual.comment_status == 1%> + 未开启匿评 + <% elsif @homework.homework_detail_manual.comment_status == 2%> + 匿评中 + <% elsif @homework.homework_detail_manual.comment_status == 3%> + 匿评已结束 + <% end%> + <% end%> + [ 隐藏作业信息 ] +
    +
    发布者:<%= @homework.user.show_name %>
    +
    +
    <%= @homework.description.html_safe %>
    +
    + + +
    +
    +
    截止时间:<%= @homework.end_time %>
    + <% if @homework.homework_detail_manual%> + <% if @homework.homework_detail_manual.comment_status == 1%> + <% end_time = @homework.end_time.to_time.to_i + 24*60*60 - 1 %> + <% if end_time >= Time.now.to_i %> +
    提交剩余时间: <%= (end_time - Time.now.to_i) / (24*60*60) %> 天 + <%= ((end_time - Time.now.to_i) % (24*60*60)) / (60*60)%> 小时 + <%= (((end_time - Time.now.to_i) % (24*60*60)) % (60*60)) / 60%>
    + <% else %> +
    提交已截止
    + <% end %> + <% elsif @homework.homework_detail_manual.comment_status == 2%> + <% end_time = @homework.homework_detail_manual.evaluation_end.to_time.to_i + 24*60*60 - 1 %> + <% if end_time >= Time.now.to_i %> +
    匿评剩余时间: <%= (end_time - Time.now.to_i) / (24*60*60)%> 天 + <%= ((end_time - Time.now.to_i) % (24*60*60)) / (60*60)%> 小时 + <%= (((end_time - Time.now.to_i) % (24*60*60)) % (60*60)) / 60%>
    + <% else %> +
    匿评已截止
    + <% end %> + <% end%> + <% end%> +
    +
    +
    +
    +
    diff --git a/app/views/users/_user_homework_list.html.erb b/app/views/users/_user_homework_list.html.erb index ad39ad14f..cf511c51f 100644 --- a/app/views/users/_user_homework_list.html.erb +++ b/app/views/users/_user_homework_list.html.erb @@ -1,15 +1,15 @@ <%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg',"init_activity_KindEditor" %> <% homework_commons.each do |homework_common|%> <% if activity && activity.course_act%> diff --git a/app/views/users/_course_homework.html.erb b/app/views/users/_course_homework.html.erb index df42f6ee9..4bde4476d 100644 --- a/app/views/users/_course_homework.html.erb +++ b/app/views/users/_course_homework.html.erb @@ -43,9 +43,15 @@
    截止时间:<%= activity.end_time.to_s %>
    -
    - <%= activity.description.html_safe %> +
    +
    + <%= activity.description.html_safe %> +
    +
    + + +
    <%# if is_teacher%> <% if ma.course_message_type == "StudentWork" && !ma.course_message.homework_common.nil? %>
    +
  •   您迟交了作品!
  • <%= time_tag(ma.created_at).html_safe %>
  • <% end %> diff --git a/public/stylesheets/new_user.css b/public/stylesheets/new_user.css index 872d8df6d..83fe0456f 100644 --- a/public/stylesheets/new_user.css +++ b/public/stylesheets/new_user.css @@ -1296,3 +1296,5 @@ a:hover.link_file_a{ background:url(../images/pic_file.png) 0 -25px no-repeat; c .list_style ol li{list-style-type: decimal;margin-left: 20px;} .list_style ul li{list-style-type: disc;margin-left: 20px;} +.list_style_disc {list-style-type:disc;margin-left:5px;} +.system_message_style li{color:#909090} From 4a188dc81d7660b8881df47e68ca7f089e58ddaa Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Fri, 23 Oct 2015 14:28:24 +0800 Subject: [PATCH 038/169] =?UTF-8?q?=E5=AF=B9=E6=89=80=E6=9C=89=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A=E6=B6=88=E6=81=AF=EF=BC=8C=E7=BB=9F=E4=B8=80=E5=9C=A8?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E5=89=8D=E5=8A=A0=E5=9C=86=E7=82=B9=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E4=B8=94=E8=B0=83=E6=95=B4=E9=A2=9C=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/_user_message_course.html.erb | 170 +++++++++--------- public/images/news_dot.png | Bin 0 -> 976 bytes public/images/news_dot2.png | Bin 0 -> 976 bytes public/stylesheets/new_user.css | 4 +- 4 files changed, 86 insertions(+), 88 deletions(-) create mode 100644 public/images/news_dot.png create mode 100644 public/images/news_dot2.png diff --git a/app/views/users/_user_message_course.html.erb b/app/views/users/_user_message_course.html.erb index 38f895e4c..fb63f4fc0 100644 --- a/app/views/users/_user_message_course.html.erb +++ b/app/views/users/_user_message_course.html.erb @@ -62,27 +62,31 @@ <%= User.current.lastname + User.current.firstname %>老师您好! <%= User.current.eql?(ma.course_message.user)?"您":(ma.course_message.user.show_name + "老师")%>刚刚发布了一个作业:

    -

    课程名称:<%= ma.course_message.course.name %> - (<%= ma.course_message.course.time.to_s + '年'+ ma.course_message.course.term %>)

    -

    作业标题:<%= ma.course_message.name %>

    -

    提交截止:<%= ma.course_message.end_time %>  24点

    -

    匿评开始:<%= ma.course_message.homework_detail_manual.evaluation_start %>  24点

    -

    匿评关闭:<%= ma.course_message.homework_detail_manual.evaluation_end %>  24点

    -

    迟交扣分:<%= ma.course_message.late_penalty %>分

    -

    缺评扣分:<%= ma.course_message.homework_detail_manual.absence_penalty %>分

    +
      +
    • 课程名称:<%= ma.course_message.course.name %> + (<%= ma.course_message.course.time.to_s + '年'+ ma.course_message.course.term %>)
    • +
    • 作业标题:<%= ma.course_message.name %>
    • +
    • 提交截止:<%= ma.course_message.end_time %>  24点
    • +
    • 匿评开始:<%= ma.course_message.homework_detail_manual.evaluation_start %>  24点
    • +
    • 匿评关闭:<%= ma.course_message.homework_detail_manual.evaluation_end %>  24点
    • +
    • 迟交扣分:<%= ma.course_message.late_penalty %>分
    • +
    • 缺评扣分:<%= ma.course_message.homework_detail_manual.absence_penalty %>分
    • +

    您可以修改作业内容、评分规则、匿评过程等,谢谢!

    <% else %>

    <%= User.current.lastname + User.current.firstname %>同学您好!<%= ma.course_message.user.lastname + ma.course_message.user.firstname %>老师刚刚发布了一个作业:

    -

    课程名称:<%= ma.course_message.course.name %> - (<%= ma.course_message.course.time.to_s + '年'+ ma.course_message.course.term %>)

    -

    作业标题:<%= ma.course_message.name %>

    -

    提交截止:<%= ma.course_message.end_time %>  24点

    -

    匿评开始:<%= ma.course_message.homework_detail_manual.evaluation_start %>  24点

    -

    匿评关闭:<%= ma.course_message.homework_detail_manual.evaluation_end %>  24点

    -

    迟交扣分:<%= ma.course_message.late_penalty %>分

    -

    缺评扣分:<%= ma.course_message.homework_detail_manual.absence_penalty %>分

    +
      +
    • 课程名称:<%= ma.course_message.course.name %> + (<%= ma.course_message.course.time.to_s + '年'+ ma.course_message.course.term %>)
    • +
    • 作业标题:<%= ma.course_message.name %>
    • +
    • 提交截止:<%= ma.course_message.end_time %>  24点
    • +
    • 匿评开始:<%= ma.course_message.homework_detail_manual.evaluation_start %>  24点
    • +
    • 匿评关闭:<%= ma.course_message.homework_detail_manual.evaluation_end %>  24点
    • +
    • 迟交扣分:<%= ma.course_message.late_penalty %>分
    • +
    • 缺评扣分:<%= ma.course_message.homework_detail_manual.absence_penalty %>分
    • +

    请抓紧时间提交您的作品,谢谢!

    @@ -109,24 +113,16 @@ <%= User.current.lastname + User.current.firstname %>同学您好! <%= ma.course_message.user.lastname + ma.course_message.user.firstname %>老师发布的作业截止日期快到了:

    -

    课程名称:<%= ma.course_message.course.name %>(<%= ma.course_message.course.time.to_s + '年'+ ma.course_message.course.term %>)

    -

    作业标题:<%= ma.course_message.name %>

    -

    提交截止:<%= ma.course_message.end_time %>  24点

    -

    匿评开始:<%= ma.course_message.homework_detail_manual.evaluation_start %>  24点

    -

    匿评关闭:<%= ma.course_message.homework_detail_manual.evaluation_end %>  24点

    -

    迟交扣分:<%= ma.course_message.late_penalty %>分

    -

    缺评扣分:<%= ma.course_message.homework_detail_manual.absence_penalty %>分

    -

    请抓紧时间提交您的作品,谢谢!

    - <% else %> -

    <%= User.current.lastname + User.current.firstname %>老师您好!<%= ma.course_message.user.lastname + ma.course_message.user.firstname %>老师发布的作业截止日期快到了:

    -

    课程名称:<%= ma.course_message.course.name %>(<%= ma.course_message.course.time.to_s + '年'+ ma.course_message.course.term %>)

    -

    作业标题:<%= ma.course_message.name %>

    -

    提交截止:<%= ma.course_message.end_time %>  24点

    -

    匿评开始:<%= ma.course_message.homework_detail_manual.evaluation_start %>  24点

    -

    匿评关闭:<%= ma.course_message.homework_detail_manual.evaluation_end %>  24点

    -

    迟交扣分:<%= ma.course_message.late_penalty %>分

    -

    缺评扣分:<%= ma.course_message.homework_detail_manual.absence_penalty %>分

    -

    您可以修改作业内容、评分规则、匿评过程等,谢谢!

    +
      +
    • 课程名称:<%= ma.course_message.course.name %>(<%= ma.course_message.course.time.to_s + '年'+ ma.course_message.course.term %>)
    • +
    • 作业标题:<%= ma.course_message.name %>
    • +
    • 提交截止:<%= ma.course_message.end_time %>  24点
    • +
    • 匿评开始:<%= ma.course_message.homework_detail_manual.evaluation_start %>  24点
    • +
    • 匿评关闭:<%= ma.course_message.homework_detail_manual.evaluation_end %>  24点
    • +
    • 迟交扣分:<%= ma.course_message.late_penalty %>分
    • +
    • 缺评扣分:<%= ma.course_message.homework_detail_manual.absence_penalty %>分
    • +

      请抓紧时间提交您的作品,谢谢!

      +
    <% end %>
  •    截止时间快到了!
  • @@ -154,12 +150,12 @@ <%= User.current.lastname + User.current.firstname %><%= User.current.allowed_to?(:as_teacher,ma.course_message.course) ? '老师' : '同学' %>您好! <%= User.current.eql?(ma.course_message.user)?"您":(ma.course_message.user.lastname + ma.course_message.user.firstname+"老师") %>开启了匿评,作业详情如下:

    -

    课程名称:<%= ma.course_message.course.name %>(<%= ma.course_message.course.time.to_s + '年'+ ma.course_message.course.term %>)

    -

    作业标题:<%= ma.course_message.name %>

    -

    缺评扣分:<%= ma.course_message.homework_detail_manual.absence_penalty %>分

    -

    - 匿评截止:<%= ma.course_message.homework_detail_manual.evaluation_end %>  24点 -

    +
      +
    • 课程名称:<%= ma.course_message.course.name %>(<%= ma.course_message.course.time.to_s + '年'+ ma.course_message.course.term %>)
    • +
    • 作业标题:<%= ma.course_message.name %>
    • +
    • 缺评扣分:<%= ma.course_message.homework_detail_manual.absence_penalty %>分
    • +
    • 匿评截止:<%= ma.course_message.homework_detail_manual.evaluation_end %>  24点
    • +
    <% unless User.current.allowed_to?(:as_teacher, ma.course_message.course)%>

    请您尽早完成匿评!如果您在规定时间内未完成匿评,一次将被扣<%= ma.course_message.homework_detail_manual.absence_penalty %>分。

    <% end%> @@ -184,8 +180,10 @@ <%= User.current.lastname + User.current.firstname %><%= User.current.allowed_to?(:as_teacher,ma.course_message.course) ? '老师':'同学'%>您好! <%= User.current.eql?(ma.course_message.user)?"您":(ma.course_message.user.lastname + ma.course_message.user.firstname+"老师") %>关闭了匿评,作业详情如下:

    -

    课程名称:<%= ma.course_message.course.name %>(<%= ma.course_message.course.time.to_s + '年'+ ma.course_message.course.term %>)

    -

    作业标题:<%= ma.course_message.name %>

    +
      +
    • 课程名称:<%= ma.course_message.course.name %>(<%= ma.course_message.course.time.to_s + '年'+ ma.course_message.course.term %>)
    • +
    • 作业标题:<%= ma.course_message.name %>
    • +
  • <%= time_tag(ma.created_at).html_safe %>
  • @@ -210,14 +208,14 @@

    <%= User.current.lastname + User.current.firstname %><%= User.current.allowed_to?(:as_teacher, ma.course_message.course) ? '老师':'同学'%>您好! <%= User.current.eql?(ma.course_message.user) ?"您":(ma.course_message.user.lastname + ma.course_message.user.firstname + "老师") %>启动作业匿评失败! -

    失败原因:提交作品的人数低于2人

    -

    -

    作业详情如下:

    -

    课程名称:<%= ma.course_message.course.name %>(<%= ma.course_message.course.time.to_s + '年' + ma.course_message.course.term %>)

    -

    作业标题:<%= ma.course_message.name %>

    -

    - 提交截止:<%= ma.course_message.end_time%>  24点 +

    +
      +
    • 失败原因:提交作品的人数低于2人
    • +
    • 课程名称:<%= ma.course_message.course.name %>(<%= ma.course_message.course.time.to_s + '年' + ma.course_message.course.term %>)
    • +
    • 作业标题:<%= ma.course_message.name %>
    • +
    • 提交截止:<%= ma.course_message.end_time%>  24点
    • +
  • <%= time_tag(ma.created_at).html_safe %>
  • @@ -304,16 +302,16 @@ <%= User.current.show_name %>同学您好! <%= ma.course_message.reviewer_role == 3? "匿名用户" : (ma.course_message.user.show_name + "老师")%><%= ma.status == 0? "评阅了您的作品":"重新评阅了您的作品"%>。详情如下:

    -

    课程名称:<%= ma.course.name %>(<%= ma.course.time.to_s + '年'+ ma.course.term %>)

    -

    作业标题:<%=ma.course_message.student_work.homework_common.name %>

    - <% content = ma.content.gsub("作业评分:","").split("    评语:")%> -

    - 作品评分:<%= content[0] %>分 -

    - <% if content.size > 1 %> -
    作品评语:
    -
    <%= content[1] %>
    - <% end %> +
      +
    • 课程名称:<%= ma.course.name %>(<%= ma.course.time.to_s + '年'+ ma.course.term %>)
    • +
    • 作业标题:<%=ma.course_message.student_work.homework_common.name %>
    • + <% content = ma.content.gsub("作业评分:","").split("    评语:")%> +
    • 作品评分:<%= content[0] %>分
    • + <% if content.size > 1 %> +
    • 作品评语:
    • +
      <%= content[1] %>
      + <% end %> +

    本次作业将在<%= ma.course_message.student_work.homework_common.homework_detail_manual.evaluation_end %>  24点结束匿评,到时您将可以看到所有其他同学的作品啦!大家可以进一步互相学习。 期待您取得更大的进步!

    @@ -351,7 +349,7 @@
  • <%= link_to ma.course_message.user.lastname + ma.course_message.user.firstname + - "#{ma.course_message.user.allowed_to?(:as_teacher, ma.course)?"同学":"老师"}", + "#{ma.course_message.user.allowed_to?(:as_teacher, ma.course)?"老师":"同学"}", user_path(ma.course_message.user), :class => "newsBlue homepageNewsPublisher" %> ">回复了作品评论:
  • @@ -365,12 +363,12 @@ <%= User.current.show_name %>老师您好! <%= ma.course_message.user.show_name%><%= ma.course_message.user.allowed_to?(:as_teacher, ma.course)?"老师":"学生"%>回复了您的作品评论。详情如下:

    -
    回复内容:
    -
    <%= ma.course_message.notes %>
    -
    您的评论:
    -
    <%= ma.course_message.jour.comment %>
    -

    课程名称:<%= ma.course.name %>(<%= ma.course.time.to_s + '年'+ ma.course.term %>)

    -

    作业标题:<%=ma.course_message.jour.student_work.homework_common.name %>

    +
      +
    • 回复内容:<%= ma.course_message.notes %>
    • +
    • 您的评论:<%= ma.course_message.jour.comment %>
    • +
    • 课程名称:<%= ma.course.name %>(<%= ma.course.time.to_s + '年'+ ma.course.term %>)
    • +
    • 作业标题:<%=ma.course_message.jour.student_work.homework_common.name %>
    • +
  • <%= time_tag(ma.created_at).html_safe %>
  • @@ -395,14 +393,12 @@ <%= User.current.lastname + User.current.firstname %> <%= User.current.allowed_to?(:as_teacher,ma.course_message.homework_common.course) ? '老师':'同学'%>您好!由于迟交作业,您及您的作品都不能参与以下作业的匿评。作业详情如下:

    -
      -
    • - 课程名称:<%= ma.course_message.homework_common.course.name %>(<%= ma.course_message.homework_common.course.time.to_s + '年' + ma.course_message.homework_common.course.term %>) -
    • -
    • 作业标题:<%= ma.course_message.homework_common.name %>
    • -
    • 提交截止:<%= ma.course_message.homework_common.end_time %> 24:00
    • -
    • 提交时间:<%= format_time(ma.course_message.created_at) %>
    • -
    • 迟交扣分:<%= ma.course_message.homework_common.late_penalty %>分
    • +
        +
      • 课程名称:<%= ma.course_message.homework_common.course.name %>(<%= ma.course_message.homework_common.course.time.to_s + '年' + ma.course_message.homework_common.course.term %>)
      • +
      • 作业标题:<%= ma.course_message.homework_common.name %>
      • +
      • 提交截止:<%= ma.course_message.homework_common.end_time %> 24:00
      • +
      • 提交时间:<%= format_time(ma.course_message.created_at) %>
      • +
      • 迟交扣分:<%= ma.course_message.homework_common.late_penalty %>分

      如需获得最终成绩,请您联系主讲老师对您的作品进行单独评分!

      @@ -444,16 +440,28 @@ 系统提示 ">您有了新的课程成员申请: -
    • +
    • <%= link_to User.find(ma.course_message_id).name+"申请成为课程\""+"#{Course.find(ma.course_id).name}"+"\"的"+"#{ma.content == '9' ? "教师" : "教辅"}", user_path(User.find(ma.course_message_id)), :class => "#{ma.viewed==0 ? "newsBlack" : "newsGrey"}", :onmouseover => "message_titile_show($(this),event)", :onmouseout => "message_titile_hide($(this))" %> +
    • + +
    • <% if ma.status == 0 || ma.status.nil?%> <%= link_to '同意',dealwith_apply_request_user_path(User.current,:agree=>'Y',:msg_id=>ma.id),:remote=>'true'%> | - <%= link_to '拒绝',dealwith_apply_request_user_path(User.current,:agree=>'N',:msg_id=>ma.id),:remote=>'true'%> + <%= link_to '拒绝',dealwith_apply_request_user_path(User.current,:agree=>'N',:msg_id=>ma.id),:remote=>'true'%> <% elsif ma.status == 1%> 您已经同意了该申请 <% elsif ma.status == 2%> @@ -461,16 +469,6 @@ <%end %>
    • -
    • <%= time_tag(ma.created_at).html_safe %>
    <% end %> diff --git a/public/images/news_dot.png b/public/images/news_dot.png new file mode 100644 index 0000000000000000000000000000000000000000..7dea0bbe497f2c0f9b39e489ae60c723fe2b48eb GIT binary patch literal 976 zcmaJ=zi-n(7R|b%XopTzev1*#sc1T1VMQNmFV2ER1605b(*cak< zLLHEpVB#-eMEn6TpbiWm76urQkg84$Na?_e#5qk;27+b#e(1gTefNFup4O`OmKLuq zavZl*E@^c(F2vu(_<#IqSYpFfsyFF2>Ck~0V6I@3HU?$a?BY5$?fr)z@CL_SfKH=H zn?^;ph|8NXhL2pIu{myIGxAMq7gNy2UB^@4@a;PY99w}q86#!*D(*R@g8*+IR2$a8 zu9dUl=3TH6$xOh-)C7^c=Y?{lz>%)Z=JB=w!3aWk6?m3Z)2IQJ1Q=xabka&81SE+^ zYZ*yGcYv5eqL5;*ltgQCMv}!W7(b9j3+#?u*NWp zVhvGDGDI@m_ox{qy>NA+pyAL89G^PG1F@pnCVi?vmg(6MTz@L-h2t`@f(eo73y4p} zN16Z)B|0N!TMk zP`4#;Ysc|yG6-*v@P;9my^xxoh0B@(nF8-Pwyfzzy`W|FY)&j9q-VBLlBkQB^)$++ z)ihEkT#ZZ5ncUb4uFoQCIB*_eyBH7`jE*cjvvW~r>W#Vf>|C^&T!AGc z#Et!{(UUFao_IR7En7^Dk3Hu0fZ6)XD|E!JgjUuHjp$qZ*Xe1?YPEhWoUDM({K@KHxPfQB`G@C_CzDnZ+=Hg4IjObG$-pdF^zI!{ z3}qtVKx}}}IdTIzROp$mOxDpcOM@8%4;1<=sJ31QD)J!^nQX@7cpgX+!{jv{Ok%Fzpet1lS<14#JQiW8-kfVKk0ra5%aUN&sIeCs9UqbD ziI$78C7Z?ILzhguPsaLfCi#+F39YK_wZb2rSDdkOGm%j98(%3ty}>4bHMYRX#X9_{H?<@5g_(pWIJnH($Ts0-vd;-PE<6;LS;N$ExL;_O5hz F{1*rxDeM3M literal 0 HcmV?d00001 diff --git a/public/stylesheets/new_user.css b/public/stylesheets/new_user.css index ee3d96b4b..599ea5476 100644 --- a/public/stylesheets/new_user.css +++ b/public/stylesheets/new_user.css @@ -1298,5 +1298,5 @@ a:hover.link_file_a{ background:url(../images/pic_file.png) 0 -25px no-repeat; c .list_style ol li{list-style-type: decimal;margin-left: 20px;} .list_style ul li{list-style-type: disc;margin-left: 20px;} -.list_style_disc {list-style-type:disc;margin-left:5px;} -.system_message_style li{color:#909090} +.ul_grey li {color:#909090; list-style-position:inside; padding-left:1px;list-style-image:url('../images/news_dot2.png')} +.ul_normal_color li {list-style-position:inside; padding-left:1px; list-style-image:url('../images/news_dot.png')} From fde4ce74ba5378eb2d8b8ac5f4210043979a0535 Mon Sep 17 00:00:00 2001 From: cxt Date: Fri, 23 Oct 2015 14:45:56 +0800 Subject: [PATCH 039/169] =?UTF-8?q?=E4=BD=9C=E4=B8=9A=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E5=A2=9E=E5=8A=A0=E8=AF=84=E5=88=86=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/homework_common_controller.rb | 9 +++++++-- app/controllers/student_work_controller.rb | 8 +++++++- app/views/homework_common/score_rule_set.js.erb | 6 ++++++ app/views/student_work/_set_score_rule.html.erb | 3 +++ app/views/student_work/index.html.erb | 2 +- app/views/users/_user_homework_detail.html.erb | 3 +++ config/routes.rb | 1 + 7 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 app/views/homework_common/score_rule_set.js.erb diff --git a/app/controllers/homework_common_controller.rb b/app/controllers/homework_common_controller.rb index 9e3a4b836..7793ef097 100644 --- a/app/controllers/homework_common_controller.rb +++ b/app/controllers/homework_common_controller.rb @@ -6,8 +6,8 @@ class HomeworkCommonController < ApplicationController include StudentWorkHelper before_filter :find_course, :only => [:index,:new,:create] - before_filter :find_homework, :only => [:edit,:update,:alert_anonymous_comment,:start_anonymous_comment,:stop_anonymous_comment,:destroy,:start_evaluation_set,:set_evaluation_attr] - before_filter :teacher_of_course, :only => [:new, :create, :edit, :update, :destroy, :start_anonymous_comment, :stop_anonymous_comment, :alert_anonymous_comment,:start_evaluation_set,:set_evaluation_attr] + before_filter :find_homework, :only => [:edit,:update,:alert_anonymous_comment,:start_anonymous_comment,:stop_anonymous_comment,:destroy,:start_evaluation_set,:set_evaluation_attr,:score_rule_set] + before_filter :teacher_of_course, :only => [:new, :create, :edit, :update, :destroy, :start_anonymous_comment, :stop_anonymous_comment, :alert_anonymous_comment,:start_evaluation_set,:set_evaluation_attr,:score_rule_set] before_filter :member_of_course, :only => [:index] def index @@ -215,6 +215,11 @@ class HomeworkCommonController < ApplicationController end end + #评分设置 + def score_rule_set + + end + private #获取课程 def find_course diff --git a/app/controllers/student_work_controller.rb b/app/controllers/student_work_controller.rb index e5e1bf391..529c5ea72 100644 --- a/app/controllers/student_work_controller.rb +++ b/app/controllers/student_work_controller.rb @@ -457,7 +457,13 @@ class StudentWorkController < ApplicationController end end respond_to do |format| - format.html{redirect_to student_work_index_url(:homework => @homework.id)} + format.html{ + if params[:student_path] + redirect_to student_work_index_url(:homework => @homework.id) + else + redirect_to user_homeworks_user_path(User.current.id) + end + } end end diff --git a/app/views/homework_common/score_rule_set.js.erb b/app/views/homework_common/score_rule_set.js.erb new file mode 100644 index 000000000..5ff42dff7 --- /dev/null +++ b/app/views/homework_common/score_rule_set.js.erb @@ -0,0 +1,6 @@ +$('#ajax-modal').html('<%= escape_javascript(render :partial => 'student_work/set_score_rule',:locals => {:homework => @homework, :student_path => false}) %>'); +showModal('ajax-modal', '350px'); +$('#ajax-modal').siblings().remove(); +$('#ajax-modal').before("" + + ""); +$('#ajax-modal').parent().css("top","25%").css("left","35%").css("position","fixed"); \ No newline at end of file diff --git a/app/views/student_work/_set_score_rule.html.erb b/app/views/student_work/_set_score_rule.html.erb index 7deed7fb8..508b89a1c 100644 --- a/app/views/student_work/_set_score_rule.html.erb +++ b/app/views/student_work/_set_score_rule.html.erb @@ -1,4 +1,7 @@ <%= form_for('new_form',:url => {:controller => 'student_work',:action => 'set_score_rule',:homework => homework.id},:method => "post") do |f|%> + <% if student_path %> + <%=hidden_field_tag 'student_path', params[:student_path], :value => student_path %> + <% end %>
    评分设置
    diff --git a/app/views/student_work/index.html.erb b/app/views/student_work/index.html.erb index 72ca36ca9..0ea5e7055 100644 --- a/app/views/student_work/index.html.erb +++ b/app/views/student_work/index.html.erb @@ -20,7 +20,7 @@ //设置评分规则 function set_score_rule(){ - $('#ajax-modal').html('<%= escape_javascript(render :partial => 'student_work/set_score_rule',:locals => {:homework => @homework}) %>'); + $('#ajax-modal').html('<%= escape_javascript(render :partial => 'student_work/set_score_rule',:locals => {:homework => @homework,:student_path => true}) %>'); showModal('ajax-modal', '350px'); $('#ajax-modal').siblings().remove(); $('#ajax-modal').before("" + diff --git a/app/views/users/_user_homework_detail.html.erb b/app/views/users/_user_homework_detail.html.erb index 848417d3e..b4beb7aa3 100644 --- a/app/views/users/_user_homework_detail.html.erb +++ b/app/views/users/_user_homework_detail.html.erb @@ -68,6 +68,9 @@
  • <%= link_to(l(:label_bid_respond_delete), homework_common_path(homework_common,:is_in_course => is_in_course),:method => 'delete', :confirm => l(:text_are_you_sure), :class => "postOptionLink") %>
  • +
  • + <%= link_to("评分设置", score_rule_set_homework_common_path(homework_common),:class => "postOptionLink", :remote => true) %> +
  • <%= link_to("匿评设置", start_evaluation_set_homework_common_path(homework_common),:class => "postOptionLink", :remote => true) if homework_common.homework_detail_manual.comment_status == 1%>
  • diff --git a/config/routes.rb b/config/routes.rb index 649cfbb75..f7edee954 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -102,6 +102,7 @@ RedmineApp::Application.routes.draw do get 'stop_anonymous_comment' get 'alert_anonymous_comment' get 'start_evaluation_set' + get 'score_rule_set' post 'set_evaluation_attr' end collection do From 1ddb21d0a999545eee59420f43a3f9b078c48134 Mon Sep 17 00:00:00 2001 From: cxt Date: Fri, 23 Oct 2015 14:53:53 +0800 Subject: [PATCH 040/169] =?UTF-8?q?=E5=8A=A8=E6=80=81=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E5=B1=95=E5=BC=80=E6=97=B6=E5=8E=BB=E6=8E=89?= =?UTF-8?q?line-height=EF=BC=8C=E6=94=B6=E8=B5=B7=E6=97=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0line-height?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/courses/_course_activity.html.erb | 2 ++ app/views/users/_user_activities.html.erb | 2 ++ app/views/users/_user_homework_list.html.erb | 2 ++ 3 files changed, 6 insertions(+) diff --git a/app/views/courses/_course_activity.html.erb b/app/views/courses/_course_activity.html.erb index b79964a8d..4a7cb900d 100644 --- a/app/views/courses/_course_activity.html.erb +++ b/app/views/courses/_course_activity.html.erb @@ -76,11 +76,13 @@ } $("#intro_content_show_<%= activity.id %>").click(function(){ $("#activity_description_<%= activity.id %>").toggleClass("maxh360"); + $("#activity_description_<%= activity.id%>").toggleClass("lh18"); $("#intro_content_show_<%= activity.id %>").hide(); $("#intro_content_hide_<%= activity.id %>").show(); }); $("#intro_content_hide_<%= activity.id %>").click(function(){ $("#activity_description_<%= activity.id %>").toggleClass("maxh360"); + $("#activity_description_<%= activity.id%>").toggleClass("lh18"); $("#intro_content_hide_<%= activity.id %>").hide(); $("#intro_content_show_<%= activity.id %>").show(); }); diff --git a/app/views/users/_user_activities.html.erb b/app/views/users/_user_activities.html.erb index a9584c80e..b7981bdbc 100644 --- a/app/views/users/_user_activities.html.erb +++ b/app/views/users/_user_activities.html.erb @@ -44,11 +44,13 @@ } $("#intro_content_show_<%= user_activity.id %>").click(function(){ $("#activity_description_<%= user_activity.id %>").toggleClass("maxh360"); + $("#activity_description_<%= user_activity.id%>").toggleClass("lh18"); $("#intro_content_show_<%= user_activity.id %>").hide(); $("#intro_content_hide_<%= user_activity.id %>").show(); }); $("#intro_content_hide_<%= user_activity.id %>").click(function(){ $("#activity_description_<%= user_activity.id %>").toggleClass("maxh360"); + $("#activity_description_<%= user_activity.id%>").toggleClass("lh18"); $("#intro_content_hide_<%= user_activity.id %>").hide(); $("#intro_content_show_<%= user_activity.id %>").show(); }); diff --git a/app/views/users/_user_homework_list.html.erb b/app/views/users/_user_homework_list.html.erb index ee3e05795..2e530d489 100644 --- a/app/views/users/_user_homework_list.html.erb +++ b/app/views/users/_user_homework_list.html.erb @@ -21,11 +21,13 @@ } $("#intro_content_show_<%= homework_common.id%>").click(function(){ $("#homework_description_<%= homework_common.id%>").toggleClass("maxh360"); + $("#homework_description_<%= homework_common.id%>").toggleClass("lh18"); $("#intro_content_show_<%= homework_common.id%>").hide(); $("#intro_content_hide_<%= homework_common.id%>").show(); }); $("#intro_content_hide_<%= homework_common.id%>").click(function(){ $("#homework_description_<%= homework_common.id%>").toggleClass("maxh360"); + $("#homework_description_<%= homework_common.id%>").toggleClass("lh18"); $("#intro_content_hide_<%= homework_common.id%>").hide(); $("#intro_content_show_<%= homework_common.id%>").show(); }); From 7ab6c4526268d09429a48ae02771811d7fe3d180 Mon Sep 17 00:00:00 2001 From: huang Date: Fri, 23 Oct 2015 15:37:58 +0800 Subject: [PATCH 041/169] =?UTF-8?q?1=E3=80=81=E8=BD=AC=E6=8D=A2=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E6=B7=BB=E5=8A=A0=E6=8F=90=E7=A4=BA=E3=80=82=202?= =?UTF-8?q?=E3=80=81url=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/repositories/show.html.erb | 6 +++--- app/views/repositories/to_gitlab.html.erb | 12 ++++++++++-- config/locales/projects/zh.yml | 6 +++++- public/stylesheets/application.css | 9 +++++++++ public/stylesheets/repository.css | 7 +++++++ 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index b1f6ba6ce..f2716c3ea 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -5,11 +5,11 @@
    <%= render :partial => 'navigation' %>
    -
    +
    <% if @repository.type.to_s=="Repository::Gitlab" %> - <%= @repos_url %> + 版本库地址:<%= @repos_url %> <% else %> - <%= h @repository.url %> + 版本库地址:<%= h @repository.url %> <% end %>
    diff --git a/app/views/repositories/to_gitlab.html.erb b/app/views/repositories/to_gitlab.html.erb index eccf7259d..d06c087ed 100644 --- a/app/views/repositories/to_gitlab.html.erb +++ b/app/views/repositories/to_gitlab.html.erb @@ -1,4 +1,12 @@ +
    +

    + <%= l(:label_repository_migrate_dec) %> +

    <%= form_for(@repository, url: to_gitlab_project_repository_path(@project, @repository)) do |f| %> - -<% end %> \ No newline at end of file + +<% end %> + + <%= l(:label_repository_name_dec) %> + +
    \ No newline at end of file diff --git a/config/locales/projects/zh.yml b/config/locales/projects/zh.yml index dbce93219..7feb6f26a 100644 --- a/config/locales/projects/zh.yml +++ b/config/locales/projects/zh.yml @@ -440,4 +440,8 @@ zh: # field_sharing: 共享 label_title_code_review: 代码评审 - label_home_non_project: 您还没有创建项目,您可以查看系统的其它项目! \ No newline at end of file + label_home_non_project: 您还没有创建项目,您可以查看系统的其它项目! + + # 版本库迁移 + label_repository_migrate_dec: 注意:Trustie版本库近期进行了一次大的改造,历史版本需要转换成新的版本,输入新的版本库名,即可完成转换。 转换过程可能需要等待一段时间。 + label_repository_name_dec: 版本库名仅小写字母(a-z)、数字、破折号(-)和下划线(_)可以使用,长度必须在 1 到 254 个字符之间,一旦保存,标识无法修改。 \ No newline at end of file diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index dbc91353b..d89b572e5 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -17,6 +17,15 @@ li{list-style-type:none;} .cancel_btn {background-color: #c1c1c1; color: #ffffff; padding: 2px 5px; border: none; border-radius: 3px; cursor: pointer;} .cancel_btn:hover {background-color:#656565; } /*huang*/ +.repository-update-dec{ + padding: 10px; +} +.repository-update-dec .c_grey { + color: #999999; +} +.repository-update-dec .c_orange { + color: #ff7143; +} .hwork_input_news{ border:1px solid #64bdd9; height:22px; width:594px; background:#fff; margin-bottom:10px; padding:5px;} .ml55{ margin-left:55px;} diff --git a/public/stylesheets/repository.css b/public/stylesheets/repository.css index 1a542d579..7745af1e5 100644 --- a/public/stylesheets/repository.css +++ b/public/stylesheets/repository.css @@ -81,4 +81,11 @@ } .repository-title-dec{ color: #fff !important; +} +.repository-url{ + color: #2D2D2D; + margin: auto 0px; + text-align: center; + font-size: 14px; + margin-bottom: 10px; } \ No newline at end of file From b063be917b7fbb9d6f9d8c8e7e66fd9a998627d0 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 23 Oct 2015 15:45:48 +0800 Subject: [PATCH 042/169] =?UTF-8?q?=E9=99=84=E4=BB=B6=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E6=9B=B4=E6=94=B9=EF=BC=9B=E5=BC=B9=E7=AA=97=E6=96=87=E5=AD=97?= =?UTF-8?q?=E4=B8=8Apadding=E5=87=8F=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/stylesheets/courses.css | 2 +- public/stylesheets/new_user.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/stylesheets/courses.css b/public/stylesheets/courses.css index 36a01f5d1..41771eb6e 100644 --- a/public/stylesheets/courses.css +++ b/public/stylesheets/courses.css @@ -158,7 +158,7 @@ a.postOptionLink:hover {color:#ffffff; background-color:#269ac9;} .homepagePostReplyContent {font-size:12px; color:#484848; margin-bottom:12px;} .homepagePostProjectState {width:52px; height:20px; line-height:20px; border-radius:1px; background-color:#28be6c; color:#ffffff; text-align:center; vertical-align:middle; font-size:12px; display:inline-block; margin-left:5px;} .homepagePostAssignTo {float:left; font-size:14px; color:#269ac9;} -.homepagePostFileAtt {height:22px; line-height:22px; vertical-align:middle; background:url(../images/homepage_icon.png) -85px -150px no-repeat; padding-left:35px; font-size:14px; margin-right:25px;} +.homepagePostFileAtt {height:22px; line-height:22px; vertical-align:middle; background:url(../images/public_icon.png) -27px -577px no-repeat; padding-left:25px; font-size:14px; margin-right:25px;} .homepagePostImageAtt {height:22px; line-height:22px; vertical-align:middle; background:url(../images/homepage_icon.png) -86px -195px no-repeat; padding-left:35px; font-size:14px; margin-right:25px;} .postAttSize {color:#888888; font-size:12px;} a.postGrey {color:#484848;} diff --git a/public/stylesheets/new_user.css b/public/stylesheets/new_user.css index ee3d96b4b..22be12633 100644 --- a/public/stylesheets/new_user.css +++ b/public/stylesheets/new_user.css @@ -501,7 +501,7 @@ input.sendSourceText:hover {background-color:#297fb8;} .popbox{/* width:300px; *//* height:100px; */position:fixed !important;/* z-index:100; */left:50%;top:50%;margin:-100px 0 0 -150px; /* background:#fff; */ -moz-border-radius:5px; /* -webkit-border-radius:5px; */ /* border-radius:5px; */ /* box-shadow:0px 0px 8px #194a81; */ /* overflow:auto; */} /*上传资源弹窗*/ .resourceUploadPopup {width:400px; height:auto; border:3px solid #269ac9; padding-left:16px; padding-bottom:16px; background-color:#ffffff; position:absolute; top:50%; left:50%; margin-left:-200px; z-index:1000;} -.uploadText {font-size:16px; color:#269ac9; line-height:16px; padding-top:20px; width:140px; display:inline-block;} +.uploadText {font-size:16px; color:#269ac9; line-height:16px; padding-top:15px; width:140px; display:inline-block;} .uploadBoxContainer {height:33px; line-height:33px; margin-top:10px; position:relative;} .uploadBox {width:100px; height:33px; line-height:33px; text-align:center; vertical-align:middle; background-color:#269ac9; border-radius:3px; float:left; margin-right:12px;} .uploadBox:hover {background-color:#297fb8;} @@ -684,7 +684,7 @@ a.postOptionLink:hover {color:#ffffff; background-color:#269ac9;} .homepagePostReplyContent {font-size:12px; color:#484848; margin-bottom:12px;} .homepagePostProjectState {width:52px; height:20px; line-height:20px; border-radius:1px; background-color:#28be6c; color:#ffffff; text-align:center; vertical-align:middle; font-size:12px; display:inline-block; margin-left:5px;} .homepagePostAssignTo {float:left; font-size:14px; color:#269ac9;} -.homepagePostFileAtt {height:22px; line-height:22px; vertical-align:middle; background:url(../images/homepage_icon.png) -85px -150px no-repeat; padding-left:35px; font-size:14px; margin-right:25px;} +.homepagePostFileAtt {height:22px; line-height:22px; vertical-align:middle; background:url(../images/public_icon.png) -27px -577px no-repeat; padding-left:25px; font-size:14px; margin-right:25px;} .homepagePostImageAtt {height:22px; line-height:22px; vertical-align:middle; background:url(../images/homepage_icon.png) -86px -195px no-repeat; padding-left:35px; font-size:14px; margin-right:25px;} .postAttSize {color:#888888; font-size:12px;} a.postGrey {color:#484848;} From b45d140a74c4cd916ef5d9c53608da3c73cc4c51 Mon Sep 17 00:00:00 2001 From: huang Date: Fri, 23 Oct 2015 16:20:32 +0800 Subject: [PATCH 043/169] =?UTF-8?q?=E6=B2=A1=E6=9C=89=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=98=BE=E7=A4=BA=E9=A1=B5=E9=9D=A2=20?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E6=8E=A5=E5=A4=B4top=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/repositories/show.html.erb | 23 ++++++++++++----------- public/stylesheets/repository.css | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index f2716c3ea..bc0ff18be 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -5,32 +5,33 @@
    <%= render :partial => 'navigation' %>
    -
    +
    + <% if @entries.nil? && authorize_for('repositories', 'browse') %> +
    + 该版本库还没有上传代码! +
    + <% end %> <% if @repository.type.to_s=="Repository::Gitlab" %> 版本库地址:<%= @repos_url %> <% else %> 版本库地址:<%= h @repository.url %> <% end %> + + +
    -<%# 各类信息入口 %> -<% if !@repository.nil? %> - <%= render :partial => 'summary' %> -<% end %> -<%# end %> - <% if !@entries.nil? && authorize_for('repositories', 'browse') %> + <%# 数据统计 %> + <%= render :partial => 'summary' %> + <%# end %> <%= render :partial => 'dir_list' %> <% end %> <%= render_properties(@properties) %> - -

    点击查看如何提交代码

    -
    - <% content_for :header_tags do %> <%= stylesheet_link_tag "scm" %> <% end %> diff --git a/public/stylesheets/repository.css b/public/stylesheets/repository.css index 7745af1e5..0fe1541d2 100644 --- a/public/stylesheets/repository.css +++ b/public/stylesheets/repository.css @@ -88,4 +88,19 @@ text-align: center; font-size: 14px; margin-bottom: 10px; + margin-top: 10px; +} +.center{ + text-align: center; +} +.light-well { + background: #F9F9F9 none repeat scroll 0% 0%; + padding: 15px; +} +.page-title { + margin-top: 0px; + line-height: 1.5; + font-weight: bold; + margin-bottom: 5px; + font-size: 18px; } \ No newline at end of file From 4bedc81524efc9d19ce9a3ef4ce55e53517a1477 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 23 Oct 2015 16:26:58 +0800 Subject: [PATCH 044/169] =?UTF-8?q?=E4=BD=9C=E5=93=81=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=8C=89=E9=92=AE=E4=BD=8D=E7=BD=AE=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_student_work_attachment_form.html.erb | 70 +++++++++---------- public/stylesheets/new_user.css | 1 + public/stylesheets/public.css | 1 + 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/app/views/student_work/_student_work_attachment_form.html.erb b/app/views/student_work/_student_work_attachment_form.html.erb index 7802f6eb6..265ff9be7 100644 --- a/app/views/student_work/_student_work_attachment_form.html.erb +++ b/app/views/student_work/_student_work_attachment_form.html.erb @@ -1,35 +1,35 @@ -
    - - -
    - - <%= button_tag "文件浏览", :type=>"button", :onclick=>"$('#_file#{work.id}').click();",:onmouseover => 'this.focus()',:class => 'sub_btn' %> - <%= file_field_tag 'attachments[dummy][file]', - :id => "_file#{work.id}", - :class => 'file_selector', - :multiple => true, - :onchange => "addInputFiles_board(this, '#{work.id}');", - :style => 'display:none', - :data => { - :max_file_size => Setting.attachment_max_size.to_i.kilobytes, - :max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)), - :max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i, - :upload_path => uploads_path(:format => 'js'), - :description_placeholder => l(:label_optional_description), - :field_is_public => l(:field_is_public), - :are_you_sure => l(:text_are_you_sure), - :file_count => l(:label_file_count), - :delete_all_files => l(:text_are_you_sure_all), - :containerid => "#{work.id}" - } %> - - <%= l(:label_no_file_uploaded) %> - - (<%= l(:label_max_size) %>: - <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) - - <% content_for :header_tags do %> - <%= javascript_include_tag 'attachments' %> - <% end %> -
    - +
    + + +
    + + <%= button_tag "文件浏览", :type=>"button", :onclick=>"$('#_file#{work.id}').click();",:onmouseover => 'this.focus()',:class => 'sub_btn mb0' %> + <%= file_field_tag 'attachments[dummy][file]', + :id => "_file#{work.id}", + :class => 'file_selector', + :multiple => true, + :onchange => "addInputFiles_board(this, '#{work.id}');", + :style => 'display:none', + :data => { + :max_file_size => Setting.attachment_max_size.to_i.kilobytes, + :max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)), + :max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i, + :upload_path => uploads_path(:format => 'js'), + :description_placeholder => l(:label_optional_description), + :field_is_public => l(:field_is_public), + :are_you_sure => l(:text_are_you_sure), + :file_count => l(:label_file_count), + :delete_all_files => l(:text_are_you_sure_all), + :containerid => "#{work.id}" + } %> + + <%= l(:label_no_file_uploaded) %> + + (<%= l(:label_max_size) %>: + <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) + + <% content_for :header_tags do %> + <%= javascript_include_tag 'attachments' %> + <% end %> +
    + diff --git a/public/stylesheets/new_user.css b/public/stylesheets/new_user.css index c1ddfa457..a3b7d7e76 100644 --- a/public/stylesheets/new_user.css +++ b/public/stylesheets/new_user.css @@ -105,6 +105,7 @@ a.linkGrey6:hover {color:#ffffff !important;} .mt12 { margin-top:12px;} .mt15 {margin-top:15px;} .mt19 {margin-top:19px !important;} +.mb0 {margin-bottom: 0px !important;} .mb4{ margin-bottom:4px;} .mb5{ margin-bottom:5px;} .mb8 {margin-bottom:8px !important;} diff --git a/public/stylesheets/public.css b/public/stylesheets/public.css index 630b96bcd..d45b6f94d 100644 --- a/public/stylesheets/public.css +++ b/public/stylesheets/public.css @@ -108,6 +108,7 @@ h4{ font-size:14px; color:#3b3b3b;} .mt15 {margin-top:15px;} .mt19 {margin-top:19px !important;} .ml70{margin-left: 70px;} +.mb0 {margin-bottom: 0px !important;} .mb4{ margin-bottom:4px;} .mb5{ margin-bottom:5px;} .mb8 {margin-bottom:8px;} From 2221fb4d7a3f02bfd345ba6bd6bc95d7e2967d84 Mon Sep 17 00:00:00 2001 From: huang Date: Fri, 23 Oct 2015 16:28:17 +0800 Subject: [PATCH 045/169] =?UTF-8?q?committers=E8=B7=B3=E8=BD=AC=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20=E6=8C=89=E9=92=AE=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 6 ++++++ public/stylesheets/public.css | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index bf3875835..b3b2f414a 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -192,6 +192,12 @@ update render :layout => "base_projects" } end + elsif request.get? + respond_to do |format| + format.html{ + render :layout => "base_projects" + } + end end end diff --git a/public/stylesheets/public.css b/public/stylesheets/public.css index b254569f0..7173222da 100644 --- a/public/stylesheets/public.css +++ b/public/stylesheets/public.css @@ -12,7 +12,7 @@ textarea {resize: none;} .pInline {margin:0px; padding:0px; display:inline-block;} /*常用*/ -select,input,textarea{ border:1px solid #269ac9; background:#fff; color:#000; padding-left:5px; } +select,input,textarea{ border:1px solid #269ac9; background:#fff; color:#000; padding-left:5px;padding-right: 5px; } .sub_btn{ cursor:pointer; -moz-border-radius:3px; -webkit-border-radius:3px; border:1px solid #707070; color:#000; border-radius:3px; padding:1px 10px; margin-bottom:10px; background:#dbdbdb;} .sub_btn:hover{ background:#b5e2fa; color:#000; border:1px solid #3c7fb1;} table{ background:#fff;} From bc3fbc270f2ca9de873429f613a3e373ca0bde52 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Fri, 23 Oct 2015 16:28:22 +0800 Subject: [PATCH 046/169] =?UTF-8?q?=E5=B0=86=E5=AD=A6=E7=94=9F=E6=94=B9?= =?UTF-8?q?=E6=88=90=E5=90=8C=E5=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/_user_message_course.html.erb | 2 +- public/stylesheets/new_user.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/users/_user_message_course.html.erb b/app/views/users/_user_message_course.html.erb index fb63f4fc0..91ea32a62 100644 --- a/app/views/users/_user_message_course.html.erb +++ b/app/views/users/_user_message_course.html.erb @@ -361,7 +361,7 @@
  • <%= time_tag(ma.created_at).html_safe %>
  • From 2a76461cd115bf25f9520a48b06d29eddfec92b5 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Sat, 24 Oct 2015 15:34:43 +0800 Subject: [PATCH 058/169] =?UTF-8?q?=E7=AE=80=E5=8D=95=E5=8D=9A=E5=AE=A2?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/attachments_controller.rb | 2 + app/controllers/blog_comments_controller.rb | 121 ++++++++++++ app/controllers/blogs_controller.rb | 43 ++++ app/helpers/blog_comments_helper.rb | 2 + app/helpers/blogs_helper.rb | 2 + app/helpers/owner_type_helper.rb | 1 + app/models/blog.rb | 16 ++ app/models/blog_comment.rb | 24 +++ app/models/user.rb | 13 ++ .../blog_comments/_attachments_links.html.erb | 41 ++++ app/views/blog_comments/_blog_attachments.erb | 78 ++++++++ app/views/blog_comments/_edit.html.erb | 53 +++++ app/views/blog_comments/_new.html.erb | 62 ++++++ app/views/blog_comments/_reply_form.html.erb | 35 ++++ .../_simple_ke_reply_form.html.erb | 31 +++ app/views/blog_comments/edit.html.erb | 6 + app/views/blog_comments/quote.js.erb | 10 + app/views/blog_comments/reply.js.erb | 2 + app/views/blog_comments/show.html.erb | 175 ++++++++++++++++ app/views/blogs/_article.html.erb | 145 ++++++++++++++ app/views/blogs/_article_list.html.erb | 101 ++++++++++ app/views/blogs/index.html.erb | 187 ++++++++++++++++++ app/views/blogs/show.html.erb | 0 app/views/courses/join.js.erb | 2 +- app/views/layouts/new_base_user.html.erb | 18 +- db/migrate/20151022071611_create_blogs.rb | 15 ++ .../20151022071804_create_blog_comments.rb | 19 ++ db/schema.rb | 75 +++++-- public/javascripts/blog.js | 82 ++++++++ public/stylesheets/new_user.css | 2 + .../blog_comments_controller_spec.rb | 5 + spec/controllers/blogs_controller_spec.rb | 5 + spec/factories/blog_comments.rb | 6 + spec/factories/blogs.rb | 6 + 34 files changed, 1357 insertions(+), 28 deletions(-) create mode 100644 app/controllers/blog_comments_controller.rb create mode 100644 app/controllers/blogs_controller.rb create mode 100644 app/helpers/blog_comments_helper.rb create mode 100644 app/helpers/blogs_helper.rb create mode 100644 app/models/blog.rb create mode 100644 app/models/blog_comment.rb create mode 100644 app/views/blog_comments/_attachments_links.html.erb create mode 100644 app/views/blog_comments/_blog_attachments.erb create mode 100644 app/views/blog_comments/_edit.html.erb create mode 100644 app/views/blog_comments/_new.html.erb create mode 100644 app/views/blog_comments/_reply_form.html.erb create mode 100644 app/views/blog_comments/_simple_ke_reply_form.html.erb create mode 100644 app/views/blog_comments/edit.html.erb create mode 100644 app/views/blog_comments/quote.js.erb create mode 100644 app/views/blog_comments/reply.js.erb create mode 100644 app/views/blog_comments/show.html.erb create mode 100644 app/views/blogs/_article.html.erb create mode 100644 app/views/blogs/_article_list.html.erb create mode 100644 app/views/blogs/index.html.erb create mode 100644 app/views/blogs/show.html.erb create mode 100644 db/migrate/20151022071611_create_blogs.rb create mode 100644 db/migrate/20151022071804_create_blog_comments.rb create mode 100644 public/javascripts/blog.js create mode 100644 spec/controllers/blog_comments_controller_spec.rb create mode 100644 spec/controllers/blogs_controller_spec.rb create mode 100644 spec/factories/blog_comments.rb create mode 100644 spec/factories/blogs.rb diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb index ee953e913..2c48857a2 100644 --- a/app/controllers/attachments_controller.rb +++ b/app/controllers/attachments_controller.rb @@ -227,6 +227,8 @@ class AttachmentsController < ApplicationController format.js elsif @attachment.container.is_a?(Message) format.html { redirect_to_referer_or new_board_message_path(@attachment.container) } + elseif @attachment.container.is_a?(BlogComment) + format.html { redirect_to_referer_or user_blog_blog_comment_path(:user_id=>@attachment.container.author.id,:blog_id=>@attachment.container.blog_id,:id=>@attachment.container.id)} elsif @course.nil? format.html { redirect_to_referer_or forum_memo_path(@attachment.container.forum, @attachment.container) } else diff --git a/app/controllers/blog_comments_controller.rb b/app/controllers/blog_comments_controller.rb new file mode 100644 index 000000000..54def4c1a --- /dev/null +++ b/app/controllers/blog_comments_controller.rb @@ -0,0 +1,121 @@ +class BlogCommentsController < ApplicationController + include ApplicationHelper + before_filter :find_user + def index + + end + def create + if User.current.logged? + @article = BlogComment.new + @article.author = User.current + @article.blog_id = params[:blog_id] + @article.safe_attributes = params[:blog_comment] + if request.post? + @article.save_attachments(params[:attachments]) + if @article.save + # 更新kindeditor上传的图片资源所有者 + # if params[:asset_id] + # ids = params[:asset_id].split(',') + # update_kindeditor_assets_owner ids,@article.id,OwnerTypeHelper::BLOGCOMMENT + # end + render_attachment_warning_if_needed(@article) + else + end + redirect_to user_blogs_path(:user_id=>params[:user_id]) + else + respond_to do |format| + format.html { + render :layout => 'new_base_user' + } + end + end + else + redirect_to signin_path + end + end + def new + respond_to do |format| + format.html {render :layout=>'new_base_user'} + end + end + def show + @article = BlogComment.find(params[:id]) + respond_to do |format| + format.html {render :layout=>'new_base_user'} + end + end + def update + @article = BlogComment.find(params[:id]) + @article.safe_attributes = params[:blog_comment] + @article.save_attachments(params[:attachments]) + if @article.save + render_attachment_warning_if_needed(@article) + else + end + redirect_to user_blog_blog_comment_path(:user_id=>params[:user_id],:blog_id=>params[:blog_id],:id=>params[:id]) + end + def destroy + @article = BlogComment.find(params[:id]) + unless @article.children.empty? #如果是文章被删,那么跳转到用户博客界面 + @article.children.delete + @article.delete + redirect_to user_blogs_path(:user_id=>User.current) + else + root = @article.root + @article.delete + redirect_to user_blog_blog_comment_path(:user_id=>root.author_id,:blog_id=>root.blog_id,:id=>root.id) + end + end + + def edit + @article = BlogComment.find(params[:id]) + respond_to do |format| + format.html {render :layout=>'new_base_user'} + end + end + + def quote + @blogComment = BlogComment.find(params[:id]) + @subject = @blogComment.title + @subject = "RE: #{@subject}" unless @subject.starts_with?('RE:') + + @content = "> #{ll(Setting.default_language, :text_user_wrote, @blogComment.author.realname)}\n> " + @temp = BlogComment.new + @temp.content = "
    #{ll(Setting.default_language, :text_user_wrote, @blogComment.author.realname)}
    #{@blogComment.content.html_safe}
    ".html_safe + respond_to do | format| + format.js + end + end + + #回复 + def reply + @article = BlogComment.find(params[:id]).root + @quote = params[:quote][:quote] + @blogComment = BlogComment.new + @blogComment.author = User.current + @blogComment.blog = Blog.find(params[:blog_id]) + params[:blog_comment][:sticky] = params[:blog_comment][:sticky] || 0 + params[:blog_comment][:locked] = params[:blog_comment][:locked] || 0 + @blogComment.safe_attributes = params[:blog_comment] + @blogComment.content = @quote + @blogComment.content + @blogComment.title = "RE: #{@article.title}" unless params[:blog_comment][:title] + @article.children << @blogComment + @user_activity_id = params[:user_activity_id] + + attachments = Attachment.attach_files(@blogComment, params[:attachments]) + render_attachment_warning_if_needed(@blogComment) + #@article.save + # redirect_to user_blogs_path(:user_id=>params[:user_id]) + respond_to do |format| + format.html { redirect_to user_blog_blog_comment_path(:user_id=>@article.author_id,:blog_id=>@article.blog_id,:id=>@article)} + format.js + end + rescue Exception => e #如果上面的代码执行发生异常就捕获 + flash[:notice] = e.message + end + + private + def find_user + @user = User.find(params[:user_id]) + end +end diff --git a/app/controllers/blogs_controller.rb b/app/controllers/blogs_controller.rb new file mode 100644 index 000000000..6be17f1d3 --- /dev/null +++ b/app/controllers/blogs_controller.rb @@ -0,0 +1,43 @@ +class BlogsController < ApplicationController + before_filter :find_blog,:except => [:index,:create,:new] + before_filter :find_user + def index + @articls = @user.blog.articles + @article = BlogComment.new + respond_to do |format| + format.html {render :layout=>'new_base_user'} + end + end + def create + + end + def new + + end + def show + + end + def update + + end + def destory + + end + def edit + + end + private + def find_blog + @blog = Blog.find(params[:blog_id]) + if @blog.nil? + #如果某个user的blog不存在,那么就创建一条 + @blog = Blog.create(:name=>User.find(params[:id]).realname , + :description=>'', + :author_id=>params[:id]) + end + end + + def find_user + @user = User.find(params[:user_id]) + end +end diff --git a/app/helpers/blog_comments_helper.rb b/app/helpers/blog_comments_helper.rb new file mode 100644 index 000000000..2b0c3e5bd --- /dev/null +++ b/app/helpers/blog_comments_helper.rb @@ -0,0 +1,2 @@ +module BlogCommentsHelper +end diff --git a/app/helpers/blogs_helper.rb b/app/helpers/blogs_helper.rb new file mode 100644 index 000000000..cc0dbd200 --- /dev/null +++ b/app/helpers/blogs_helper.rb @@ -0,0 +1,2 @@ +module BlogsHelper +end diff --git a/app/helpers/owner_type_helper.rb b/app/helpers/owner_type_helper.rb index c03f2d19e..7119d4f60 100644 --- a/app/helpers/owner_type_helper.rb +++ b/app/helpers/owner_type_helper.rb @@ -7,4 +7,5 @@ module OwnerTypeHelper BID = 6 JOURNALSFORMESSAGE = 7 HOMEWORKCOMMON = 8 + BLOGCOMMENT=9 end \ No newline at end of file diff --git a/app/models/blog.rb b/app/models/blog.rb new file mode 100644 index 000000000..bd338cdad --- /dev/null +++ b/app/models/blog.rb @@ -0,0 +1,16 @@ +class Blog < ActiveRecord::Base + # attr_accessible :title, :body + include Redmine::SafeAttributes + belongs_to :user + has_many :articles, :class_name => 'BlogComment', :conditions => "#{BlogComment.table_name}.parent_id IS NULL ", :order => "#{BlogComment.table_name}.created_on DESC" + has_many :blog_comments, :dependent => :destroy, :order => "#{BlogComment.table_name}.created_on DESC" + belongs_to :last_comment, :class_name => 'BlogComment', :foreign_key => :last_comment_id + acts_as_tree :dependent => :nullify + #acts_as_list :scope => '(user_id = #{user_id} AND parent_id #{user_id ? = "#{parent_id}" : "IS NULL"})' + acts_as_watchable + + validates :name, presence: true, length: {maximum: 30} + validates :description, length: {maximum: 255} + + safe_attributes 'name', 'description' +end diff --git a/app/models/blog_comment.rb b/app/models/blog_comment.rb new file mode 100644 index 000000000..92970b663 --- /dev/null +++ b/app/models/blog_comment.rb @@ -0,0 +1,24 @@ +class BlogComment < ActiveRecord::Base + # attr_accessible :title, :body + include Redmine::SafeAttributes + belongs_to :blog + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + + acts_as_tree :counter_cache => :comments_count, :order => "#{BlogComment.table_name}.sticky desc ,#{BlogComment.table_name}.created_on ASC" + acts_as_attachable + belongs_to :last_reply, :class_name => 'BlogComment', :foreign_key => 'last_comment_id' + + acts_as_watchable + + validates_presence_of :title, :content + validates_length_of :title, :maximum => 255 + #validate :cannot_reply_to_locked_comment, :on => :create + safe_attributes 'title', 'content',"sticky", "locked" + + def deleted_attach_able_by? user + (user && user.logged? && (self.author == user) ) || user.admin? + end + + def project + end +end diff --git a/app/models/user.rb b/app/models/user.rb index d66785460..7aaae3492 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -93,6 +93,7 @@ class User < Principal has_many :changesets, :dependent => :nullify has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'" + has_one :blog, :class_name => 'Blog', :foreign_key => "author_id" has_one :api_token, :class_name => 'Token', :conditions => "action='api'" belongs_to :auth_source belongs_to :ucourse, :class_name => 'Course', :foreign_key => :id #huang @@ -255,6 +256,18 @@ class User < Principal # count = self.journals_for_messages(:conditions => ["status=? and is_readed = ? " ,1, 0]).count end + def blog + @blog = Blog.where("author_id = #{self.id}").all[0] + if @blog.nil? + #如果某个user的blog不存在,那么就创建一条,并且跳转 + @blog = Blog.create(:name=>(User.find(self.id).realname), + :description=>'', + :author_id=>self.id) + @blog.save + end + @blog + end + # 查询指派给我的缺陷记录 def count_new_issue_assign_to self.issue_assigns diff --git a/app/views/blog_comments/_attachments_links.html.erb b/app/views/blog_comments/_attachments_links.html.erb new file mode 100644 index 000000000..ca0f41d16 --- /dev/null +++ b/app/views/blog_comments/_attachments_links.html.erb @@ -0,0 +1,41 @@ +
    + <% for attachment in attachments %> + + + + <% if options[:length] %> + + <%= link_to_short_attachment attachment, :class => 'fl FilesName02', :download => true,:length => options[:length] -%> + (<%= number_to_human_size attachment.filesize , :precision => 0 %>) + <% if options[:deletable] %> + <%#= link_to image_tag('delete.png'), attachment_path(attachment), + :data => {:confirm => l(:text_are_you_sure)}, + :method => :delete, + :class => 'delete', + #:remote => true, + #:id => "attachments_" + attachment.id.to_s, + :title => l(:button_delete) %> + + <% end %> +
    + <% else %> + + <%= link_to_short_attachment attachment, :class => 'fl FilesName02', :download => true, :length => 45 -%> + (<%= number_to_human_size attachment.filesize , :precision => 0 %>) + <% if options[:deletable] %> + + <% end %> +
    + <% end %> + <% end %> + <% if defined?(thumbnails) && thumbnails %> + <% images = attachments.select(&:thumbnailable?) %> + <% if images.any? %> +
    + <% images.each do |attachment| %> +
    <%= thumbnail_tag(attachment) %>
    + <% end %> +
    + <% end %> + <% end %> +
    diff --git a/app/views/blog_comments/_blog_attachments.erb b/app/views/blog_comments/_blog_attachments.erb new file mode 100644 index 000000000..48fe91099 --- /dev/null +++ b/app/views/blog_comments/_blog_attachments.erb @@ -0,0 +1,78 @@ + +
    + +<% if defined?(container) && container && container.saved_attachments %> + <% container.attachments.each_with_index do |attachment, i| %> + + <%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename readonly', :readonly => 'readonly') %><%= text_field_tag("attachments[p#{i}][description]", attachment.description, :maxlength => 254, :placeholder => l(:label_optional_description), :class => 'description', :style => "display: inline-block;") %><%= l(:field_is_public) %>: + <%= check_box_tag("attachments[p#{i}][is_public_checkbox]", attachment.is_public, attachment.is_public == 1 ? true : false, :class => 'is_public') %> + <%= if attachment.id.nil? + #待补充代码 + else + link_to(' '.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') + end + %> + <%#= render :partial => 'tags/tag', :locals => {:obj => attachment, :object_flag => "6"} %> + + <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %> + +
    + <% end %> + <% container.saved_attachments.each_with_index do |attachment, i| %> + + <%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename readonly', :readonly => 'readonly') %> + <%= text_field_tag("attachments[p#{i}][description]", attachment.description, :maxlength => 254, :placeholder => l(:label_optional_description), :class => 'description', :style => "display: inline-block;") %> + <%= l(:field_is_public) %>: + <%= check_box_tag("attachments[p#{i}][is_public_checkbox]", attachment.is_public, attachment.is_public == 1 ? true : false, :class => 'is_public') %> + <%= if attachment.id.nil? + #待补充代码 + else + link_to(' '.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') + end + %> + <%#= render :partial => 'tags/tag', :locals => {:obj => attachment, :object_flag => "6"} %> + + <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %> + +
    + <% end %> +<% end %> +
    +
    + + <%#= button_tag "浏览", :type=>"button", :onclick=>"CompatibleSend();" %> + + <%#= button_tag "文件浏览", :type=>"button", :onclick=>"$('#_file').click();",:onmouseover => 'this.focus()',:class => 'sub_btn' %> + 上传附件 + <%= file_field_tag 'attachments[dummy][file]', + :id => '_file', + :class => 'file_selector', + :multiple => true, + :onchange => 'addInputFiles(this);', + :style => ie8? ? '' : 'display:none', + :data => { + :max_file_size => Setting.attachment_max_size.to_i.kilobytes, + :max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)), + :max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i, + :upload_path => uploads_path(:format => 'js', :project => container), + :description_placeholder => l(:label_optional_description), + :field_is_public => l(:field_is_public), + :are_you_sure => l(:text_are_you_sure), + :file_count => l(:label_file_count), + :delete_all_files => l(:text_are_you_sure_all) + } %> + + <%= l(:label_no_file_uploaded) %> + + (<%= l(:label_max_size) %>: + <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) + + + <% content_for :header_tags do %> + <%= javascript_include_tag 'attachments' %> + <% end %> +
    + diff --git a/app/views/blog_comments/_edit.html.erb b/app/views/blog_comments/_edit.html.erb new file mode 100644 index 000000000..d56557ce7 --- /dev/null +++ b/app/views/blog_comments/_edit.html.erb @@ -0,0 +1,53 @@ +<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg' ,'blog' %> +
    +
    +
    +
    + +

    +
    +
    + <%if User.current.id == user.id%> +
    + <%= f.check_box :sticky%> + <%= label_tag 'message_sticky', l(:label_board_sticky) %> + <%= f.check_box :locked%> + <%= label_tag 'message_locked', l(:label_board_locked) %> +
    +
    + <% end %> +
    +
    + <%= text_area :quote,:quote,:style => 'display:none' %> + <%= hidden_field_tag :asset_id,params[:asset_id],:required => false,:style => 'display:none' %> + + <%= f.kindeditor :content,:editor_id => 'message_content_editor', + :owner_id => article.nil? ? 0: article.id, + :owner_type => OwnerTypeHelper::BLOGCOMMENT, + :width => '100%', + :height => 300, + :minHeight=>300, + :class => 'talk_text fl', + :input_html => { :id => 'message_content', + :class => 'talk_text fl', + :maxlength => 5000 }%> +
    +

    +
    +
    +
    +
    + <%= render :partial => 'blog_comments/blog_attachments', :locals => {:container => article} %> +
    +
    +
    +
    + 确定 + + 取消 +
    +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/app/views/blog_comments/_new.html.erb b/app/views/blog_comments/_new.html.erb new file mode 100644 index 000000000..2a2281a40 --- /dev/null +++ b/app/views/blog_comments/_new.html.erb @@ -0,0 +1,62 @@ +<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg' ,'blog' %> +
    +
    +
    +
    + +

    +
    + +
    + <%#= render :partial => 'course_new_topic', :locals => {:f => f, :topic => @message} %> + +
    +
    \ No newline at end of file diff --git a/app/views/blog_comments/_reply_form.html.erb b/app/views/blog_comments/_reply_form.html.erb new file mode 100644 index 000000000..cc4c0e952 --- /dev/null +++ b/app/views/blog_comments/_reply_form.html.erb @@ -0,0 +1,35 @@ +<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg' %> +
  • + +
    <%= f.text_field :title, { size: 60, id: "message_subject",:class=>"talk_input w585 fl" }.merge({ hidden: "hidden"}) %>
    + +
    +
  • +
  • +
    +
  • +
  • +
    + + <%= text_area :quote,:quote,:style => 'display:none' %> + <%= hidden_field_tag :asset_id,params[:asset_id],:required => false,:style => 'display:none' %> + + + + <%= f.kindeditor :content, :editor_id => 'message_content_editor', + :width => '99%', + :height => 100, + :minHeight=>100, + :input_html => { :id => 'message_content', + :class => 'talk_text fl', + :maxlength => 5000 }%> +
    +

    +
  • +
    +
  • +
    +
  • diff --git a/app/views/blog_comments/_simple_ke_reply_form.html.erb b/app/views/blog_comments/_simple_ke_reply_form.html.erb new file mode 100644 index 000000000..fa7ff0c4a --- /dev/null +++ b/app/views/blog_comments/_simple_ke_reply_form.html.erb @@ -0,0 +1,31 @@ + + +
    +
    <%= link_to image_tag(url_to_avatar(User.current), :width => "33", :height => "33"), user_path(User.current), :alt => "用户头像" %>
    +
    +
    + <%= form_for @blog_comment, :as => :reply, :url => {:controller => 'blog_comments',:action => 'reply', :id => @blogComment.id}, :html => {:multipart => true, :id => 'new_form'} do |f| %> + + + +
    + +
    +

    + <% end%> +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/app/views/blog_comments/edit.html.erb b/app/views/blog_comments/edit.html.erb new file mode 100644 index 000000000..a878063db --- /dev/null +++ b/app/views/blog_comments/edit.html.erb @@ -0,0 +1,6 @@ +<% if User.current.logged? && User.current.id == @user.id %> + <%= form_for @article, :url =>{:controller=>'blog_comments',:action => 'update',:user_id=>@user.id , :blog_id => @article.id},:method=>'PUT', + :html => {:nhname=>'form',:multipart => true, :id => 'message-form'} do |f| %> + <%= render :partial => 'blog_comments/edit', :locals => {:f => f, :article => @article, :edit_mode => true, :user => @user} %> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/blog_comments/quote.js.erb b/app/views/blog_comments/quote.js.erb new file mode 100644 index 000000000..4d16745ca --- /dev/null +++ b/app/views/blog_comments/quote.js.erb @@ -0,0 +1,10 @@ +if($("#reply_message_<%= @blogComment.id%>").length > 0) { + $("#reply_message_<%= @blogComment.id%>").replaceWith("<%= escape_javascript(render :partial => 'simple_ke_reply_form', :locals => {:reply => @blogComment,:temp =>@temp,:subject =>@subject}) %>"); + $(function(){ + $('#reply_subject').val("<%= raw escape_javascript(@subject) %>"); + $('#quote_quote').val("<%= raw escape_javascript(@temp.content.html_safe) %>"); + init_activity_KindEditor_data(<%= @blogComment.id%>,null,"85%"); + }); +}else if($("#reply_to_message_<%= @blogComment.id%>").length >0) { + $("#reply_to_message_<%= @blogComment.id%>").replaceWith("

    "); +} \ No newline at end of file diff --git a/app/views/blog_comments/reply.js.erb b/app/views/blog_comments/reply.js.erb new file mode 100644 index 000000000..7ebe4d077 --- /dev/null +++ b/app/views/blog_comments/reply.js.erb @@ -0,0 +1,2 @@ +$("#user_activity_<%= @user_activity_id%>").replaceWith("<%= escape_javascript(render :partial => 'blogs/article', :locals => {:activity => @article,:user_activity_id =>@user_activity_id,:first_user_activity =>@first_user_activity,:page => @page}) %>"); +init_activity_KindEditor_data(<%= @user_activity_id%>,"","87%"); \ No newline at end of file diff --git a/app/views/blog_comments/show.html.erb b/app/views/blog_comments/show.html.erb new file mode 100644 index 000000000..922fde8ed --- /dev/null +++ b/app/views/blog_comments/show.html.erb @@ -0,0 +1,175 @@ +<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg',"init_activity_KindEditor",'blog' %> + + + +
    +
    +
    + <%= link_to image_tag(url_to_avatar(@article.author),:width=>50,:height => 50,:alt=>'图像' ),user_path(@article.author) %> +
    +
    + <% if @article.author.id == User.current.id%> + + <%end%> + +
    + +
    + <% if @article.try(:author).try(:realname) == ' ' %> + <%= link_to @article.try(:author), user_path(@article.author,:host=>Setting.host_user), :class => "linkBlue2", :target=> "_blank" %> + <% else %> + <%= link_to @article.try(:author).try(:realname), user_path(@article.author,:host=>Setting.host_user), :class => "linkBlue2", :target=> "_blank" %> + <% end %> +
    +
    <%= format_time( @article.created_on)%>
    +
    +
    + <%= @article.content.html_safe%> +
    +
    +
    + <%#= link_to_attachments_course @topic, :author => false %> + <% if @article.attachments.any?%> + <% options = {:author => true, :deletable => true} %> + <%= render :partial => 'blog_comments/attachments_links', :locals => {:attachments => @article.attachments, :options => options, :is_float => true} %> + <% end %> +
    +
    +
    +
    +
    + <% count=0 %> + <% if @article.parent %> + <% count=@article.parent.children.count%> + <% else %> + <% count=@article.children.count%> + <% end %> +
    + <% unless count == 0 %> +
    +
    回复(<%=count %>)
    +
    + +
    +
    + <%@article.children.reorder('created_on desc').each_with_index do |reply,i| %> + +
    +
    + <%= link_to image_tag(url_to_avatar(reply.author), :width => 33,:height => 33), user_path(reply.author) %> +
    +
    +
    + <% if reply.try(:author).try(:realname) == ' ' %> + <%= link_to reply.try(:author), user_path(reply.author_id,:host=>Setting.host_user), :class => "newsBlue mr10 f14" %> + <% else %> + <%= link_to reply.try(:author).try(:realname), user_path(reply.author_id,:host=>Setting.host_user), :class => "newsBlue mr10 f14" %> + <% end %> +
    +
    + <%= reply.content.html_safe%> +
    +
    + <%= format_time(reply.created_on) %> + +
    +

    +
    +
    +
    + <% end %> +
    + + <% end %> +
    + <% if !@article.locked? && User.current.logged?%> +
    + +
    +
    + <%= form_for :blog_comment, :url => {:action => 'reply',:controller => 'blog_comments',:user_id=>@article.author.id,:blog_id=>@article.blog_id, :id => @article.id}, :html => {:multipart => true, :id => 'message_form'} do |f| %> + <%= render :partial => 'blog_comments/reply_form', :locals => {:f => f,:user=>@user,:article=>@article} %> + <%= link_to l(:button_cancel), "javascript:void(0)", :onclick => 'canel_message_replay();', :class => "blue_btn grey_btn fr c_white mt10 mr5" %> + <%= link_to l(:button_submit), "javascript:void(0)", :onclick => 'submit_message_replay();', :class => "blue_btn fr c_white mt10", :style => "margin-right: 5px;" %> + <% end %> +
    +
    +
    + <% end %> +
    +
    \ No newline at end of file diff --git a/app/views/blogs/_article.html.erb b/app/views/blogs/_article.html.erb new file mode 100644 index 000000000..943d21852 --- /dev/null +++ b/app/views/blogs/_article.html.erb @@ -0,0 +1,145 @@ +
    +
    +
    + <%= link_to image_tag(url_to_avatar(activity.author), :width => "50", :height => "50"), user_path(activity.author_id,:host=>Setting.host_user), :alt => "用户头像" %> +
    +
    +
    + <% if activity.try(:author).try(:realname) == ' ' %> + <%= link_to activity.try(:author), user_path(activity.author_id,:host=>Setting.host_user), :class => "newsBlue mr15" %> + <% else %> + <%= link_to activity.try(:author).try(:realname), user_path(activity.author_id,:host=>Setting.host_user), :class => "newsBlue mr15" %> + <% end %> + TO + <%= link_to activity.blog.name+" | 博客", user_blogs_path(:user_id=>activity.author_id,:host=>Setting.host_user), :class => "newsBlue ml15 mr5"%> +
    + + <% if activity.sticky == 1%> + 置顶 + <% end%> + <% if activity.locked%> +        + <% end%> +
    +
    + 发帖时间:<%= format_time(activity.created_on) %> +
    + +
    + <% if activity.parent_id.nil? %> + <%= activity.content.to_s.html_safe%> + <% else %> + <%= activity.parent.content.to_s.html_safe%> + <% end %> +
    +
    +
    + <% if activity.attachments.any?%> + <% options = {:author => true, :deletable => false } %> + <%= render :partial => 'blog_comments/attachments_links', :locals => {:attachments => activity.attachments, :options => options, :is_float => true} %> + <% end %> +
    + +
    +
    +
    + <% count=0 %> + <% if activity.parent %> + <% count=activity.parent.children.count%> + <% else %> + <% count=activity.children.count%> + <% end %> +
    +
    +
    +
    回复( + <%= count %> + )
    +
    <%#=format_date(activity.updated_on)%>
    + <%if count > 3 %> + + <% end %> +
    + + <% activity= activity.parent ? activity.parent : activity%> + <% replies_all_i = 0 %> + <% if count > 0 %> +
    +
      + <% activity.children.reorder("created_on desc").each do |reply|%> + + <% replies_all_i=replies_all_i+1 %> +
    • +
      + <%= link_to image_tag(url_to_avatar(reply.author), :width => "33", :height => "33"), user_path(reply.author_id,:host=>Setting.host_user), :alt => "用户头像" %> +
      +
      +
      + <% if reply.try(:author).try(:realname) == ' ' %> + <%= link_to reply.try(:author), user_path(reply.author_id,:host=>Setting.host_user), :class => "newsBlue mr10 f14" %> + <% else %> + <%= link_to reply.try(:author).try(:realname), user_path(reply.author_id,:host=>Setting.host_user), :class => "newsBlue mr10 f14" %> + <% end %> + <%= format_time(reply.created_on) %> +
      +
      + <%= reply.content.html_safe %> +
      +
      +
      +
    • + <% end %> +
    +
    + <% end %> + + <% if !activity.locked? %> +
    +
    <%= link_to image_tag(url_to_avatar(User.current), :width => "33", :height => "33"), user_path(activity.author_id), :alt => "用户头像" %>
    +
    +
    + <%= form_for('new_form',:url => {:controller=>'blog_comments',:action => 'reply', :id => activity.id, :blog_id => activity.blog.id, :user_id => activity.author_id},:method => "post",:remote=>true) do |f|%> + + + + + + +
    + +
    +

    + <% end%> +
    +
    +
    +
    +
    +
    + <% end %> +
    +
    diff --git a/app/views/blogs/_article_list.html.erb b/app/views/blogs/_article_list.html.erb new file mode 100644 index 000000000..e397da2cc --- /dev/null +++ b/app/views/blogs/_article_list.html.erb @@ -0,0 +1,101 @@ +<%= javascript_include_tag "/assets/kindeditor/kindeditor", '/assets/kindeditor/pasteimg', "init_activity_KindEditor" %> + + + +
    +
    +
    + <%= @user.name%>的博客 +
    +
    + <% if User.current.logged? && User.current.id == @user.id %> + <%= labelled_form_for @article, :url =>{:controller=>'blog_comments',:action => 'create',:user_id=>user.id , :blog_id => blog.id}, + :html => {:nhname=>'form',:multipart => true, :id => 'message-form'} do |f| %> + <%= render :partial => 'blog_comments/new', :locals => {:f => f, :article => @article, :edit_mode => false, :user => @user} %> + <% end %> + <% end %> + + <% if topics%> + <% topics.each do |topic| %> + + <% if topic %> + <%= render :partial => 'blogs/article', :locals => {:activity => topic, :user_activity_id => topic.id} %> + <% end %> + <% end %> + + <%# if topics.count == 10 %> + + <%# end %> + <% end%> +
    + + diff --git a/app/views/blogs/index.html.erb b/app/views/blogs/index.html.erb new file mode 100644 index 000000000..0e69d1654 --- /dev/null +++ b/app/views/blogs/index.html.erb @@ -0,0 +1,187 @@ + + +<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg' %> +<%#= javascript_include_tag "/assets/kindeditor/kindeditor-min" %> + + +<%= render :partial => 'blogs/article_list', :locals => {:blog=>@user.blog,:topics => @user.blog.articles.reorder("#{BlogComment.table_name}.sticky desc,#{BlogComment.table_name}.created_on desc"), :page => 0, :user => @user} %> + + + diff --git a/app/views/blogs/show.html.erb b/app/views/blogs/show.html.erb new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/courses/join.js.erb b/app/views/courses/join.js.erb index b4e6a98ec..4965de676 100644 --- a/app/views/courses/join.js.erb +++ b/app/views/courses/join.js.erb @@ -1,4 +1,4 @@ -<% if @object_id && @state != 6%> +<% if @object_id && @state != 6 && @state != 4 %> $("#join_in_course_header").html("<%= escape_javascript(join_in_course_header(@course, @user)) %>"); <% end %> <% if @state %> diff --git a/app/views/layouts/new_base_user.html.erb b/app/views/layouts/new_base_user.html.erb index f3adb0155..01c73addb 100644 --- a/app/views/layouts/new_base_user.html.erb +++ b/app/views/layouts/new_base_user.html.erb @@ -71,6 +71,14 @@
    +
    +
    + <%= link_to(@user.blog.blog_comments.where("#{BlogComment.table_name}.parent_id is null").count, + {:controller => 'blogs', :action => 'index', :user_id => @user.id }, :class => 'homepageImageNumber',:id => 'user_score') %> +
    +
    博客
    +
    +
    <%= link_to User.watched_by(@user.id).count.to_s, {:controller=>"users", :action=>"user_watchlist",:id=>@user.id},:class=>"homepageImageNumber" %> @@ -84,14 +92,8 @@
    粉丝
    -
    -
    -
    - <%= link_to(format("%.2f" ,get_option_number(@user,1).total_score ).to_i, - {:controller => 'users', :action => 'show_new_score', :remote => true, :id => @user.id }, :class => 'homepageImageNumber',:id => 'user_score') %> -
    -
    积分
    -
    + +
    diff --git a/db/migrate/20151022071611_create_blogs.rb b/db/migrate/20151022071611_create_blogs.rb new file mode 100644 index 000000000..dd782c78f --- /dev/null +++ b/db/migrate/20151022071611_create_blogs.rb @@ -0,0 +1,15 @@ +class CreateBlogs < ActiveRecord::Migration + def change + create_table :blogs do |t| + t.string "name", :default => "", :null => false + t.text "description" + t.integer "position", :default => 1 + t.integer "article_count", :default => 0, :null => false + t.integer "comments_count", :default => 0, :null => false + t.integer "last_comments_id" + t.integer "parent_id" + t.integer "author_id" + t.timestamps + end + end +end diff --git a/db/migrate/20151022071804_create_blog_comments.rb b/db/migrate/20151022071804_create_blog_comments.rb new file mode 100644 index 000000000..254dfd692 --- /dev/null +++ b/db/migrate/20151022071804_create_blog_comments.rb @@ -0,0 +1,19 @@ +class CreateBlogComments < ActiveRecord::Migration + def change + create_table :blog_comments do |t| + t.integer "blog_id", :null => false + t.integer "parent_id" + t.string "title", :default => "", :null => false + t.text "content" + t.integer "author_id" + t.integer "comments_count", :default => 0, :null => false + t.integer "last_comment_id" + t.datetime "created_on", :null => false + t.datetime "updated_on", :null => false + t.boolean "locked", :default => false + t.integer "sticky", :default => 0 + t.integer "reply_id" + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4aa12f627..b18ffb544 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20151020021234) do +ActiveRecord::Schema.define(:version => 20151022071804) do create_table "activities", :force => true do |t| t.integer "act_id", :null => false @@ -144,6 +144,36 @@ ActiveRecord::Schema.define(:version => 20151020021234) do t.integer "open_anonymous_evaluation", :default => 1 end + create_table "blog_comments", :force => true do |t| + t.integer "blog_id", :null => false + t.integer "parent_id" + t.string "title", :default => "", :null => false + t.text "content" + t.integer "author_id" + t.integer "comments_count", :default => 0, :null => false + t.integer "last_comment_id" + t.datetime "created_on", :null => false + t.datetime "updated_on", :null => false + t.boolean "locked", :default => false + t.integer "sticky", :default => 0 + t.integer "reply_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "blogs", :force => true do |t| + t.string "name", :default => "", :null => false + t.text "description" + t.integer "position", :default => 1 + t.integer "article_count", :default => 0, :null => false + t.integer "comments_count", :default => 0, :null => false + t.integer "last_comments_id" + t.integer "parent_id" + t.integer "author_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "boards", :force => true do |t| t.integer "project_id", :null => false t.string "name", :default => "", :null => false @@ -497,26 +527,23 @@ ActiveRecord::Schema.define(:version => 20151020021234) do add_index "documents", ["created_on"], :name => "index_documents_on_created_on" add_index "documents", ["project_id"], :name => "documents_project_id" - create_table "dts", :primary_key => "Num", :force => true do |t| - t.string "Defect", :limit => 50 - t.string "Category", :limit => 50 - t.string "File" - t.string "Method" - t.string "Module", :limit => 20 - t.string "Variable", :limit => 50 - t.integer "StartLine" - t.integer "IPLine" - t.string "IPLineCode", :limit => 200 - t.string "Judge", :limit => 15 - t.integer "Review", :limit => 1 + create_table "dts", :force => true do |t| + t.string "IPLineCode" t.string "Description" - t.text "PreConditions", :limit => 2147483647 - t.text "TraceInfo", :limit => 2147483647 - t.text "Code", :limit => 2147483647 + t.string "Num" + t.string "Variable" + t.string "TraceInfo" + t.string "Method" + t.string "File" + t.string "IPLine" + t.string "Review" + t.string "Category" + t.string "Defect" + t.string "PreConditions" + t.string "StartLine" t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "enabled_modules", :force => true do |t| @@ -785,6 +812,16 @@ ActiveRecord::Schema.define(:version => 20151020021234) do add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id" + create_table "journal_details_copy", :force => true do |t| + t.integer "journal_id", :default => 0, :null => false + t.string "property", :limit => 30, :default => "", :null => false + t.string "prop_key", :limit => 30, :default => "", :null => false + t.text "old_value" + t.text "value" + end + + add_index "journal_details_copy", ["journal_id"], :name => "journal_details_journal_id" + create_table "journal_replies", :id => false, :force => true do |t| t.integer "journal_id" t.integer "user_id" diff --git a/public/javascripts/blog.js b/public/javascripts/blog.js new file mode 100644 index 000000000..62dbaedc5 --- /dev/null +++ b/public/javascripts/blog.js @@ -0,0 +1,82 @@ +function regexTopicSubject() { + var name = $("#message_subject").val(); + if(name.length ==0) + { + $("#subjectmsg").text("标题不能为空"); + $("#subjectmsg").css('color','#ff0000'); + $("#message_subject").focus(); + return false; + } + else if(name.length <= 255) + { + $("#subjectmsg").text("填写正确"); + $("#subjectmsg").css('color','#008000'); + return true; + } + else + { + $("#subjectmsg").text("标题超过255个字符"); + $("#subjectmsg").css('color','#ff0000'); + $("#message_subject").focus(); + return false; + } +} + +function submit_article() +{ + if(regexTopicSubject() && regexTopicDescription()) + { + message_content_editor.sync(); + $("#message-form").submit(); + } +} + +function regexTopicDescription() +{ + var name = message_content_editor.html(); + if(name.length ==0) + { + $("#message_content_span").text("描述不能为空"); + $("#message_content_span").css('color','#ff0000'); + return false; + } + else if(name.length >=6000){ + $("#message_content_span").text("描述最多3000个汉字(或6000个英文字符)"); + $("#message_content_span").css('color','#ff0000'); + return false; + } + else + { + $("#message_content_span").text("填写正确"); + $("#message_content_span").css('color','#008000'); + return true; + } +} + +function MessageReplayVevify() { + var content = message_content_editor.html();//$.trim($("#message_content").val()); + if (content.length == 0) { + $("#message_content_span").text("回复不能为空"); + $("#message_content_span").css('color', '#ff0000'); + return false; + } + else { + $("#message_content_span").text("填写正确"); + $("#message_content_span").css('color', '#008000'); + return true; + } +} +function submit_message_replay() +{ + if(MessageReplayVevify()) + { + message_content_editor.sync();//提交内容之前要sync,不然服务器端取不到值 + $("#message_form").submit(); + } +} + +function canel_message_replay() +{ + $("#reply").hide(200); + $("#message_quote").html(""); +} diff --git a/public/stylesheets/new_user.css b/public/stylesheets/new_user.css index 872d8df6d..0a61e0676 100644 --- a/public/stylesheets/new_user.css +++ b/public/stylesheets/new_user.css @@ -1296,3 +1296,5 @@ a:hover.link_file_a{ background:url(../images/pic_file.png) 0 -25px no-repeat; c .list_style ol li{list-style-type: decimal;margin-left: 20px;} .list_style ul li{list-style-type: disc;margin-left: 20px;} + +.ReplyToMessageInputContainer {width: 582px;float: left;} diff --git a/spec/controllers/blog_comments_controller_spec.rb b/spec/controllers/blog_comments_controller_spec.rb new file mode 100644 index 000000000..585af2555 --- /dev/null +++ b/spec/controllers/blog_comments_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe BlogCommentsController, :type => :controller do + +end diff --git a/spec/controllers/blogs_controller_spec.rb b/spec/controllers/blogs_controller_spec.rb new file mode 100644 index 000000000..5b618caa8 --- /dev/null +++ b/spec/controllers/blogs_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe BlogsController, :type => :controller do + +end diff --git a/spec/factories/blog_comments.rb b/spec/factories/blog_comments.rb new file mode 100644 index 000000000..e168f824a --- /dev/null +++ b/spec/factories/blog_comments.rb @@ -0,0 +1,6 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :blog_comment do + end +end diff --git a/spec/factories/blogs.rb b/spec/factories/blogs.rb new file mode 100644 index 000000000..a1a640f89 --- /dev/null +++ b/spec/factories/blogs.rb @@ -0,0 +1,6 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :blog do + end +end From 5e4e5419f10f45600449354b3789ae4b96b84145 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Sat, 24 Oct 2015 15:37:27 +0800 Subject: [PATCH 059/169] =?UTF-8?q?=E5=8F=AA=E6=9C=89=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E5=80=99=E6=89=8D=E5=85=81=E8=AE=B8=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/blog_comments/show.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/blog_comments/show.html.erb b/app/views/blog_comments/show.html.erb index 922fde8ed..e8f6c3b81 100644 --- a/app/views/blog_comments/show.html.erb +++ b/app/views/blog_comments/show.html.erb @@ -81,7 +81,7 @@
    <%#= link_to_attachments_course @topic, :author => false %> <% if @article.attachments.any?%> - <% options = {:author => true, :deletable => true} %> + <% options = {:author => true, :deletable => false} %> <%= render :partial => 'blog_comments/attachments_links', :locals => {:attachments => @article.attachments, :options => options, :is_float => true} %> <% end %>
    From a42a58fb42ec457e77d92141a673969b5e3955fa Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Sat, 24 Oct 2015 16:09:44 +0800 Subject: [PATCH 060/169] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=81=AB=E7=8B=90?= =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=99=A8=E5=87=BA=E7=8E=B0=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=9C=81=E7=95=A5=E5=8F=B7=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/stylesheets/new_user.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/stylesheets/new_user.css b/public/stylesheets/new_user.css index 3b79af2bb..5ccf68516 100644 --- a/public/stylesheets/new_user.css +++ b/public/stylesheets/new_user.css @@ -1269,7 +1269,7 @@ a:hover.tijiao{ background:#0f99a9;} #cboxPrevious{position:absolute; bottom:0px; left:0; color:#444;} #cboxNext{position:absolute; bottom:0px; left:63px; color:#444;} #cboxClose{position:absolute; bottom:0; right:0; display:block; color:#444;} -.system_message_style {line-height: 19.1px; max-width: 681px; work-wrap: break-word; word-break: break-all; text-overflow:clip;} +.system_message_style {line-height: 19.1px; max-width: 681px; work-wrap: break-word; word-break: break-all; text-overflow:clip; z-index:9999} .system_message_style img {max-width: 100%;} /*20150906关联项目LB*/ a.RalationIcon{ background: url(../images/homepage_icon.png) -183px -396px no-repeat; height:20px; display:block; padding-left:20px; color:#888888;} From 479b23a1fd77879841b57c157df84218c3aebc39 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Sat, 24 Oct 2015 16:12:03 +0800 Subject: [PATCH 061/169] =?UTF-8?q?=E9=87=8D=E5=A4=8D=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E7=BB=99=E5=87=BA=E6=8F=90=E7=A4=BA=20?= =?UTF-8?q?=E5=B9=B6=E4=B8=8D=E5=8F=91=E9=80=81=E6=B6=88=E6=81=AF=20?= =?UTF-8?q?=E4=B8=8D=E5=85=81=E8=AE=B8=E9=87=8D=E5=A4=8D=E5=8F=91=E9=80=81?= =?UTF-8?q?=E7=9B=B8=E5=90=8C=E7=9A=84=E7=94=B3=E8=AF=B7=20=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E7=A1=AE=E5=AE=9A=E8=87=AA=E5=8A=A8=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/courses_service.rb | 12 ++++++++++-- app/views/courses/join.js.erb | 10 +++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/services/courses_service.rb b/app/services/courses_service.rb index 1e0bfbec2..fb01e0398 100644 --- a/app/services/courses_service.rb +++ b/app/services/courses_service.rb @@ -300,6 +300,7 @@ class CoursesService #@state == 4 您加入的课程不存在 #@state == 5 您还未登录 #@state == 6 申请成功,请等待审核完毕 + #@state == 7 您已经发送过申请了,请耐心等待 #@state 其他 未知错误,请稍后再试 def join_course params,current_user course = Course.find_by_id params[:object_id] @@ -313,6 +314,8 @@ class CoursesService #如果加入角色为学生 if params[:role] == "10" @state = 3 + elsif current_user.allowed_to?(:as_teacher,course) + @state = 3 else #如果加入角色为教师或者教辅 CourseMessage.create(:user_id => course.tea_id, :course_id => course.id, :viewed => false,:content=> params[:role],:course_message_id=>User.current.id,:course_message_type=>'JoinCourseRequest',:status=>0) @@ -330,8 +333,13 @@ class CoursesService StudentsForCourse.create(:student_id => current_user.id, :course_id => params[:object_id]) @state = 0 else - CourseMessage.create(:user_id => course.tea_id, :course_id => course.id, :viewed => false,:content=> params[:role],:course_message_id=>User.current.id,:course_message_type=>'JoinCourseRequest',:status=>0) - @state = 6 + #如果已经发送过消息了,那么就要给个提示 + if CourseMessage.where("course_message_type = 'JoinCourseRequest' and user_id = #{course.tea_id} and content = #{params[:role]} and course_message_id = #{User.current.id} and course_id = #{course.id} and status = 0").count != 0 + @state = 7 + else + CourseMessage.create(:user_id => course.tea_id, :course_id => course.id, :viewed => false,:content=> params[:role],:course_message_id=>User.current.id,:course_message_type=>'JoinCourseRequest',:status=>0) + @state = 6 + end end else @state = 1 diff --git a/app/views/courses/join.js.erb b/app/views/courses/join.js.erb index 53cf250d7..e2de705fe 100644 --- a/app/views/courses/join.js.erb +++ b/app/views/courses/join.js.erb @@ -5,21 +5,25 @@ $("#join_in_course_header").html("<%= escape_javascript(join_in_course_header(@c <% if @state == 0 %> alert("加入成功"); hideModal($("#popbox02")); -$("#try_join_course_link").replaceWith(" 'index',:course=>course.id, :host=>Setting.host_course)%>' target='_blank' class='blue_n_btn fr mt20'>提交作品"); -window.location.href= "http://"+"<%= Setting.host_name%>"+"/courses/" + "<%= course.id%>" +$("#try_join_course_link").replaceWith(" 'index',:course=>@course.id, :host=>Setting.host_course)%>' target='_blank' class='blue_n_btn fr mt20'>提交作品"); +window.location.href= "http://"+"<%= Setting.host_name%>"+"/courses/" + "<%= @course.id%>" <% elsif @state == 1 %> alert("密码错误"); <% elsif @state == 2 %> alert("课程已过期\n请联系课程管理员重启课程。(在配置课程处)"); <% elsif @state == 3 %> alert("您已经加入了课程"); -window.location.href= "http://"+"<%= Setting.host_name%>"+"/courses/" + "<%= course.id%>" +window.location.href= "http://"+"<%= Setting.host_name%>"+"/courses/" + "<%= @course.id%>" <% elsif @state == 4 %> alert("您加入的课程不存在"); <% elsif @state == 5 %> alert("您还未登录"); <% elsif @state == 6 %> alert("申请成功,请等待审核") +hideModal($("#popbox02")); +<% elsif @state == 7%> + alert("您已经发送过申请了,请耐心等待") + hideModal($("#popbox02")); <% else %> alert("未知错误,请稍后再试"); <% end %> From 4431f4664f5cce2abc6191708ac6bcf8e0a8ed67 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Sat, 24 Oct 2015 16:16:04 +0800 Subject: [PATCH 062/169] =?UTF-8?q?=E7=82=B9=E5=87=BB=E7=A1=AE=E5=AE=9A?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/courses/join.js.erb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/courses/join.js.erb b/app/views/courses/join.js.erb index e2de705fe..8e6f8ce20 100644 --- a/app/views/courses/join.js.erb +++ b/app/views/courses/join.js.erb @@ -4,7 +4,7 @@ $("#join_in_course_header").html("<%= escape_javascript(join_in_course_header(@c <% if @state %> <% if @state == 0 %> alert("加入成功"); -hideModal($("#popbox02")); +hidden_join_course_form(); $("#try_join_course_link").replaceWith(" 'index',:course=>@course.id, :host=>Setting.host_course)%>' target='_blank' class='blue_n_btn fr mt20'>提交作品"); window.location.href= "http://"+"<%= Setting.host_name%>"+"/courses/" + "<%= @course.id%>" <% elsif @state == 1 %> @@ -19,11 +19,11 @@ alert("您加入的课程不存在"); <% elsif @state == 5 %> alert("您还未登录"); <% elsif @state == 6 %> -alert("申请成功,请等待审核") -hideModal($("#popbox02")); +alert("申请成功,请等待审核"); +hidden_join_course_form(); <% elsif @state == 7%> - alert("您已经发送过申请了,请耐心等待") - hideModal($("#popbox02")); + alert("您已经发送过申请了,请耐心等待"); +hidden_join_course_form(); <% else %> alert("未知错误,请稍后再试"); <% end %> From 586cde6f87c1d89fac58681b2449c7aa07ec73d9 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Mon, 26 Oct 2015 09:04:09 +0800 Subject: [PATCH 063/169] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E6=88=90=E4=B8=BA=E8=AF=BE=E7=A8=8B=E7=9A=84?= =?UTF-8?q?=E8=80=81=E5=B8=88=E6=88=96=E6=95=99=E8=BE=85=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E7=9A=84=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/users_controller.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 7425692c0..2ffc7338e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -918,6 +918,9 @@ class UsersController < ApplicationController end def show + #更新用户申请成为课程老师或教辅消息的状态 + join_course_messages = CourseMessage.where("course_message_type =? and course_message_id =? and viewed =?", 'JoinCourseRequest', @user.id, false) + join_course_messages.update_all(:viewed => true) @page = params[:page] ? params[:page].to_i + 1 : 0 user_project_ids = @user.projects.visible.empty? ? "(-1)" : "(" + @user.projects.visible.map{|project| project.id}.join(",") + ")" user_course_ids = @user.courses.visible.empty? ? "(-1)" : "(" + @user.courses.visible.map{|course| course.id}.join(",") + ")" From 69bc26033e09f88de292548c7587151f9fd65da6 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Mon, 26 Oct 2015 10:54:27 +0800 Subject: [PATCH 064/169] =?UTF-8?q?=E9=98=B2=E6=AD=A2url=E5=88=A0=E5=87=8F?= =?UTF-8?q?=E6=8A=A5=E9=94=99=20url=E5=8D=9A=E5=AE=A2=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E9=95=BF=E5=BA=A6=E9=99=90=E5=88=B6=E6=94=B9=E4=B8=BA20000?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/blogs_controller.rb | 6 +++++- public/javascripts/blog.js | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/blogs_controller.rb b/app/controllers/blogs_controller.rb index 6be17f1d3..07bf60464 100644 --- a/app/controllers/blogs_controller.rb +++ b/app/controllers/blogs_controller.rb @@ -28,7 +28,11 @@ class BlogsController < ApplicationController end private def find_blog - @blog = Blog.find(params[:blog_id]) + if params[:blog_id] + @blog = Blog.find(params[:blog_id]) + else + render_404 + end if @blog.nil? #如果某个user的blog不存在,那么就创建一条 @blog = Blog.create(:name=>User.find(params[:id]).realname , diff --git a/public/javascripts/blog.js b/public/javascripts/blog.js index 62dbaedc5..feaab0100 100644 --- a/public/javascripts/blog.js +++ b/public/javascripts/blog.js @@ -40,8 +40,8 @@ function regexTopicDescription() $("#message_content_span").css('color','#ff0000'); return false; } - else if(name.length >=6000){ - $("#message_content_span").text("描述最多3000个汉字(或6000个英文字符)"); + else if(name.length >=20000){ + $("#message_content_span").text("描述最多20000个汉字(或40000个英文字符)"); $("#message_content_span").css('color','#ff0000'); return false; } From 2824b564815aab96ec1f8191fc65c280d22a81d2 Mon Sep 17 00:00:00 2001 From: cxt Date: Mon, 26 Oct 2015 11:54:59 +0800 Subject: [PATCH 065/169] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E6=95=99=E5=B8=88=E5=8A=A0=E5=85=A5=E8=AF=BE=E7=A8=8B=E7=9A=84?= =?UTF-8?q?=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/layouts/new_base_user.html.erb | 24 +++++++++++++++++++++++- public/stylesheets/new_user.css | 5 +++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/new_base_user.html.erb b/app/views/layouts/new_base_user.html.erb index f3adb0155..7e7a1adf7 100644 --- a/app/views/layouts/new_base_user.html.erb +++ b/app/views/layouts/new_base_user.html.erb @@ -103,7 +103,21 @@ 课程 <% if is_current_user%> <% if User.current.user_extensions && User.current.user_extensions.identity == 0 && User.current.allowed_to?(:add_course, nil, :global => true)%> - <%=link_to "", new_course_path(:host=> Setting.host_course), :class => "homepageMenuSetting fr", :title => "新建课程"%> +
    +
      +
    • +
        +
      • + <%= link_to "新建课程", new_course_path(:host=> Setting.host_course), :class => "menuGrey"%> +
      • + +
      • + <%= link_to "加入课程",join_private_courses_courses_path,:remote => true,:class => "menuGrey",:method => "post"%> +
      • +
      +
    • +
    +
    <% else%> <%=link_to "", join_private_courses_courses_path, :class => "homepageMenuSetting fr",:remote => true, :title => "加入课程"%> <% end%> @@ -201,5 +215,13 @@
    + diff --git a/public/stylesheets/new_user.css b/public/stylesheets/new_user.css index 3b79af2bb..29d3397c6 100644 --- a/public/stylesheets/new_user.css +++ b/public/stylesheets/new_user.css @@ -696,6 +696,11 @@ a:hover.gz_btn{ color:#ff5722;} .lh18 {line-height: 18px;} .maxh360 {max-height: 360px;} +.courseMenu {width:30px; display:block; float:right;height: 50px;} +.courseMenuIcon {display:inline-block; background:url(../images/homepage_icon2.png) -190px -365px no-repeat; width:15px; height:15px; margin-top: 16px; margin-right: 15px; position: relative;line-height:0;} +.topnav_course_menu{display: none; border:1px solid #eaeaea; background:#fff; padding-left:10px; padding-bottom:10px; padding-top:8px; width:60px; left:-7px; position:absolute; z-index:9999; line-height:2; box-shadow: 0px 2px 8px rgba(146, 153, 169, 0.5); margin-top: 20px;} +.topnav_course_menu a{color:#269ac9;} + /*课程主页css*/ .homepageCoursesType {width:75px; background-color:#ffffff; float:left; list-style:none; position:absolute; border:1px solid #eaeaea; border-radius:5px; top:15px; padding:5px 10px; left:-65px; font-size:12px; color:#4b4b4b; line-height:2; z-index:9999; display:none;} From d509ef01e83ed4fe03cf53f18556853751ed8007 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Mon, 26 Oct 2015 13:26:49 +0800 Subject: [PATCH 066/169] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E5=8A=A0=E5=85=A5=E9=A1=B9=E7=9B=AE=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E5=8F=8D=E9=A6=88=E7=9A=84=E6=B6=88=E6=81=AF=E7=9A=84?= =?UTF-8?q?=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/application_controller.rb | 5 +++++ app/controllers/courses_controller.rb | 4 ++++ app/controllers/users_controller.rb | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 86c220bd2..7632cf591 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -381,6 +381,11 @@ class ApplicationController < ActionController::Base if allowed true else + if params[:action] == 'show' + #更新申请结果反馈消息的状态 + messages = CourseMessage.where("course_message_type =? and course_id =? and user_id =? and viewed =?", 'CourseRequestDealResult', @course.id, User.current.id, false) + messages.update_all(:viewed => true) + end if @course && @course.archived? render_403 :message => :notice_not_authorized_archived_project else diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 56252253a..042311961 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -617,6 +617,10 @@ class CoursesController < ApplicationController create_course_messages = @course.course_messages.where("user_id =? and course_message_type =? and course_id =? and viewed =?", User.current.id, 'Course', @course.id, 0) create_course_messages.update_all(:viewed => true) + #更新申请结果反馈消息的状态 + course_request_messages = CourseMessage.where("user_id =? and course_id =? and course_message_type =? and viewed =?", User.current.id, @course.id, 'CourseRequestDealResult', false) + course_request_messages.update_all(:viewed => true) + course_activities = @course.course_activities @canShowRealName = User.current.member_of_course? @course @page = params[:page] ? params[:page].to_i + 1 : 0 diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2ffc7338e..e2edcb44a 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -919,7 +919,8 @@ class UsersController < ApplicationController def show #更新用户申请成为课程老师或教辅消息的状态 - join_course_messages = CourseMessage.where("course_message_type =? and course_message_id =? and viewed =?", 'JoinCourseRequest', @user.id, false) + join_course_messages = CourseMessage.where("course_id =? and course_message_type =? and user_id =? and course_message_id =? and viewed =?", + params[:course_id], 'JoinCourseRequest', User.current.id, @user.id, false) join_course_messages.update_all(:viewed => true) @page = params[:page] ? params[:page].to_i + 1 : 0 user_project_ids = @user.projects.visible.empty? ? "(-1)" : "(" + @user.projects.visible.map{|project| project.id}.join(",") + ")" From 5fb1291bf8b15c84e7fb5ee6a4c96d6039dc3721 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Mon, 26 Oct 2015 14:15:23 +0800 Subject: [PATCH 067/169] =?UTF-8?q?=E6=B6=88=E6=81=AF=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/users_controller.rb | 2 +- app/views/users/_user_message_course.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 e2edcb44a..2a646f5ce 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -919,7 +919,7 @@ class UsersController < ApplicationController def show #更新用户申请成为课程老师或教辅消息的状态 - join_course_messages = CourseMessage.where("course_id =? and course_message_type =? and user_id =? and course_message_id =? and viewed =?", + join_course_messages = CourseMessage.where("course_id =? and course_message_type =? and u ser_id =? and course_message_id =? and viewed =?", params[:course_id], 'JoinCourseRequest', User.current.id, @user.id, false) join_course_messages.update_all(:viewed => true) @page = params[:page] ? params[:page].to_i + 1 : 0 diff --git a/app/views/users/_user_message_course.html.erb b/app/views/users/_user_message_course.html.erb index d7042b6c2..2398fd8c5 100644 --- a/app/views/users/_user_message_course.html.erb +++ b/app/views/users/_user_message_course.html.erb @@ -442,7 +442,7 @@ ">您有了新的课程成员申请:
  • - <%= link_to User.find(ma.course_message_id).name+"申请成为课程\""+"#{Course.find(ma.course_id).name}"+"\"的"+"#{ma.content == '9' ? "教师" : "教辅"}", user_path(User.find(ma.course_message_id)), + <%= link_to User.find(ma.course_message_id).name+"申请成为课程\""+"#{Course.find(ma.course_id).name}"+"\"的"+"#{ma.content == '9' ? "教师" : "教辅"}", user_path(User.find(ma.course_message_id), :course_id => ma.course_id), :class => "#{ma.viewed==0 ? "newsBlack" : "newsGrey"}", :onmouseover => "message_titile_show($(this),event)", :onmouseout => "message_titile_hide($(this))" %> From 5f5a235e4bc13c61271105104e5fbae007d13e49 Mon Sep 17 00:00:00 2001 From: cxt Date: Mon, 26 Oct 2015 14:35:42 +0800 Subject: [PATCH 068/169] =?UTF-8?q?=E7=82=B9=E5=87=BB=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=E8=AF=BE=E7=A8=8B=E5=90=8E=E9=A6=96=E9=A1=B5=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E5=8F=91=E7=94=9F=E5=8F=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/_join_private_course.html.erb | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/app/views/courses/_join_private_course.html.erb b/app/views/courses/_join_private_course.html.erb index 4122a7105..5748d981e 100644 --- a/app/views/courses/_join_private_course.html.erb +++ b/app/views/courses/_join_private_course.html.erb @@ -4,33 +4,33 @@ 快速进入课程通道 @@ -33,7 +40,7 @@ <%= text_field_tag 'course_password', nil, :style=>'width:300px;'%>
    - + 确  定 From 2cf1f050220d9d06d4bd6f3a48a7b9dd351534d1 Mon Sep 17 00:00:00 2001 From: huang Date: Tue, 27 Oct 2015 16:32:25 +0800 Subject: [PATCH 091/169] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/repositories/_dir_list.html.erb | 13 +------------ public/stylesheets/project.css | 4 ++-- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/app/views/repositories/_dir_list.html.erb b/app/views/repositories/_dir_list.html.erb index 26ba05bb6..771d4d1ec 100644 --- a/app/views/repositories/_dir_list.html.erb +++ b/app/views/repositories/_dir_list.html.erb @@ -1,17 +1,6 @@
    - - - - -<% if @repository.report_last_commit %> - - - - -<% end %> - - + <%= render :partial => 'dir_list_content' %> diff --git a/public/stylesheets/project.css b/public/stylesheets/project.css index 3672b8238..e769e14e4 100644 --- a/public/stylesheets/project.css +++ b/public/stylesheets/project.css @@ -815,8 +815,8 @@ div.changeset { border-bottom: 1px solid #ddd; } /*****项目版本库文件 Tables *****/ -.autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;} -tr.entry { border: 1px solid #f8f8f8; } +.autoscroll {overflow-x: auto; margin-bottom: 0.2em;} +tr.entry { border: 1px solid #DDD; } tr.entry td { white-space: nowrap; } tr.entry td.filename { width: 30%; } tr.entry td.filename_no_report { width: 70%; } From 00ce480d52f0e73b738e891cbf0a0682edccc1f4 Mon Sep 17 00:00:00 2001 From: huang Date: Tue, 27 Oct 2015 17:26:43 +0800 Subject: [PATCH 092/169] =?UTF-8?q?=E6=9F=A5=E7=9C=8B=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E5=BC=BA=E5=88=B6=E4=B8=8D=E6=8D=A2=E8=A1=8C=20?= =?UTF-8?q?=E9=BC=A0=E6=A0=87=E5=85=89=E6=A0=87=E6=B8=B8=E8=B5=B0=E5=B1=8F?= =?UTF-8?q?=E5=B9=95=E8=B7=9F=E8=B5=B0=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/common/_file.html.erb | 2 +- public/stylesheets/repository.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/common/_file.html.erb b/app/views/common/_file.html.erb index d4c0875c2..97443beea 100644 --- a/app/views/common/_file.html.erb +++ b/app/views/common/_file.html.erb @@ -8,7 +8,7 @@ <%= line_num %> <% line_num += 1 %> diff --git a/public/stylesheets/repository.css b/public/stylesheets/repository.css index 3b8027d74..375b188d3 100644 --- a/public/stylesheets/repository.css +++ b/public/stylesheets/repository.css @@ -206,7 +206,7 @@ table.list th, table.list td {border: 1px solid #aaa;} } -.cloneUrl {width:235px; height:21px; border:1px solid #dddddd; outline:none; overflow:hidden; line-height:21px; resize:none;} +.cloneUrl {width:235px; height:21px; border:1px solid #dddddd; outline:none; overflow:hidden; line-height:21px; resize:none;white-space:nowrap;} .clone_btn {width:30px; height:21px; border-top:1px solid #dddddd; border-bottom:1px solid #dddddd; border-right:1px solid #dddddd; outline:none; float:left; background-image:linear-gradient(#FCFCFC, #EEE); text-align:center;} .vl_btn {height:21px; padding:0px 5px; vertical-align:middle; border:1px solid #dddddd; float:left; line-height:21px; background-image:linear-gradient(#FCFCFC, #EEE);} .vl_btn_2 {height:21px; padding:0px 5px; vertical-align:middle; border-top:1px solid #dddddd; border-bottom:1px solid #dddddd; border-right:1px solid #dddddd; float:left; line-height:21px;} From 87b1790f043badeb3d7a22f5dfdf433b04ff9ccf Mon Sep 17 00:00:00 2001 From: huang Date: Wed, 28 Oct 2015 09:23:12 +0800 Subject: [PATCH 093/169] =?UTF-8?q?=E6=9C=AA=E5=AE=8C=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E8=83=BDJS=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/repositories/show.html.erb | 4 ++-- public/javascripts/project.js | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index 8eb52197b..a1dd44cb7 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -25,8 +25,8 @@
    克隆网址:
    - - + +
    Fork 0
    <% if @changesets && !@changesets.empty? %> diff --git a/public/javascripts/project.js b/public/javascripts/project.js index d9138901a..5ba7c7145 100644 --- a/public/javascripts/project.js +++ b/public/javascripts/project.js @@ -516,4 +516,8 @@ function jsCopy(){ var e=document.getElementById("copy_rep_content"); e.select(); document.execCommand("Copy"); +} + +function zip(){ + alert("该功能正在紧张的开发中,我们会争取在最短时间内上线,如若对您工作造成不便敬请谅解!") } \ No newline at end of file From b1284cbf5508d0bab08044bcae8d296d3388559a Mon Sep 17 00:00:00 2001 From: cxt Date: Wed, 28 Oct 2015 10:17:45 +0800 Subject: [PATCH 094/169] =?UTF-8?q?=E4=B8=AA=E4=BA=BA=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E5=92=8C=E8=AF=BE=E7=A8=8B=E5=8A=A8=E6=80=81=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E4=BD=9C=E4=B8=9A=E6=B7=BB=E5=8A=A0=E9=85=8D=E7=BD=AE=E8=8F=9C?= =?UTF-8?q?=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/homework_common_controller.rb | 7 +- app/controllers/student_work_controller.rb | 16 ++-- .../_set_evalutation_att.html.erb | 4 +- .../homework_common/score_rule_set.js.erb | 2 +- .../start_evaluation_set.js.erb | 6 +- .../student_work/_set_score_rule.html.erb | 75 +++---------------- .../_set_score_rule_detail.html.erb | 61 +++++++++++++++ app/views/student_work/set_score_rule.js.erb | 8 ++ app/views/users/_course_homework.html.erb | 17 +++-- .../users/_user_homework_detail.html.erb | 2 +- app/views/users/show.html.erb | 1 + 11 files changed, 112 insertions(+), 87 deletions(-) create mode 100644 app/views/student_work/_set_score_rule_detail.html.erb create mode 100644 app/views/student_work/set_score_rule.js.erb diff --git a/app/controllers/homework_common_controller.rb b/app/controllers/homework_common_controller.rb index 7793ef097..55807886b 100644 --- a/app/controllers/homework_common_controller.rb +++ b/app/controllers/homework_common_controller.rb @@ -217,7 +217,12 @@ class HomeworkCommonController < ApplicationController #评分设置 def score_rule_set - + if params[:user_activity_id] + @user_activity_id = params[:user_activity_id] + else + @user_activity_id = -1 + end + @is_in_course = params[:is_in_course] end private diff --git a/app/controllers/student_work_controller.rb b/app/controllers/student_work_controller.rb index 529c5ea72..2f828669c 100644 --- a/app/controllers/student_work_controller.rb +++ b/app/controllers/student_work_controller.rb @@ -456,14 +456,14 @@ class StudentWorkController < ApplicationController student_work.save end end - respond_to do |format| - format.html{ - if params[:student_path] - redirect_to student_work_index_url(:homework => @homework.id) - else - redirect_to user_homeworks_user_path(User.current.id) - end - } + if params[:student_path] + redirect_to student_work_index_url(:homework => @homework.id) + else + @user_activity_id = params[:user_activity_id] + @is_in_course = params[:is_in_course] + respond_to do |format| + format.js + end end end diff --git a/app/views/homework_common/_set_evalutation_att.html.erb b/app/views/homework_common/_set_evalutation_att.html.erb index 01d9351ee..d51713e9a 100644 --- a/app/views/homework_common/_set_evalutation_att.html.erb +++ b/app/views/homework_common/_set_evalutation_att.html.erb @@ -7,7 +7,7 @@ 开启匿评
    - <%= calendar_for('evaluation_start_time')%> + <%#= calendar_for('evaluation_start_time')%>

    @@ -18,7 +18,7 @@ 关闭匿评
    - <%= calendar_for('evaluation_end_time')%> + <%#= calendar_for('evaluation_end_time')%>

    diff --git a/app/views/homework_common/score_rule_set.js.erb b/app/views/homework_common/score_rule_set.js.erb index 5ff42dff7..a3afb0c9a 100644 --- a/app/views/homework_common/score_rule_set.js.erb +++ b/app/views/homework_common/score_rule_set.js.erb @@ -1,4 +1,4 @@ -$('#ajax-modal').html('<%= escape_javascript(render :partial => 'student_work/set_score_rule',:locals => {:homework => @homework, :student_path => false}) %>'); +$('#ajax-modal').html('<%= escape_javascript(render :partial => 'student_work/set_score_rule',:locals => {:homework => @homework, :student_path => false, :user_activity_id => @user_activity_id,:is_in_course => @is_in_course,:remote=>true}) %>'); showModal('ajax-modal', '350px'); $('#ajax-modal').siblings().remove(); $('#ajax-modal').before("" + diff --git a/app/views/homework_common/start_evaluation_set.js.erb b/app/views/homework_common/start_evaluation_set.js.erb index 9d494f908..aac4ecd66 100644 --- a/app/views/homework_common/start_evaluation_set.js.erb +++ b/app/views/homework_common/start_evaluation_set.js.erb @@ -1,6 +1,10 @@ $('#ajax-modal').html('<%= escape_javascript(render :partial => 'homework_common/set_evalutation_att') %>'); +var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: 0, showOn: 'button', buttonImageOnly: true, buttonImage: '/images/public_icon.png', showButtonPanel: true, showWeek: true, showOtherMonths: true, selectOtherMonths: true}; showModal('ajax-modal', '350px'); $('#ajax-modal').siblings().remove(); $('#ajax-modal').before("" + ""); -$('#ajax-modal').parent().css("top","25%").css("left","35%").css("position","fixed"); \ No newline at end of file +$('#ajax-modal').parent().css("top","25%").css("left","35%").css("position","fixed"); +$(function() { $('#evaluation_start_time').datepicker(datepickerOptions); + $('#evaluation_end_time').datepicker(datepickerOptions); +}); \ No newline at end of file diff --git a/app/views/student_work/_set_score_rule.html.erb b/app/views/student_work/_set_score_rule.html.erb index 508b89a1c..26e3b06cb 100644 --- a/app/views/student_work/_set_score_rule.html.erb +++ b/app/views/student_work/_set_score_rule.html.erb @@ -1,66 +1,9 @@ -<%= form_for('new_form',:url => {:controller => 'student_work',:action => 'set_score_rule',:homework => homework.id},:method => "post") do |f|%> - <% if student_path %> - <%=hidden_field_tag 'student_path', params[:student_path], :value => student_path %> - <% end %> -
    - 评分设置 -
    - 迟交扣分 - -
    -
    - 缺评扣分 - -
    - - <% if homework.homework_type == 2%> -
    - 系统评分 - <%= select_tag :sy_proportion,options_for_select(ta_proportion_option,homework.homework_detail_programing.ta_proportion), {:class => "markPercentage"} %> -
    - - - <% else%> - - <% end%> - -
    - 教辅评分 - <%= select_tag :ta_proportion,options_for_select(ta_proportion_option_to(100-(homework.homework_detail_programing ? homework.homework_detail_programing.ta_proportion * 100 : 0).to_i),homework.homework_detail_manual.ta_proportion), {:class => "markPercentage"} %> -
    -
    - 学生匿评 - -
    -
    - 教师优先 - /> - 教师评分为最终评分 -
    -
    -
    - 确定 -
    -
    - 取消 -
    -
    -
    -
    -<% end%> +<% if student_path%> + <%= form_for('new_form',:url => {:controller => 'student_work',:action => 'set_score_rule',:homework => homework.id,:student_path => student_path},:method => "post") do |f|%> + <% render :partial => 'student_work/set_score_rule_detail', :locals => {:homework => homework, :f => f}%> + <% end%> +<% else %> + <%= form_for('new_form',:url => {:controller => 'student_work',:action => 'set_score_rule',:homework => homework.id,:user_activity_id=>user_activity_id,:is_in_course=>is_in_course},:method => "post",:remote => true) do |f|%> + <% render :partial => 'student_work/set_score_rule_detail', :locals => {:homework => homework, :f => f}%> + <% end%> +<% end %> \ No newline at end of file diff --git a/app/views/student_work/_set_score_rule_detail.html.erb b/app/views/student_work/_set_score_rule_detail.html.erb new file mode 100644 index 000000000..f49bb2c69 --- /dev/null +++ b/app/views/student_work/_set_score_rule_detail.html.erb @@ -0,0 +1,61 @@ +
    + 评分设置 +
    + 迟交扣分 + +
    +
    + 缺评扣分 + +
    + + <% if homework.homework_type == 2%> +
    + 系统评分 + <%= select_tag :sy_proportion,options_for_select(ta_proportion_option,homework.homework_detail_programing.ta_proportion), {:class => "markPercentage"} %> +
    + + + <% else%> + + <% end%> + +
    + 教辅评分 + <%= select_tag :ta_proportion,options_for_select(ta_proportion_option_to(100-(homework.homework_detail_programing ? homework.homework_detail_programing.ta_proportion * 100 : 0).to_i),homework.homework_detail_manual.ta_proportion), {:class => "markPercentage"} %> +
    +
    + 学生匿评 + +
    +
    + 教师优先 + /> + 教师评分为最终评分 +
    +
    +
    + 确定 +
    +
    + 取消 +
    +
    +
    +
    \ No newline at end of file diff --git a/app/views/student_work/set_score_rule.js.erb b/app/views/student_work/set_score_rule.js.erb new file mode 100644 index 000000000..ff3a0e7ed --- /dev/null +++ b/app/views/student_work/set_score_rule.js.erb @@ -0,0 +1,8 @@ +clickCanel(); +<% if @user_activity_id %> + $("#user_activity_<%= @user_activity_id%>").replaceWith("<%= escape_javascript(render :partial => 'users/course_homework', :locals => {:activity => @homework,:user_activity_id =>@user_activity_id}) %>"); + init_activity_KindEditor_data(<%= @user_activity_id%>,"","87%"); +<% else %> + $("#homework_common_<%= @homework.id %>").replaceWith("<%= escape_javascript(render :partial => 'users/user_homework_detail', :locals => {:homework_common => @homework,:is_in_course => @is_in_course}) %>"); + init_activity_KindEditor_data(<%= @homework.id%>,"","87%"); +<% end %> \ No newline at end of file diff --git a/app/views/users/_course_homework.html.erb b/app/views/users/_course_homework.html.erb index 70494192d..aa0344800 100644 --- a/app/views/users/_course_homework.html.erb +++ b/app/views/users/_course_homework.html.erb @@ -56,28 +56,31 @@ <%= render :partial => 'student_work/work_attachments', :locals => {:attachments => activity.attachments} %>
    - <%# if is_teacher%> - + <% end%>
    diff --git a/app/views/users/_user_homework_detail.html.erb b/app/views/users/_user_homework_detail.html.erb index 4570f365c..25f14ab31 100644 --- a/app/views/users/_user_homework_detail.html.erb +++ b/app/views/users/_user_homework_detail.html.erb @@ -69,7 +69,7 @@ <%= link_to(l(:label_bid_respond_delete), homework_common_path(homework_common,:is_in_course => is_in_course),:method => 'delete', :confirm => l(:text_are_you_sure), :class => "postOptionLink") %>
  • - <%= link_to("评分设置", score_rule_set_homework_common_path(homework_common),:class => "postOptionLink", :remote => true) %> + <%= link_to("评分设置", score_rule_set_homework_common_path(homework_common, :is_in_course => is_in_course),:class => "postOptionLink", :remote => true) %>
  • <%= link_to("匿评设置", start_evaluation_set_homework_common_path(homework_common),:class => "postOptionLink", :remote => true) if homework_common.homework_detail_manual.comment_status == 1%> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index fc87bd3a0..2805c4379 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,3 +1,4 @@ +
    最新动态
    From bd350536eb9911c2cd0120677f01e82177ee6c28 Mon Sep 17 00:00:00 2001 From: cxt Date: Wed, 28 Oct 2015 10:30:31 +0800 Subject: [PATCH 095/169] =?UTF-8?q?=E5=BC=B9=E5=87=BA=E2=80=9C=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E8=AF=BE=E7=A8=8B=E2=80=9D=E5=AF=B9=E8=AF=9D=E6=A1=86?= =?UTF-8?q?=E5=90=8E=E9=9A=90=E8=97=8F"=E5=8A=A0=E5=85=A5/=E6=96=B0?= =?UTF-8?q?=E5=BB=BA=E8=AF=BE=E7=A8=8B"=E7=9A=84=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/courses/join_private_courses.js.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/courses/join_private_courses.js.erb b/app/views/courses/join_private_courses.js.erb index 8a1680030..0c44770bd 100644 --- a/app/views/courses/join_private_courses.js.erb +++ b/app/views/courses/join_private_courses.js.erb @@ -1,3 +1,4 @@ +$('#topnav_course_menu').hide(); $('#ajax-modal').html('<%= escape_javascript(render :partial => 'join_private_course') %>'); showModal('ajax-modal', '540px'); $('#ajax-modal').css('height','390px'); From 788e900d4afa91882fa9801116c70fc7dab0d50f Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Wed, 28 Oct 2015 10:58:37 +0800 Subject: [PATCH 096/169] =?UTF-8?q?=E4=B8=AA=E4=BA=BA=E5=8D=9A=E5=AE=A2?= =?UTF-8?q?=E5=8F=91=E5=8D=9A=E6=96=87=E4=B9=9F=E8=A6=81=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E5=8A=A8=E6=80=81=20=E4=B8=AA=E4=BA=BA=E5=8D=9A=E5=AE=A2=20?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E8=AE=BE=E7=BD=AE=E8=8F=9C=E5=8D=95=E8=A6=81?= =?UTF-8?q?=E5=87=BA=E7=8E=B0=E5=9C=A8=E5=8D=9A=E5=AE=A2=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E7=9A=84=E5=8F=B3=E4=B8=8A=E6=96=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/blog_comments_controller.rb | 3 + app/controllers/users_controller.rb | 13 ++- app/models/blog_comment.rb | 28 ++++- app/views/blog_comments/reply.js.erb | 7 +- app/views/blogs/_article.html.erb | 33 +++++- app/views/users/_user_activities.html.erb | 7 ++ app/views/users/_user_blog.html.erb | 119 ++++++++++++++++++++ 7 files changed, 203 insertions(+), 7 deletions(-) create mode 100644 app/views/users/_user_blog.html.erb diff --git a/app/controllers/blog_comments_controller.rb b/app/controllers/blog_comments_controller.rb index 95790a68d..f9cb3c3ad 100644 --- a/app/controllers/blog_comments_controller.rb +++ b/app/controllers/blog_comments_controller.rb @@ -89,6 +89,9 @@ class BlogCommentsController < ApplicationController #回复 def reply + if params[:in_user_center] + @in_user_center = true + end @article = BlogComment.find(params[:id]).root @quote = params[:quote][:quote] @blogComment = BlogComment.new diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6d049783e..175c8ce51 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -949,10 +949,19 @@ class UsersController < ApplicationController when "current_user" @user_activities = UserActivity.where("user_id = #{@user.id} and ((container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types}) or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types}))").order('updated_at desc').limit(10).offset(@page * 10) else - @user_activities = UserActivity.where("(container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types}) or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types}) or (container_type = 'Principal' and act_type= '#{principal_types}' and container_id = #{@user.id})").order('updated_at desc').limit(10).offset(@page * 10) + blog_ids = "("+@user.blog.id.to_s+","+User.watched_by(@user.id).map{|u| u.blog.id}.join(',')+")" + @user_activities = UserActivity.where("(container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types})" + + "or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types}) "+ + "or (container_type = 'Principal' and act_type= '#{principal_types}' and container_id = #{@user.id}) " + + "or (container_type = 'Blog' and act_type= 'BlogComment' and container_id in #{blog_ids})").order('updated_at desc').limit(10).offset(@page * 10) end else - @user_activities = UserActivity.where("(container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types}) or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types})or (container_type = 'Principal' and act_type= '#{principal_types}' and container_id = #{@user.id})").order('updated_at desc').limit(10).offset(@page * 10) + # @user_activities = UserActivity.where("(container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types}) or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types})or (container_type = 'Principal' and act_type= '#{principal_types}' and container_id = #{@user.id})").order('updated_at desc').limit(10).offset(@page * 10) + blog_ids = "("+@user.blog.id.to_s+","+User.watched_by(@user.id).map{|u| u.blog.id}.join(',')+")" + @user_activities = UserActivity.where("(container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types})" + + "or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types}) "+ + "or (container_type = 'Principal' and act_type= '#{principal_types}' and container_id = #{@user.id}) " + + "or (container_type = 'Blog' and act_type= 'BlogComment' and container_id in #{blog_ids})").order('updated_at desc').limit(10).offset(@page * 10) end # @user_activities = paginateHelper @user_activities,500 @type = params[:type] diff --git a/app/models/blog_comment.rb b/app/models/blog_comment.rb index 92970b663..9bb28ddd8 100644 --- a/app/models/blog_comment.rb +++ b/app/models/blog_comment.rb @@ -7,7 +7,8 @@ class BlogComment < ActiveRecord::Base acts_as_tree :counter_cache => :comments_count, :order => "#{BlogComment.table_name}.sticky desc ,#{BlogComment.table_name}.created_on ASC" acts_as_attachable belongs_to :last_reply, :class_name => 'BlogComment', :foreign_key => 'last_comment_id' - + # 虚拟关联 + has_many :user_acts, :class_name => 'UserAcivity',:as =>:act acts_as_watchable validates_presence_of :title, :content @@ -15,6 +16,31 @@ class BlogComment < ActiveRecord::Base #validate :cannot_reply_to_locked_comment, :on => :create safe_attributes 'title', 'content',"sticky", "locked" + after_save :add_user_activity + before_destroy :destroy_user_activity + + #在个人动态里面增加当前动态 + def add_user_activity + if self.parent_id.nil? #只有发博文才插入动态 + user_activity = UserActivity.where("act_type = '#{self.class.to_s}' and act_id = '#{self.id}'").first + if user_activity + user_activity.save + else + user_activity = UserActivity.new + user_activity.act_id = self.id + user_activity.act_type = self.class.to_s + user_activity.container_type = "Blog" + user_activity.container_id = self.blog_id + user_activity.user_id = self.author_id + user_activity.save + end + end + end + + def destroy_user_activity + user_activity = UserActivity.where("act_type = '#{self.class.to_s}' and act_id = '#{self.id}'") + user_activity.destroy_all + end def deleted_attach_able_by? user (user && user.logged? && (self.author == user) ) || user.admin? end diff --git a/app/views/blog_comments/reply.js.erb b/app/views/blog_comments/reply.js.erb index 7ebe4d077..f8ed4bb24 100644 --- a/app/views/blog_comments/reply.js.erb +++ b/app/views/blog_comments/reply.js.erb @@ -1,2 +1,7 @@ +<% if @in_user_center%> + $("#user_activity_<%= @user_activity_id%>").replaceWith("<%= escape_javascript(render :partial => 'users/user_blog', :locals => {:activity => @article,:user_activity_id =>@user_activity_id}) %>"); + init_activity_KindEditor_data(<%= @user_activity_id%>,"","87%"); +<% else%> $("#user_activity_<%= @user_activity_id%>").replaceWith("<%= escape_javascript(render :partial => 'blogs/article', :locals => {:activity => @article,:user_activity_id =>@user_activity_id,:first_user_activity =>@first_user_activity,:page => @page}) %>"); -init_activity_KindEditor_data(<%= @user_activity_id%>,"","87%"); \ No newline at end of file +init_activity_KindEditor_data(<%= @user_activity_id%>,"","87%"); +<% end %> \ No newline at end of file diff --git a/app/views/blogs/_article.html.erb b/app/views/blogs/_article.html.erb index 943d21852..33a3201c0 100644 --- a/app/views/blogs/_article.html.erb +++ b/app/views/blogs/_article.html.erb @@ -1,10 +1,36 @@ -
    -
    +
    +
    <%= link_to image_tag(url_to_avatar(activity.author), :width => "50", :height => "50"), user_path(activity.author_id,:host=>Setting.host_user), :alt => "用户头像" %>
    -
    + <% if activity.author.id == User.current.id%> + + <%end%> +
    <% if activity.try(:author).try(:realname) == ' ' %> <%= link_to activity.try(:author), user_path(activity.author_id,:host=>Setting.host_user), :class => "newsBlue mr15" %> <% else %> @@ -13,6 +39,7 @@ TO <%= link_to activity.blog.name+" | 博客", user_blogs_path(:user_id=>activity.author_id,:host=>Setting.host_user), :class => "newsBlue ml15 mr5"%>
    +
    From 1a2edd91035538781ad98837cdc3037319bf36b2 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 28 Oct 2015 17:34:41 +0800 Subject: [PATCH 104/169] =?UTF-8?q?=E8=AF=BE=E7=A8=8B=E5=A4=A7=E7=BA=B2?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E7=AD=89=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/layouts/base_courses.html.erb | 7 ++++--- public/images/course/syllabus.png | Bin 0 -> 2298 bytes public/images/course/syllabus_icon.png | Bin 0 -> 1584 bytes public/images/course/syllabus_setting.png | Bin 0 -> 2023 bytes public/stylesheets/courses.css | 5 +++++ public/stylesheets/new_user.css | 1 + public/stylesheets/public.css | 1 + 7 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 public/images/course/syllabus.png create mode 100644 public/images/course/syllabus_icon.png create mode 100644 public/images/course/syllabus_setting.png diff --git a/app/views/layouts/base_courses.html.erb b/app/views/layouts/base_courses.html.erb index 9b1dfbb7f..04d16a334 100644 --- a/app/views/layouts/base_courses.html.erb +++ b/app/views/layouts/base_courses.html.erb @@ -54,15 +54,16 @@
    -
    - +
    + <%= @course.name %> <% if @course.is_public == 0%> - + <%= l(:field_is_private)%> <% end %> +
    diff --git a/public/images/course/syllabus.png b/public/images/course/syllabus.png new file mode 100644 index 0000000000000000000000000000000000000000..32c4bc4cc6e08e9a62212e9471e34ac117f4fb33 GIT binary patch literal 2298 zcmaJ@eLRzUAD)B~b+AWFyKZWzu#IzeS|&? z1~X*%&;p^+2>Oroxq>YPVHB=75(F4Rb~G3WvbhPd?Vu|R zrj6tUZk@MY< zea$*krSXrPQE z3A{Ha}buj@i=`oQztnKuRUG0NuJc>hIUMM zZb*%6+l*#y@39l-iAi08NWX@iC5u_51VT5g{q6i8=RsNeqe3hr2p$8{fxb9 z4yNp17#E!~L$zR))*T61H*HdpKmOq1>tfSsYMxV@WnDo>bYb)wyWf~OS}#pj*e4h8 zq-RUKZ3WXe@7X1W#zSq4KTHZc2@fZq>0Vo;*d+-o=K{!O*yUw0NaA_10pfeD$<;SduJ9C& zTV9(p;cZ{g*=@4e_`&_^$_dx%2)#w9X@e@W?*8db>W6LhAf`WZ#hFjeolE$)bzirv z*TjE`@Wiy zX4KC(-3<-!M!yC1;V5nGlI)j@I9GduF^y z4Y!`Km;IcftL=Y}HVz-3T*)$En4Vf_ed>X`cA!yulY4gi^QAU%Yt6gaoh)5DzbwR_ zQr|6`$pI>7V;0}j{>O$eqjY*tmFZwn!uc(DuDq`q$q!@m>+jU7o?_#YO>7LoF-BI2%Lj^^^o+tqh6 z;*ajgmR&!bHHO5Y#=^A=N{0|d;mah|R{~Wjt?b4$^Vkqdo`4VV?K^VuxQlVdQA|X6PFDuE$NFJyrF}wKXI=VW$U^nUXZN+9<;>@N zxTt;JZpYT)!x=Ut)8V75#}BQ^zgM(GNQgezpF5SgU9U>-z`9o%oV^&v`kOS$kFO*V zLo1Kg?v?fK8ypQRNv)ObZ`K`jH}R=gPwqb8)qG(1kHh!4isr=d7 N2!rlNyWka(@)wBI!6N_w literal 0 HcmV?d00001 diff --git a/public/images/course/syllabus_icon.png b/public/images/course/syllabus_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2b8e17c46ef383c3c6485d29b7a134c33c067f78 GIT binary patch literal 1584 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 literal 0 HcmV?d00001 diff --git a/public/stylesheets/courses.css b/public/stylesheets/courses.css index d75b33c2f..0aaaff344 100644 --- a/public/stylesheets/courses.css +++ b/public/stylesheets/courses.css @@ -1089,3 +1089,8 @@ a.postRouteLink:hover {text-decoration:underline;} .ReplyToMessageContainer {border-bottom:1px solid #e3e3e3; width:632px; margin:0px auto; margin-top:15px; min-height:60px;} .ReplyToMessageInputContainer {width:582px; float:left;} + +/*课程大纲图标样式20151028Tim*/ +.syllabusIcon {background:url("/images/course/syllabus.png") 0px 0px no-repeat; width: 17px; height: 16px; display: inline-block;} +.syllabusSetting {background:url("/images/course/syllabus.png") 0px -16px no-repeat; width: 20px; height: 16px; display: inline-block;} + diff --git a/public/stylesheets/new_user.css b/public/stylesheets/new_user.css index b4ef37dbc..f753b191b 100644 --- a/public/stylesheets/new_user.css +++ b/public/stylesheets/new_user.css @@ -1309,3 +1309,4 @@ a:hover.link_file_a{ background:url(../images/pic_file.png) 0 -25px no-repeat; c .ul_normal_color li {list-style-position:inside; padding-left:1px; list-style-image:url('../images/news_dot.png')} span.author { font-size: 0.9em; color: #888; } .ReplyToMessageInputContainer {width: 582px;float: left;} + diff --git a/public/stylesheets/public.css b/public/stylesheets/public.css index df42f783e..d03ee1a74 100644 --- a/public/stylesheets/public.css +++ b/public/stylesheets/public.css @@ -907,3 +907,4 @@ a.resourcesTypeUser {background:url(images/homepage_icon.png) -178px -453px no-r .list_style ol li{list-style-type: decimal;margin-left: 20px;} .list_style ul li{list-style-type: disc;margin-left: 20px;} + From 0de97fb9efb736b883168db3d6cb8fa96ffa51a7 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Thu, 29 Oct 2015 09:04:04 +0800 Subject: [PATCH 105/169] =?UTF-8?q?=E5=A6=82=E6=9E=9C=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E5=85=B3=E6=B3=A8=E7=9A=84=E4=BA=BA=EF=BC=8Csql=E4=BC=9A?= =?UTF-8?q?=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/users_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 175c8ce51..7c41dd038 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -949,7 +949,7 @@ class UsersController < ApplicationController when "current_user" @user_activities = UserActivity.where("user_id = #{@user.id} and ((container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types}) or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types}))").order('updated_at desc').limit(10).offset(@page * 10) else - blog_ids = "("+@user.blog.id.to_s+","+User.watched_by(@user.id).map{|u| u.blog.id}.join(',')+")" + blog_ids = "("+@user.blog.id.to_s+","+((User.watched_by(@user.id).count == 0 )? '0' :User.watched_by(@user.id).map{|u| u.blog.id}.join(','))+")" @user_activities = UserActivity.where("(container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types})" + "or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types}) "+ "or (container_type = 'Principal' and act_type= '#{principal_types}' and container_id = #{@user.id}) " + @@ -957,7 +957,7 @@ class UsersController < ApplicationController end else # @user_activities = UserActivity.where("(container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types}) or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types})or (container_type = 'Principal' and act_type= '#{principal_types}' and container_id = #{@user.id})").order('updated_at desc').limit(10).offset(@page * 10) - blog_ids = "("+@user.blog.id.to_s+","+User.watched_by(@user.id).map{|u| u.blog.id}.join(',')+")" + blog_ids = "("+@user.blog.id.to_s+","+((User.watched_by(@user.id).count == 0 )? '0' :User.watched_by(@user.id).map{|u| u.blog.id}.join(','))+")" @user_activities = UserActivity.where("(container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types})" + "or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types}) "+ "or (container_type = 'Principal' and act_type= '#{principal_types}' and container_id = #{@user.id}) " + From b67afc2b9509e3e960a581f770c289d352b32289 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Thu, 29 Oct 2015 09:07:26 +0800 Subject: [PATCH 106/169] =?UTF-8?q?=E5=A4=A7=E7=BA=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/_course_outlines_list.html.erb | 56 +++---------------- app/views/courses/course_outline.js.erb | 9 +-- app/views/layouts/base_courses.html.erb | 24 ++++++++ config/routes.rb | 1 + public/javascripts/course.js | 7 ++- public/stylesheets/courses.css | 9 +-- 6 files changed, 49 insertions(+), 57 deletions(-) diff --git a/app/views/courses/_course_outlines_list.html.erb b/app/views/courses/_course_outlines_list.html.erb index efadbede9..cb7c52cfb 100644 --- a/app/views/courses/_course_outlines_list.html.erb +++ b/app/views/courses/_course_outlines_list.html.erb @@ -1,14 +1,15 @@ -
    请选择课程大纲
    -
    +
    请选择课程大纲
    +
    - - + + +
    - 未搜索到对应大纲,请重新输入 -
    + +
    • @@ -16,48 +17,7 @@
    • 博客一
    发布时间:2015-05-11
    -
      -
    • - -
    • -
    • 高等数学-14学期
    • -
    -
    发布时间:2014-05-11
    -
      -
    • - -
    • -
    • 博客二
    • -
    -
    发布时间:2014-03-04
    -
      -
    • - -
    • -
    • 高等数学-13学期
    • -
    -
    发布时间:2013-05-11
    -
      -
    • - -
    • -
    • 博客三-浅谈前段开发中所应用到的最新侧导航样式设计30案例
    • -
    -
    发布时间:2013-05-10
    -
      -
    • - -
    • -
    • 博客四
    • -
    -
    发布时间:2013-05-08
    -
      -
    • - -
    • -
    • 博客五
    • -
    -
    发布时间:2013-05-01
    +
    diff --git a/app/views/courses/course_outline.js.erb b/app/views/courses/course_outline.js.erb index ea8f95c86..300ba6466 100644 --- a/app/views/courses/course_outline.js.erb +++ b/app/views/courses/course_outline.js.erb @@ -1,8 +1,9 @@ $('#ajax-modal').html('<%= escape_javascript(render :partial => 'course_outlines_list') %>'); showModal('ajax-modal', '300px'); -$('#ajax-modal').css('height','360px'); +//$('#ajax-modal').css('height','250px'); +$('#ajax-modal').css('padding-top','0px'); $('#ajax-modal').siblings().remove(); -$('#ajax-modal').before(""); -$('#ajax-modal').parent().css("top","40%").css("left","46%"); +$('#ajax-modal').before(' '); +$('#ajax-modal').parent().css("top","30%").css("left","50%"); $('#ajax-modal').parent().addClass("courseOutlinePopup"); -$('#ajax-modal').css("padding-left","16px").css("padding-bottom","16px"); \ No newline at end of file +$('#ajax-modal').css("padding-left","16px")//.css("padding-bottom","16px"); \ No newline at end of file diff --git a/app/views/layouts/base_courses.html.erb b/app/views/layouts/base_courses.html.erb index 4eb036bbf..4e9868efe 100644 --- a/app/views/layouts/base_courses.html.erb +++ b/app/views/layouts/base_courses.html.erb @@ -171,5 +171,29 @@ <%= call_hook :view_layouts_base_body_bottom %> + \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 6af87a299..bdbdea619 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -790,6 +790,7 @@ RedmineApp::Application.routes.draw do get 'copy_course' match 'course_activity', :to => 'courses#course_activity', :via => :get, :as => "course_activity" get 'course_outline' + post 'search_course_outline' end collection do match 'join_private_courses', :via => [:get, :post] diff --git a/public/javascripts/course.js b/public/javascripts/course.js index 921260a72..ddd323166 100644 --- a/public/javascripts/course.js +++ b/public/javascripts/course.js @@ -1229,4 +1229,9 @@ function course_outline(id){ $.get( ' /courses/'+id+'/course_outline' ) -} \ No newline at end of file +} +//$(function(){ +// $('#course_outline_search').on('input',function(){ +// alert('<%= @course.id%>') +// }) +//}) \ No newline at end of file diff --git a/public/stylesheets/courses.css b/public/stylesheets/courses.css index 39150ed73..9fe75db34 100644 --- a/public/stylesheets/courses.css +++ b/public/stylesheets/courses.css @@ -1092,9 +1092,10 @@ a.postRouteLink:hover {text-decoration:underline;} /*课程大纲弹框*/ .courseOutlinePopup {width:400px; height:auto; border:3px solid #269ac9; padding-left:16px; padding-bottom:16px; background-color:#ffffff; position:absolute; top:50%; left:50%; margin-left:-200px; z-index:1000;} -.courseOutlineSearchIcon{width:31px; height:25px; background-color:#ffffff; background:url(images/resource_icon_list.png) -40px -18px no-repeat; display:inline-block; float:left;} +.searchIconPopup{width:31px; height:25px; background-color:#ffffff; background:url(../images/homepage_icon.png) 5px -394px no-repeat; display:inline-block; float:left; cursor: pointer;} +.searchIconPopup:hover {background:url(../images/homepage_icon.png) 5px -420px no-repeat;} .blogTitle {max-width:240px; font-size:12px; color:#484848; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;} -.blogBlock {overflow-x:hidden; max-height:200px; overflow-y:auto; margin-bottom:5px;} +.blogBlock {overflow-x:hidden; max-height:200px;min-height: 200px; overflow-y:auto; margin-bottom:5px;} .blogRow {width:280px; height:15px; line-height:15px;} -.blogSearchBox {border:1px solid #e6e6e6; width:280px; height:25px; background-color:#ffffff; margin-top:8px; margin-bottom:8px;} -.blogSearchContent {border:none; outline:none; background-color:#ffffff; width:236px; height:25px; padding-left:10px; display:inline-block; float:left;} +.blogSearchBox {border:1px solid #e6e6e6; height:25px; background-color:#ffffff; margin-top:8px; margin-bottom:8px;}/*width:280px;*/ +.blogSearchContent {border:none; outline:none; background-color:#ffffff; width:216px; height:25px; padding-left:10px; display:inline-block; float:left;} From 4f64859b145f42fc17a50949a8e3227d7a3b7d3a Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Thu, 29 Oct 2015 09:11:13 +0800 Subject: [PATCH 107/169] =?UTF-8?q?=E6=B2=A1=E6=9C=89=E5=85=B3=E6=B3=A8?= =?UTF-8?q?=E7=9A=84=E4=BA=BA=EF=BC=8Csql=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/users_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 175c8ce51..7c41dd038 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -949,7 +949,7 @@ class UsersController < ApplicationController when "current_user" @user_activities = UserActivity.where("user_id = #{@user.id} and ((container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types}) or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types}))").order('updated_at desc').limit(10).offset(@page * 10) else - blog_ids = "("+@user.blog.id.to_s+","+User.watched_by(@user.id).map{|u| u.blog.id}.join(',')+")" + blog_ids = "("+@user.blog.id.to_s+","+((User.watched_by(@user.id).count == 0 )? '0' :User.watched_by(@user.id).map{|u| u.blog.id}.join(','))+")" @user_activities = UserActivity.where("(container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types})" + "or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types}) "+ "or (container_type = 'Principal' and act_type= '#{principal_types}' and container_id = #{@user.id}) " + @@ -957,7 +957,7 @@ class UsersController < ApplicationController end else # @user_activities = UserActivity.where("(container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types}) or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types})or (container_type = 'Principal' and act_type= '#{principal_types}' and container_id = #{@user.id})").order('updated_at desc').limit(10).offset(@page * 10) - blog_ids = "("+@user.blog.id.to_s+","+User.watched_by(@user.id).map{|u| u.blog.id}.join(',')+")" + blog_ids = "("+@user.blog.id.to_s+","+((User.watched_by(@user.id).count == 0 )? '0' :User.watched_by(@user.id).map{|u| u.blog.id}.join(','))+")" @user_activities = UserActivity.where("(container_type = 'Project' and container_id in #{user_project_ids} and act_type in #{project_types})" + "or (container_type = 'Course' and container_id in #{user_course_ids} and act_type in #{course_types}) "+ "or (container_type = 'Principal' and act_type= '#{principal_types}' and container_id = #{@user.id}) " + From c3a67a544bc6fc88d3eeeb59aff5202d559e1301 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Thu, 29 Oct 2015 09:23:04 +0800 Subject: [PATCH 108/169] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=B6=88=E6=81=AFlogo=E4=B8=BA=E7=BA=A2=E8=89=B2trustie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/_user_message_system.html.erb | 2 +- app/views/users/user_system_messages.html.erb | 2 +- public/images/trustie_logo1.png | Bin 0 -> 121085 bytes 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 public/images/trustie_logo1.png diff --git a/app/views/users/_user_message_system.html.erb b/app/views/users/_user_message_system.html.erb index efbd5d76b..e3037f227 100644 --- a/app/views/users/_user_message_system.html.erb +++ b/app/views/users/_user_message_system.html.erb @@ -2,7 +2,7 @@ <%# @user_system_messages.each do |usm| %>
    • - +
    • Trustie平台发布新消息: diff --git a/app/views/users/user_system_messages.html.erb b/app/views/users/user_system_messages.html.erb index 2905e1e7d..3c342117e 100644 --- a/app/views/users/user_system_messages.html.erb +++ b/app/views/users/user_system_messages.html.erb @@ -4,7 +4,7 @@
      - <%= image_tag("/images/logo.png", :width => "50", :height => "50") %> + <%= image_tag("/images/trustie_logo1.png", :width => "50", :height => "50") %>
      diff --git a/public/images/trustie_logo1.png b/public/images/trustie_logo1.png new file mode 100644 index 0000000000000000000000000000000000000000..d6c7fc8403b6b85acca42a7e989f55699f514020 GIT binary patch literal 121085 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 literal 0 HcmV?d00001 From f8931ea78f88d21b3d6dbc4d9ba4f7a57cf52e19 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Thu, 29 Oct 2015 09:36:36 +0800 Subject: [PATCH 109/169] =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=B6=88=E6=81=AF?= =?UTF-8?q?=EF=BC=8C=E5=B0=86=E6=88=AA=E6=AD=A2=E6=97=B6=E9=97=B4=E6=94=B9?= =?UTF-8?q?=E6=88=90=E5=8F=91=E5=B8=83=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/user_system_messages.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/users/user_system_messages.html.erb b/app/views/users/user_system_messages.html.erb index 3c342117e..4b1ce2c70 100644 --- a/app/views/users/user_system_messages.html.erb +++ b/app/views/users/user_system_messages.html.erb @@ -18,7 +18,7 @@

      -

      截止时间:<%= format_time(system_message.created_at) %>

      +

      发布时间:<%= format_time(system_message.created_at) %>

      From b11aaa545a84c613a7fd6052cf88352a4f49845e Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Thu, 29 Oct 2015 09:52:59 +0800 Subject: [PATCH 110/169] =?UTF-8?q?=E8=80=81=E5=B8=88=E5=B0=86=E8=AF=BE?= =?UTF-8?q?=E7=A8=8B=E6=88=90=E5=91=98=E6=8B=89=E5=85=A5=E6=88=96=E8=80=85?= =?UTF-8?q?=E7=A7=BB=E5=87=BA=E9=A1=B9=E7=9B=AE=E6=97=B6=EF=BC=8C=E7=BB=99?= =?UTF-8?q?=E5=87=BA=E7=9B=B8=E5=BA=94=E7=9A=84=E6=B6=88=E6=81=AF=E9=80=9A?= =?UTF-8?q?=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/members_controller.rb | 13 +++- app/views/users/_user_message_course.html.erb | 60 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index 6bb61e6a1..34e039ae0 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -159,6 +159,14 @@ class MembersController < ApplicationController if role && (role.name == "学生" || role.name == "Student") StudentsForCourse.create(:student_id => user_id, :course_id =>@course.id) end + + #给新成员和老师发送加入课程的消息,发送者id放在CourseMessage的course_message_id字段中 + #course_message_type设置为JoinCourse + #status = 0 表示给学生发,status = 1表示给老师发 + course_join = CourseMessage.new(:user_id =>user_id, :course_message_id=>User.current.id,:course_id => @course.id,:course_message_type=>"JoinCourse", :viewed => false, :status => 0) + course_join.save + CourseMessage.create(:user_id => User.current.id, :course_message_id => user_id, :course_id => @course.id, :course_message_type => "JoinCourse", :viewed => false, status => 1) + members << member #user_grades << UserGrade.new(:user_id => user_id, :course_id => @course.id) if (params[:membership][:role_ids]) @@ -305,7 +313,8 @@ class MembersController < ApplicationController grade.destroy end end - ForgeMessage.create(:user_id => @member.user_id, :project_id => @project.id, :forge_message_type => "RemoveFromProject", :viewed => false, :forge_message_id => User.current.id) + #移出项目发送消息 + ForgeMessage.create(:user_id => @member.user_id, :project_id => @project.id, :forge_message_type => "RemoveFromProject", :viewed => false, :forge_message_id => User.current.id) end respond_to do |format| format.html { redirect_to_settings_in_projects } @@ -333,6 +342,8 @@ class MembersController < ApplicationController end @roles = Role.givable.all[3..5] @members = @course.member_principals.includes(:roles, :principal).all.sort + #移出课程发送消息 + CourseMessage.create(:user_id => @member.user_id, :course_id => @course.id, :course_message_type => "RemoveFromCourse", :viewed => false, :course_message_id => User.current.id) end respond_to do |format| format.html { redirect_to_settings_in_courses } diff --git a/app/views/users/_user_message_course.html.erb b/app/views/users/_user_message_course.html.erb index afbeb5ed5..ed0147be8 100644 --- a/app/views/users/_user_message_course.html.erb +++ b/app/views/users/_user_message_course.html.erb @@ -503,4 +503,64 @@
    • <%= time_tag(ma.created_at).html_safe %>
    <% end %> + + + <% if ma.course_message_type == "JoinCourse" %> + + <% end %> + + + <% if ma.course_message_type == "RemoveFromCourse" %> + + <% end %> <% end %> \ No newline at end of file From 958d7819974b533e3dd5c300d124fed8c3b1a9ae Mon Sep 17 00:00:00 2001 From: cxt Date: Thu, 29 Oct 2015 10:43:01 +0800 Subject: [PATCH 111/169] =?UTF-8?q?=E4=BD=9C=E5=93=81=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E7=9A=84=E6=A0=B7=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/stylesheets/public.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/stylesheets/public.css b/public/stylesheets/public.css index d03ee1a74..0be7a6ff0 100644 --- a/public/stylesheets/public.css +++ b/public/stylesheets/public.css @@ -12,7 +12,7 @@ textarea {resize: none;} .pInline {margin:0px; padding:0px; display:inline-block;} /*常用*/ -select,input,textarea{ border:1px solid #269ac9; background:#fff; color:#000; padding-left:5px;padding-right: 5px; } +select,input,textarea{ border:1px solid #269ac9; background:#fff; color:#000; padding-left:5px} .sub_btn{ cursor:pointer; -moz-border-radius:3px; -webkit-border-radius:3px; border:1px solid #707070; color:#000; border-radius:3px; padding:1px 10px; margin-bottom:10px; background:#dbdbdb;} .sub_btn:hover{ background:#b5e2fa; color:#000; border:1px solid #3c7fb1;} table{ background:#fff;} From e1a7ebd95f54a327e2d9f466e5a2adac2c4a74d7 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Thu, 29 Oct 2015 10:50:19 +0800 Subject: [PATCH 112/169] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E8=AF=BE=E7=A8=8B=E6=B6=88=E6=81=AF=E6=8F=90=E7=A4=BA=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/_user_message_course.html.erb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/views/users/_user_message_course.html.erb b/app/views/users/_user_message_course.html.erb index ed0147be8..a6086b888 100644 --- a/app/views/users/_user_message_course.html.erb +++ b/app/views/users/_user_message_course.html.erb @@ -426,10 +426,16 @@

    课程名称:<%= ma.course_message.name %>

    开课学期:<%= ma.course_message.time.to_s + '年' + ma.course_message.term %>

    -
    课程描述:
    -
    <%= ma.course_message.description.html_safe %>
    -

    学时总数:<%= ma.course_message.class_period%>

    +

    课程ID:<%= ma.course_message.id %>

    +

    课程密码:<%= ma.course_message.password %>

    +

    学时总数:<%= ma.course_message.class_period %>

    创建时间:<%= format_time(ma.course_message.created_at) %>

    +

    您可以点击左上角的“配置”按钮,修改课程基本信息,添加及删除课程成员。您也可以把课程ID及密码告诉学生和其他成员,让他们输入ID及密码加入课程。

    + <% if ma.course_message.is_public %> +

    您的课程是公开的,所有人都能访问您的课程。若不想设置为公开,您可以在配置中设置。

    + <% else %> +

    您的课程是私有的,非课程成员不能访问您的课程。如果想设置为公开,您可以在配置中设置。

    + <% end %>
  • <%= time_tag(ma.created_at).html_safe %>
  • From ac82910aa5f30c6084270b75d8df2cf04cb033a3 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Thu, 29 Oct 2015 11:53:52 +0800 Subject: [PATCH 113/169] =?UTF-8?q?=E5=A4=A7=E7=BA=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/courses_controller.rb | 21 +++++++++++++++ app/models/blog_comment.rb | 9 +++++++ .../courses/_course_outlines_list.html.erb | 27 +++++++++++-------- app/views/courses/course_outline.js.erb | 2 +- app/views/courses/set_course_outline.js.erb | 1 + app/views/layouts/base_courses.html.erb | 27 ++++++++++++++++--- config/routes.rb | 1 + 7 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 app/views/courses/set_course_outline.js.erb diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 1113de159..1216599a1 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -713,7 +713,28 @@ class CoursesController < ApplicationController #从课程创建的老师那里选择课程大纲 def course_outline + @teacher = User.find(@course.tea_id) + @blog_articles = @teacher.blog.articles + respond_to do |format| + format.js + end + end + + #根据关键字搜索,查找方法一样的,但返回内容不一样 + def search_course_outline + @article_title = params[:title] + @teacher = User.find(@course.tea_id) + @blog_articles = @teacher.blog.articles.like(@article_title) + render :json=>@blog_articles.to_json + end + #设置或者更改课程的大纲 + def set_course_outline + @course.outline = params[:outline_id] + @course.save + respond_to do |format| + format.js + end end #删除课程 diff --git a/app/models/blog_comment.rb b/app/models/blog_comment.rb index 9bb28ddd8..27da33121 100644 --- a/app/models/blog_comment.rb +++ b/app/models/blog_comment.rb @@ -19,6 +19,15 @@ class BlogComment < ActiveRecord::Base after_save :add_user_activity before_destroy :destroy_user_activity + scope :like, lambda {|arg| + if arg.blank? + where(nil) + else + pattern = "%#{arg.to_s.strip.downcase}%" + where(" LOWER(title) LIKE :p ", :p => pattern) + end + } + #在个人动态里面增加当前动态 def add_user_activity if self.parent_id.nil? #只有发博文才插入动态 diff --git a/app/views/courses/_course_outlines_list.html.erb b/app/views/courses/_course_outlines_list.html.erb index cb7c52cfb..b8a8bfd2a 100644 --- a/app/views/courses/_course_outlines_list.html.erb +++ b/app/views/courses/_course_outlines_list.html.erb @@ -8,19 +8,24 @@ - + + <%= form_tag(url_for(:controller=>'courses',:action=>'set_course_outline',:id=>course.id),:method=>'post',:remote=>'true') do %>
    -
      -
    • - -
    • -
    • 博客一
    • -
    -
    发布时间:2015-05-11
    - + <% unless articles.blank? %> + <% articles.each do |article|%> +
      +
    • + +
    • +
    • <%= article.title%>
    • +
    +
    发布时间:<%= format_date(article.created_at)%>
    + <% end %> + <% end %>
    +<% end %> diff --git a/app/views/courses/course_outline.js.erb b/app/views/courses/course_outline.js.erb index 300ba6466..f747661b8 100644 --- a/app/views/courses/course_outline.js.erb +++ b/app/views/courses/course_outline.js.erb @@ -1,4 +1,4 @@ -$('#ajax-modal').html('<%= escape_javascript(render :partial => 'course_outlines_list') %>'); +$('#ajax-modal').html('<%= escape_javascript(render :partial => 'course_outlines_list',:locals => {:articles=>@blog_articles,:course=>@course}) %>'); showModal('ajax-modal', '300px'); //$('#ajax-modal').css('height','250px'); $('#ajax-modal').css('padding-top','0px'); diff --git a/app/views/courses/set_course_outline.js.erb b/app/views/courses/set_course_outline.js.erb new file mode 100644 index 000000000..ad3b78189 --- /dev/null +++ b/app/views/courses/set_course_outline.js.erb @@ -0,0 +1 @@ +hideModal(); \ No newline at end of file diff --git a/app/views/layouts/base_courses.html.erb b/app/views/layouts/base_courses.html.erb index 4e9868efe..43f754097 100644 --- a/app/views/layouts/base_courses.html.erb +++ b/app/views/layouts/base_courses.html.erb @@ -63,7 +63,7 @@ <%= l(:field_is_private)%> <% end %> - <%if @course.tea_id == User.current.id && @course.outline == 0%> + <%if @course.tea_id == User.current.id %> 设置大纲 @@ -172,6 +172,7 @@ <%= call_hook :view_layouts_base_body_bottom %> @@ -40,7 +33,7 @@ <%= text_field_tag 'course_password', nil, :style=>'width:300px;'%>
    - + 确  定 From 16206d8c0aa6adb1db3fb744935881174dc09557 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Thu, 29 Oct 2015 16:48:54 +0800 Subject: [PATCH 119/169] =?UTF-8?q?=E9=9A=90=E8=97=8F=E8=AF=BE=E7=A8=8B?= =?UTF-8?q?=E7=9A=84=E5=85=B3=E9=97=AD/=E5=BC=80=E5=90=AF=E6=8C=89?= =?UTF-8?q?=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/helpers/courses_helper.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb index f9fb31969..a5acc4523 100644 --- a/app/helpers/courses_helper.rb +++ b/app/helpers/courses_helper.rb @@ -628,10 +628,10 @@ module CoursesHelper #重启、关闭课程按钮 def set_course_time course - id = "finish_course_#{course.id}" - linkPath = course_endTime_timeout?(course) ? restartcourse_course_path(course) : finishcourse_course_path(course, format: :js) - desc = course_endTime_timeout?(course) ? l(:label_course_reload) : l(:label_course_closed) - link_to "#{desc}".html_safe, linkPath, :remote => true, :method => :post, :id => id, :confirm => l(:label_course_closed_tips, :desc => desc), :class => "pr_join_a" + # id = "finish_course_#{course.id}" + # linkPath = course_endTime_timeout?(course) ? restartcourse_course_path(course) : finishcourse_course_path(course, format: :js) + # desc = course_endTime_timeout?(course) ? l(:label_course_reload) : l(:label_course_closed) + # link_to "#{desc}".html_safe, linkPath, :remote => true, :method => :post, :id => id, :confirm => l(:label_course_closed_tips, :desc => desc), :class => "pr_join_a" end #加入课程、退出课程按钮 From 578bb2537dfa47ede7771b404af7aa0294a59521 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Thu, 29 Oct 2015 17:09:03 +0800 Subject: [PATCH 120/169] =?UTF-8?q?=E8=AF=BE=E7=A8=8B=E5=A4=A7=E7=BA=B2?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/blog_comments_controller.rb | 24 ++- app/controllers/courses_controller.rb | 7 + .../_simple_ke_reply_form.html.erb | 3 + app/views/blog_comments/quote.js.erb | 2 +- app/views/courses/set_course_outline.js.erb | 6 +- .../courses/show_course_outline.html.erb | 160 ++++++++++++++++++ app/views/layouts/base_courses.html.erb | 50 ++++-- config/routes.rb | 1 + public/stylesheets/courses.css | 9 + 9 files changed, 243 insertions(+), 19 deletions(-) create mode 100644 app/views/courses/show_course_outline.html.erb diff --git a/app/controllers/blog_comments_controller.rb b/app/controllers/blog_comments_controller.rb index 14a0a3536..42f1e2f20 100644 --- a/app/controllers/blog_comments_controller.rb +++ b/app/controllers/blog_comments_controller.rb @@ -60,10 +60,16 @@ class BlogCommentsController < ApplicationController @article.children.delete @article.delete redirect_to user_blogs_path(:user_id=>User.current) - else - root = @article.root - @article.delete - redirect_to user_blog_blog_comment_path(:user_id=>root.author_id,:blog_id=>root.blog_id,:id=>root.id) + else#如果是回复被删, + if params[:course_id] #如果呆了course_id过来了,那么这是要跳到课程大纲去的 + @article.delete + redirect_to show_course_outline_course_path(:id=>params[:course_id]) + else + root = @article.root + @article.delete + redirect_to user_blog_blog_comment_path(:user_id=>root.author_id,:blog_id=>root.blog_id,:id=>root.id) + end + end end @@ -81,6 +87,7 @@ class BlogCommentsController < ApplicationController @content = "> #{ll(Setting.default_language, :text_user_wrote, @blogComment.author.realname)}\n> " @temp = BlogComment.new + @course_id = params[:course_id] @temp.content = "
    #{ll(Setting.default_language, :text_user_wrote, @blogComment.author.realname)}
    #{@blogComment.content.html_safe}
    ".html_safe respond_to do | format| format.js @@ -114,7 +121,14 @@ class BlogCommentsController < ApplicationController #@article.save # redirect_to user_blogs_path(:user_id=>params[:user_id]) respond_to do |format| - format.html { redirect_to user_blog_blog_comment_path(:user_id=>@article.author_id,:blog_id=>@article.blog_id,:id=>@article)} + format.html { + if params[:course_id] #如果呆了course_id过来了,那么这是要跳到课程大纲去的 + redirect_to show_course_outline_course_path(:id=>params[:course_id]) + else + redirect_to user_blog_blog_comment_path(:user_id=>@article.author_id,:blog_id=>@article.blog_id,:id=>@article) + end + + } format.js end rescue Exception => e #如果上面的代码执行发生异常就捕获 diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 1216599a1..3c211b361 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -737,6 +737,13 @@ class CoursesController < ApplicationController end end + #显示课程大纲 + def show_course_outline + @article = BlogComment.find(@course.outline) + respond_to do |format| + format.html {render :layout => 'base_courses'} + end + end #删除课程 #删除课程只是将课程的is_delete状态改为false,is_delete为false状态的课程只有管理员可以看到 def destroy diff --git a/app/views/blog_comments/_simple_ke_reply_form.html.erb b/app/views/blog_comments/_simple_ke_reply_form.html.erb index fa7ff0c4a..d65dd3faa 100644 --- a/app/views/blog_comments/_simple_ke_reply_form.html.erb +++ b/app/views/blog_comments/_simple_ke_reply_form.html.erb @@ -17,6 +17,9 @@
    <%= form_for @blog_comment, :as => :reply, :url => {:controller => 'blog_comments',:action => 'reply', :id => @blogComment.id}, :html => {:multipart => true, :id => 'new_form'} do |f| %> + <% if course_id%> + + <% end %>
    diff --git a/app/views/blog_comments/quote.js.erb b/app/views/blog_comments/quote.js.erb index 4d16745ca..088b2cf67 100644 --- a/app/views/blog_comments/quote.js.erb +++ b/app/views/blog_comments/quote.js.erb @@ -1,5 +1,5 @@ if($("#reply_message_<%= @blogComment.id%>").length > 0) { - $("#reply_message_<%= @blogComment.id%>").replaceWith("<%= escape_javascript(render :partial => 'simple_ke_reply_form', :locals => {:reply => @blogComment,:temp =>@temp,:subject =>@subject}) %>"); + $("#reply_message_<%= @blogComment.id%>").replaceWith("<%= escape_javascript(render :partial => 'blog_comments/simple_ke_reply_form', :locals => {:reply => @blogComment,:temp =>@temp,:subject =>@subject,:course_id=>@course_id}) %>"); $(function(){ $('#reply_subject').val("<%= raw escape_javascript(@subject) %>"); $('#quote_quote').val("<%= raw escape_javascript(@temp.content.html_safe) %>"); diff --git a/app/views/courses/set_course_outline.js.erb b/app/views/courses/set_course_outline.js.erb index ad3b78189..febcb0b1f 100644 --- a/app/views/courses/set_course_outline.js.erb +++ b/app/views/courses/set_course_outline.js.erb @@ -1 +1,5 @@ -hideModal(); \ No newline at end of file +hideModal(); +<%if @course.tea_id == User.current.id && @course.outline == 0 %> +<% else %> + $("#course_outline_bar").html('
    ') +<%end %> diff --git a/app/views/courses/show_course_outline.html.erb b/app/views/courses/show_course_outline.html.erb new file mode 100644 index 000000000..ff503d983 --- /dev/null +++ b/app/views/courses/show_course_outline.html.erb @@ -0,0 +1,160 @@ +<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg',"init_activity_KindEditor",'blog' %> + + + +
    +
    +
    + <%= link_to image_tag(url_to_avatar(@article.author),:width=>50,:height => 50,:alt=>'图像' ),user_path(@article.author) %> +
    +
    + <% if @article.author.id == User.current.id%> + + + + + + + [设置大纲] + + <%end%> + +
    + +
    + <% if @article.try(:author).try(:realname) == ' ' %> + <%= link_to @article.try(:author), user_path(@article.author,:host=>Setting.host_user), :class => "linkBlue2", :target=> "_blank" %> + <% else %> + <%= link_to @article.try(:author).try(:realname), user_path(@article.author,:host=>Setting.host_user), :class => "linkBlue2", :target=> "_blank" %> + <% end %> +
    +
    <%= format_time( @article.created_on)%>
    +
    +
    + <%= @article.content.html_safe%> +
    +
    +
    + <%#= link_to_attachments_course @topic, :author => false %> + <% if @article.attachments.any?%> + <% options = {:author => true, :deletable => false} %> + <%= render :partial => 'blog_comments/attachments_links', :locals => {:attachments => @article.attachments, :options => options, :is_float => true} %> + <% end %> +
    +
    +
    +
    +
    + <% count=0 %> + <% if @article.parent %> + <% count=@article.parent.children.count%> + <% else %> + <% count=@article.children.count%> + <% end %> +
    + <% unless count == 0 %> +
    +
    回复(<%=count %>)
    +
    + +
    +
    + <%@article.children.reorder('created_on desc').each_with_index do |reply,i| %> + +
    +
    + <%= link_to image_tag(url_to_avatar(reply.author), :width => 33,:height => 33), user_path(reply.author) %> +
    +
    +
    + <% if reply.try(:author).try(:realname) == ' ' %> + <%= link_to reply.try(:author), user_path(reply.author_id,:host=>Setting.host_user), :class => "newsBlue mr10 f14" %> + <% else %> + <%= link_to reply.try(:author).try(:realname), user_path(reply.author_id,:host=>Setting.host_user), :class => "newsBlue mr10 f14" %> + <% end %> +
    +
    + <%= reply.content.html_safe%> +
    +
    + <%= format_time(reply.created_on) %> + +
    +

    +
    +
    +
    + <% end %> +
    + + <% end %> +
    + <% if !@article.locked? && User.current.logged?%> +
    + +
    +
    + <%= form_for :blog_comment, :url => {:action => 'reply',:controller => 'blog_comments',:user_id=>@article.author.id,:blog_id=>@article.blog_id, :id => @article.id}, :html => {:multipart => true, :id => 'message_form'} do |f| %> + + <%= render :partial => 'blog_comments/reply_form', :locals => {:f => f,:user=>@user,:article=>@article} %> + <%= link_to l(:button_cancel), "javascript:void(0)", :onclick => 'canel_message_replay();', :class => " grey_btn fr c_white mt10 mr5" %> + <%= link_to l(:button_submit), "javascript:void(0)", :onclick => 'submit_message_replay();', :class => "blue_btn fr c_white mt10", :style => "margin-right: 5px;" %> + <% end %> +
    +
    +
    + <% end %> +
    +
    \ No newline at end of file diff --git a/app/views/layouts/base_courses.html.erb b/app/views/layouts/base_courses.html.erb index 43f754097..d7e7bba90 100644 --- a/app/views/layouts/base_courses.html.erb +++ b/app/views/layouts/base_courses.html.erb @@ -54,21 +54,47 @@
    + + + + + + + + + + + + + + + + + + +
    - - <%= @course.name %> - - <% if @course.is_public == 0%> - + +
    + + <%= @course.name %> + + <% if @course.is_public == 0%> + + <%= l(:field_is_private)%> - <% end %> - <%if @course.tea_id == User.current.id %> - - 设置大纲 - - <% end %> -
    + <% end %> + + <%if @course.tea_id == User.current.id && (@course.outline == 0 || BlogComment.where(:id=>@course.outline).count == 0) %> + + <% elsif @course.tea_id == User.current.id && @course.outline != 0 && BlogComment.where(:id=>@course.outline).count != 0%> + + <% elsif @course.tea_id != User.current.id %> + <%end %> + +
    +
    <%= l(:label_account_identity_teacher)%>(<%= course_teacher_link teacher_num %>) diff --git a/config/routes.rb b/config/routes.rb index 3811bd43f..5be734dee 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -792,6 +792,7 @@ RedmineApp::Application.routes.draw do get 'course_outline' post 'search_course_outline' post 'set_course_outline' + get 'show_course_outline' end collection do match 'join_private_courses', :via => [:get, :post] diff --git a/public/stylesheets/courses.css b/public/stylesheets/courses.css index 9fe75db34..744f1e07a 100644 --- a/public/stylesheets/courses.css +++ b/public/stylesheets/courses.css @@ -1099,3 +1099,12 @@ a.postRouteLink:hover {text-decoration:underline;} .blogRow {width:280px; height:15px; line-height:15px;} .blogSearchBox {border:1px solid #e6e6e6; height:25px; background-color:#ffffff; margin-top:8px; margin-bottom:8px;}/*width:280px;*/ .blogSearchContent {border:none; outline:none; background-color:#ffffff; width:216px; height:25px; padding-left:10px; display:inline-block; float:left;} + +/*课程大纲图标样式20151028Tim*/ +.syllabusIcon {background:url("../images/course/syllabus.png") 0px 0px no-repeat; width: 17px; height: 16px; display: inline-block;} +.syllabusSetting {background:url("../images/course/syllabus.png") 0px -16px no-repeat; width: 20px; height: 16px; display: inline-block;} + +.syllabusSettingIcon {background:url(../images/course/syllabus_setting.png) 0px 0px no-repeat; width:20px; height:20px;} +.syllabusSettingIcon:hover {cursor: pointer} + +.pic_files{display:block; background:url(../images/public_icon.png) 0px -578px no-repeat; width:20px; height:15px;} From 070166e7c7e0b7cce7d2fd8eb28b787231060288 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Thu, 29 Oct 2015 17:26:37 +0800 Subject: [PATCH 121/169] =?UTF-8?q?=E6=90=9C=E7=B4=A2=E8=AF=BE=E7=A8=8B?= =?UTF-8?q?=E5=90=8E=EF=BC=8C=E7=82=B9=E5=87=BB=E9=80=80=E5=87=BA=E6=8C=89?= =?UTF-8?q?=E9=92=AE=EF=BC=8C=E6=89=A7=E8=A1=8C=E6=93=8D=E4=BD=9C=E5=90=8E?= =?UTF-8?q?=EF=BC=8C=E5=88=B7=E6=96=B0=E5=BD=93=E5=89=8D=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=EF=BC=8C=E4=BD=BF=E9=80=80=E5=87=BA=E5=8F=98=E6=88=90=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/courses/_set_join.js.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/courses/_set_join.js.erb b/app/views/courses/_set_join.js.erb index 33caf5273..dbdf3d7f3 100644 --- a/app/views/courses/_set_join.js.erb +++ b/app/views/courses/_set_join.js.erb @@ -23,4 +23,6 @@ <% else %> alert("未知错误,请稍后再试"); <% end %> +<% else %> + location.reload(); <% end %> From 57f37acecc449522542e156bfbdec5a9b6026d27 Mon Sep 17 00:00:00 2001 From: huang Date: Thu, 29 Oct 2015 17:28:23 +0800 Subject: [PATCH 122/169] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=83=85=E5=86=B5?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=20=E6=8F=90=E4=BA=A4=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 4 ++- app/views/repositories/_revisions.html.erb | 30 +++++++++++----------- app/views/repositories/show.html.erb | 10 +------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 72314cad9..f41dbe6c7 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -283,7 +283,9 @@ update def changes @entry = @repository.entry(@path, @rev) (show_error_not_found; return) unless @entry - @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i) + g = Gitlab.client + @changesets = g.get ("/projects/#{@project.gpid}/repository/commits?#{@rev}") + #@changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i) @properties = @repository.properties(@path, @rev) @changeset = @repository.find_changeset_by_name(@rev) render :layout => 'base_projects' diff --git a/app/views/repositories/_revisions.html.erb b/app/views/repositories/_revisions.html.erb index 3da718484..fe60f8933 100644 --- a/app/views/repositories/_revisions.html.erb +++ b/app/views/repositories/_revisions.html.erb @@ -1,35 +1,35 @@ - <%= form_tag( {:controller => 'repositories', :action => 'diff', :id => project, :repository_id => @repository.identifier_param, :path => to_path_param(path)}, :method => :get ) do %>
    <%= l(:field_name) %><%= l(:field_filesize) %><%= l(:label_revision) %><%= l(:label_age) %><%= l(:field_author) %><%= l(:field_comments) %>
    -
    <%= line.html_safe %>
    +
    <%= line.html_safe %>
    - - - - - - - - + + + + + + + + <% show_diff = revisions.size > 1 %> <% line_num = 1 %> <% revisions.each do |changeset| %> - - - - - + + + + + <% line_num += 1 %> <% end %>
    #<%= l(:label_date) %><%= l(:field_author) %><%= l(:field_comments) %>
    <%= radio_button_tag('rev', changeset.identifier, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('#cbto-#{line_num+1}').attr('checked',true);") if show_diff && (line_num < revisions.size) %><%= radio_button_tag('rev_to', changeset.identifier, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('#cb-#{line_num}').attr('checked')) {$('#cb-#{line_num-1}').attr('checked',true);}") if show_diff && (line_num > 1) %><%= format_time(changeset.committed_on) %><%= h truncate(changeset.author.to_s, :length => 30) %><%= textilizable(truncate_at_line_break(changeset.comments)) %><%= h truncate(changeset.id.to_s, :length => 20) %><%= format_time(changeset.created_at) %><%= h truncate(changeset.author_name.to_s, :length => 30) %><%= textilizable(truncate_at_line_break(changeset.message)) %>

    - <%= submit_tag(l(:label_view_diff), :name => nil, :class=>"c_blue") if show_diff %> + <%#= submit_tag(l(:label_view_diff), :name => nil, :class=>"c_blue") if show_diff %>

    + <% end %> diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index 132d915f2..7f9f675d2 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -59,15 +59,7 @@ <%= render_properties(@properties) %> -<% if authorize_for('repositories', 'revisions') %> - <% if @changesets && !@changesets.empty? %> - <% has_branches = (!@repository.branches.nil? && @repository.branches.length > 0) - sep = '' %> - <% if @repository.supports_all_revisions? && @path.blank? %> - <%= link_to l(:label_view_all_revisions_commits), :action => 'revisions', :id => @project, :repository_id => @repository.identifier_param %> - <% end %> | - <% end %> -<% end %> + 如何提交代码 <% content_for :header_tags do %> From c4816a04143a9b3c8849b5fae76afe98bc97479c Mon Sep 17 00:00:00 2001 From: cxt Date: Fri, 30 Oct 2015 09:56:55 +0800 Subject: [PATCH 123/169] =?UTF-8?q?=E5=90=8D=E5=AD=97=E4=B8=BA=E7=A9=BA?= =?UTF-8?q?=E7=9A=84=E7=94=A8=E6=88=B7=E5=88=9B=E5=BB=BA=E5=8D=9A=E5=AE=A2?= =?UTF-8?q?=E6=97=B6=E5=8D=9A=E5=AE=A2=E5=90=8D=E7=A7=B0=E4=B8=BA=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index b75b67a1a..e68fc7d8e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -260,7 +260,7 @@ class User < Principal @blog = Blog.where("author_id = #{self.id}").all[0] if @blog.nil? #如果某个user的blog不存在,那么就创建一条,并且跳转 - @blog = Blog.create(:name=>(User.find(self.id).realname), + @blog = Blog.create(:name=>(User.find(self.id).realname.blank? ? User.find(self.id).login : User.find(self.id).realname ), :description=>'', :author_id=>self.id) @blog.save From 70003a8d9cfeb73776495cba610d6cb44fe7b624 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Fri, 30 Oct 2015 10:19:57 +0800 Subject: [PATCH 124/169] =?UTF-8?q?=E8=AF=BE=E7=A8=8B=E5=A4=A7=E7=BA=B2?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20=E5=BC=B9=E5=87=BA=E6=A1=86=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/stylesheets/courses.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/stylesheets/courses.css b/public/stylesheets/courses.css index 744f1e07a..2ea3b82c8 100644 --- a/public/stylesheets/courses.css +++ b/public/stylesheets/courses.css @@ -1095,7 +1095,7 @@ a.postRouteLink:hover {text-decoration:underline;} .searchIconPopup{width:31px; height:25px; background-color:#ffffff; background:url(../images/homepage_icon.png) 5px -394px no-repeat; display:inline-block; float:left; cursor: pointer;} .searchIconPopup:hover {background:url(../images/homepage_icon.png) 5px -420px no-repeat;} .blogTitle {max-width:240px; font-size:12px; color:#484848; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;} -.blogBlock {overflow-x:hidden; max-height:200px;min-height: 200px; overflow-y:auto; margin-bottom:5px;} +.blogBlock {overflow-x:hidden; max-height:200px;min-height: 200px; overflow-y:auto; margin-bottom:5px;width: 200px} .blogRow {width:280px; height:15px; line-height:15px;} .blogSearchBox {border:1px solid #e6e6e6; height:25px; background-color:#ffffff; margin-top:8px; margin-bottom:8px;}/*width:280px;*/ .blogSearchContent {border:none; outline:none; background-color:#ffffff; width:216px; height:25px; padding-left:10px; display:inline-block; float:left;} From bc7ce290a2b68b8fb270fe1e0cf72ade88c7d45a Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Fri, 30 Oct 2015 10:43:35 +0800 Subject: [PATCH 125/169] =?UTF-8?q?=E6=B2=A1=E6=9C=89=E9=80=89=E4=B8=AD?= =?UTF-8?q?=E4=BB=BB=E4=BD=95=E5=A4=A7=E7=BA=B2=E4=B8=8D=E8=83=BD=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/courses/_course_outlines_list.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/courses/_course_outlines_list.html.erb b/app/views/courses/_course_outlines_list.html.erb index b8a8bfd2a..e3b441f24 100644 --- a/app/views/courses/_course_outlines_list.html.erb +++ b/app/views/courses/_course_outlines_list.html.erb @@ -24,7 +24,7 @@ <% end %>
    From 4766cff41d7985165bb1b58e9adc6d61393b6db0 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Fri, 30 Oct 2015 10:46:51 +0800 Subject: [PATCH 126/169] =?UTF-8?q?=E4=B8=8D=E7=AC=A6=E5=90=88=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E8=BE=93=E5=85=A5=E7=9A=84=E6=97=B6=E5=80=99?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E6=98=BE=E7=A4=BA=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/layouts/base_courses.html.erb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/base_courses.html.erb b/app/views/layouts/base_courses.html.erb index d7e7bba90..305eaaa29 100644 --- a/app/views/layouts/base_courses.html.erb +++ b/app/views/layouts/base_courses.html.erb @@ -86,11 +86,15 @@ <% end %> - <%if @course.tea_id == User.current.id && (@course.outline == 0 || BlogComment.where(:id=>@course.outline).count == 0) %> + <%if User.current && @course.tea_id == User.current.id && (@course.outline == 0 || BlogComment.where(:id=>@course.outline).count == 0) %> - <% elsif @course.tea_id == User.current.id && @course.outline != 0 && BlogComment.where(:id=>@course.outline).count != 0%> + <% elsif User.current && @course.tea_id == User.current.id && @course.outline != 0 && BlogComment.where(:id=>@course.outline).count != 0%> - <% elsif @course.tea_id != User.current.id %> + <% elsif User.current && @course.tea_id != User.current.id && !@course.is_public? && User.current.member_of_course?(@course)%> + + <% elsif User.current && @course.tea_id != User.current.id && @course.is_public?%> + + <%else%> <%end %>
    @@ -233,6 +237,7 @@ } }else{ + $("#course_outline_list").html(''); $("#course_outline_hint").show(); } } From 9e1c675c66ff42d66d5a45fa7b0d3dfef7aa6d1b Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Fri, 30 Oct 2015 10:49:58 +0800 Subject: [PATCH 127/169] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E5=A4=A7=E7=BA=B2?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=BB=93=E6=9E=9C=E9=80=82=E5=BA=94=E9=AB=98?= =?UTF-8?q?=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/stylesheets/courses.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/stylesheets/courses.css b/public/stylesheets/courses.css index 2ea3b82c8..f1df34894 100644 --- a/public/stylesheets/courses.css +++ b/public/stylesheets/courses.css @@ -1095,7 +1095,7 @@ a.postRouteLink:hover {text-decoration:underline;} .searchIconPopup{width:31px; height:25px; background-color:#ffffff; background:url(../images/homepage_icon.png) 5px -394px no-repeat; display:inline-block; float:left; cursor: pointer;} .searchIconPopup:hover {background:url(../images/homepage_icon.png) 5px -420px no-repeat;} .blogTitle {max-width:240px; font-size:12px; color:#484848; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;} -.blogBlock {overflow-x:hidden; max-height:200px;min-height: 200px; overflow-y:auto; margin-bottom:5px;width: 200px} +.blogBlock {overflow-x:hidden; max-height:200px;min-height: 20px; overflow-y:auto; margin-bottom:5px;width: 200px} .blogRow {width:280px; height:15px; line-height:15px;} .blogSearchBox {border:1px solid #e6e6e6; height:25px; background-color:#ffffff; margin-top:8px; margin-bottom:8px;}/*width:280px;*/ .blogSearchContent {border:none; outline:none; background-color:#ffffff; width:216px; height:25px; padding-left:10px; display:inline-block; float:left;} From a27c359df4775fa26b563c1f270028ae48587177 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Fri, 30 Oct 2015 10:52:34 +0800 Subject: [PATCH 128/169] =?UTF-8?q?=E5=A4=A7=E7=BA=B2=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E7=9A=84=E5=9B=BE=E6=A0=87=E6=94=B9=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/show_course_outline.html.erb | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/app/views/courses/show_course_outline.html.erb b/app/views/courses/show_course_outline.html.erb index ff503d983..84ddc1074 100644 --- a/app/views/courses/show_course_outline.html.erb +++ b/app/views/courses/show_course_outline.html.erb @@ -34,15 +34,35 @@ <%= link_to image_tag(url_to_avatar(@article.author),:width=>50,:height => 50,:alt=>'图像' ),user_path(@article.author) %>
    - <% if @article.author.id == User.current.id%> + <% if User.current && @article.author.id == User.current.id%> + - [设置大纲] - + + <%end%>
    主题: <%= @article.title%> From 00157b430273bed9359401cc10cb96fa42a4df39 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Fri, 30 Oct 2015 10:52:56 +0800 Subject: [PATCH 129/169] =?UTF-8?q?=E5=A4=A7=E7=BA=B2=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E7=9A=84=E5=9B=BE=E6=A0=87=E6=94=B9=E5=8A=A8=20=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=88=A0=E9=99=A4=E5=8A=A8=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/blog_comments_controller.rb | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/controllers/blog_comments_controller.rb b/app/controllers/blog_comments_controller.rb index 42f1e2f20..e398501e4 100644 --- a/app/controllers/blog_comments_controller.rb +++ b/app/controllers/blog_comments_controller.rb @@ -56,12 +56,22 @@ class BlogCommentsController < ApplicationController end def destroy @article = BlogComment.find(params[:id]) - if @article.parent_id.nil? #如果是文章被删,那么跳转到用户博客界面 - @article.children.delete - @article.delete - redirect_to user_blogs_path(:user_id=>User.current) + if @article.parent_id.nil? #如果是文章被删,那么跳转到用户博客界面,如果带了course_id过来,那么就要跳转到课程首页 + if params[:course_id] + @article.children.delete + @article.delete + @course = Course.find(params[:course_id]) + @course.outline = 0 + @course.save + redirect_to course_path(:id=>params[:course_id]) + else + @article.children.delete + @article.delete + redirect_to user_blogs_path(:user_id=>User.current) + end + else#如果是回复被删, - if params[:course_id] #如果呆了course_id过来了,那么这是要跳到课程大纲去的 + if params[:course_id] #如果带了course_id过来了,那么这是要跳到课程大纲去的 @article.delete redirect_to show_course_outline_course_path(:id=>params[:course_id]) else From 6b1c140258d78f53dcc1162ff5e950f87dd55784 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Fri, 30 Oct 2015 11:12:50 +0800 Subject: [PATCH 130/169] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E5=AE=BD=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/stylesheets/courses.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/stylesheets/courses.css b/public/stylesheets/courses.css index f1df34894..b587d5e36 100644 --- a/public/stylesheets/courses.css +++ b/public/stylesheets/courses.css @@ -1095,7 +1095,7 @@ a.postRouteLink:hover {text-decoration:underline;} .searchIconPopup{width:31px; height:25px; background-color:#ffffff; background:url(../images/homepage_icon.png) 5px -394px no-repeat; display:inline-block; float:left; cursor: pointer;} .searchIconPopup:hover {background:url(../images/homepage_icon.png) 5px -420px no-repeat;} .blogTitle {max-width:240px; font-size:12px; color:#484848; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;} -.blogBlock {overflow-x:hidden; max-height:200px;min-height: 20px; overflow-y:auto; margin-bottom:5px;width: 200px} +.blogBlock {overflow-x:hidden; max-height:200px;min-height: 20px; overflow-y:auto; margin-bottom:5px;width: 246px} .blogRow {width:280px; height:15px; line-height:15px;} .blogSearchBox {border:1px solid #e6e6e6; height:25px; background-color:#ffffff; margin-top:8px; margin-bottom:8px;}/*width:280px;*/ .blogSearchContent {border:none; outline:none; background-color:#ffffff; width:216px; height:25px; padding-left:10px; display:inline-block; float:left;} From 8dc730cd3e333edee11ddaead99afa62d40ab3fb Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Fri, 30 Oct 2015 11:31:42 +0800 Subject: [PATCH 131/169] =?UTF-8?q?=E5=A4=A7=E7=BA=B2=E5=9B=9E=E5=A4=8D?= =?UTF-8?q?=E6=A1=86=E6=94=B9=E5=8F=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_simple_ke_reply_form.html.erb | 2 +- .../courses/show_course_outline.html.erb | 56 +++++++++++++++---- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/app/views/blog_comments/_simple_ke_reply_form.html.erb b/app/views/blog_comments/_simple_ke_reply_form.html.erb index d65dd3faa..61669058b 100644 --- a/app/views/blog_comments/_simple_ke_reply_form.html.erb +++ b/app/views/blog_comments/_simple_ke_reply_form.html.erb @@ -23,7 +23,7 @@
    - +

    <% end%> diff --git a/app/views/courses/show_course_outline.html.erb b/app/views/courses/show_course_outline.html.erb index 84ddc1074..3af869e2d 100644 --- a/app/views/courses/show_course_outline.html.erb +++ b/app/views/courses/show_course_outline.html.erb @@ -1,5 +1,16 @@ <%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg',"init_activity_KindEditor",'blog' %> - + @@ -162,18 +173,39 @@ <% end %>
    <% if !@article.locked? && User.current.logged?%> -
    - -
    -
    - <%= form_for :blog_comment, :url => {:action => 'reply',:controller => 'blog_comments',:user_id=>@article.author.id,:blog_id=>@article.blog_id, :id => @article.id}, :html => {:multipart => true, :id => 'message_form'} do |f| %> - - <%= render :partial => 'blog_comments/reply_form', :locals => {:f => f,:user=>@user,:article=>@article} %> - <%= link_to l(:button_cancel), "javascript:void(0)", :onclick => 'canel_message_replay();', :class => " grey_btn fr c_white mt10 mr5" %> - <%= link_to l(:button_submit), "javascript:void(0)", :onclick => 'submit_message_replay();', :class => "blue_btn fr c_white mt10", :style => "margin-right: 5px;" %> - <% end %> + + + + + + + + + + + + + +
    +
    <%= link_to image_tag(url_to_avatar(User.current), :width => "33", :height => "33"), user_path(@article.author_id), :alt => "用户头像" %>
    +
    +
    + <%= form_for 'blog_comment',:url => {:action => 'reply',:controller => 'blog_comments',:user_id=>@article.author.id,:blog_id=>@article.blog_id, :id => @article.id},:method => "post",:html => {:multipart => true, :id => 'message_form'} do |f|%> + + + + + +
    + +
    +

    + <% end%> +
    +
    <% end %>
    From 4d7ca63a12aed1f640aee8dd6d65aa92bbc6fd04 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Fri, 30 Oct 2015 11:51:09 +0800 Subject: [PATCH 132/169] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=8E=A7=E5=88=B6=20=E5=9C=A8=E8=AF=BE?= =?UTF-8?q?=E7=A8=8B=E9=A6=96=E9=A1=B5=E8=AE=BE=E7=BD=AE=E5=A4=A7=E7=BA=B2?= =?UTF-8?q?=E4=B8=8D=E8=B7=B3=E8=BD=AC=20=E5=9C=A8=E5=A4=A7=E7=BA=B2?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=E9=A1=B5=E9=9D=A2=E8=AE=BE=E7=BD=AE=E5=A4=A7?= =?UTF-8?q?=E7=BA=B2=EF=BC=8C=E8=B7=B3=E8=BD=AC=E5=A4=A7=E7=BA=B2=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/courses_controller.rb | 2 ++ app/views/courses/_course_outlines_list.html.erb | 1 + app/views/courses/course_outline.js.erb | 4 ++-- app/views/courses/set_course_outline.js.erb | 3 +++ app/views/courses/show_course_outline.html.erb | 2 +- public/javascripts/course.js | 4 ++-- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 3c211b361..c9d632fec 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -715,6 +715,7 @@ class CoursesController < ApplicationController def course_outline @teacher = User.find(@course.tea_id) @blog_articles = @teacher.blog.articles + @is_in_show_outline_page = params[:is_in_show_outline_page] respond_to do |format| format.js end @@ -732,6 +733,7 @@ class CoursesController < ApplicationController def set_course_outline @course.outline = params[:outline_id] @course.save + @is_in_show_outline_page = params[:is_in_show_outline_page] respond_to do |format| format.js end diff --git a/app/views/courses/_course_outlines_list.html.erb b/app/views/courses/_course_outlines_list.html.erb index e3b441f24..6710c22ba 100644 --- a/app/views/courses/_course_outlines_list.html.erb +++ b/app/views/courses/_course_outlines_list.html.erb @@ -10,6 +10,7 @@
    <%= form_tag(url_for(:controller=>'courses',:action=>'set_course_outline',:id=>course.id),:method=>'post',:remote=>'true') do %> +
    <% unless articles.blank? %> <% articles.each do |article|%> diff --git a/app/views/courses/course_outline.js.erb b/app/views/courses/course_outline.js.erb index f747661b8..490361a24 100644 --- a/app/views/courses/course_outline.js.erb +++ b/app/views/courses/course_outline.js.erb @@ -1,4 +1,4 @@ -$('#ajax-modal').html('<%= escape_javascript(render :partial => 'course_outlines_list',:locals => {:articles=>@blog_articles,:course=>@course}) %>'); +$('#ajax-modal').html('<%= escape_javascript(render :partial => 'course_outlines_list',:locals => {:articles=>@blog_articles,:course=>@course,:show_page=>@is_in_show_outline_page}) %>'); showModal('ajax-modal', '300px'); //$('#ajax-modal').css('height','250px'); $('#ajax-modal').css('padding-top','0px'); @@ -6,4 +6,4 @@ $('#ajax-modal').siblings().remove(); $('#ajax-modal').before(' '); $('#ajax-modal').parent().css("top","30%").css("left","50%"); $('#ajax-modal').parent().addClass("courseOutlinePopup"); -$('#ajax-modal').css("padding-left","16px")//.css("padding-bottom","16px"); \ No newline at end of file +$('#ajax-modal').css("padding-left","16px")//.css("padding-bottom","16px"); diff --git a/app/views/courses/set_course_outline.js.erb b/app/views/courses/set_course_outline.js.erb index febcb0b1f..af075d2b8 100644 --- a/app/views/courses/set_course_outline.js.erb +++ b/app/views/courses/set_course_outline.js.erb @@ -3,3 +3,6 @@ hideModal(); <% else %> $("#course_outline_bar").html(' ') <%end %> +<%if @is_in_show_outline_page && @is_in_show_outline_page == 'Y'%> + window.location.href='<%=show_course_outline_course_path(@course) %>'; +<% end %> diff --git a/app/views/courses/show_course_outline.html.erb b/app/views/courses/show_course_outline.html.erb index 3af869e2d..18a980209 100644 --- a/app/views/courses/show_course_outline.html.erb +++ b/app/views/courses/show_course_outline.html.erb @@ -51,7 +51,7 @@
    • - 重设大纲 + 重设大纲
    • <%= link_to( diff --git a/public/javascripts/course.js b/public/javascripts/course.js index ddd323166..68ffeb46b 100644 --- a/public/javascripts/course.js +++ b/public/javascripts/course.js @@ -1224,10 +1224,10 @@ var autoTextarea2 = function (elem,elem2, extra, maxHeight) { change(); }; -//课程大纲选择请求 +//课程大纲选择请求,第二个参数是可选的,判断当前页面是大纲显示页面还是 课程首页 function course_outline(id){ $.get( - ' /courses/'+id+'/course_outline' + ' /courses/'+id+'/course_outline'+"?is_in_show_outline_page="+(arguments[1] ? arguments[1] : 'N') ) } //$(function(){ From 6dcc97d5d1fb2fb7c648b7fb508b852948877707 Mon Sep 17 00:00:00 2001 From: cxt Date: Fri, 30 Oct 2015 13:58:54 +0800 Subject: [PATCH 133/169] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BD=9C=E4=B8=9A?= =?UTF-8?q?=E7=9A=84=E5=8F=91=E5=B8=83=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/homework_common_controller.rb | 8 ++++++ app/controllers/users_controller.rb | 20 ++++++++++++--- app/models/homework_common.rb | 25 ++++++++++++++----- app/views/homework_common/edit.html.erb | 1 + app/views/homework_common/index.html.erb | 1 + .../users/_user_homework_detail.html.erb | 8 ++++-- app/views/users/_user_homework_form.html.erb | 4 +++ app/views/users/user_homeworks.html.erb | 1 + ...006_update_homework_common_publish_time.rb | 16 ++++++++++++ db/schema.rb | 6 ++--- lib/tasks/homework_publishtime.rake | 23 +++++++++++++++++ public/javascripts/course.js | 24 ++++++++++++++++++ public/javascripts/new_user.js | 25 +++++++++++++++++++ 13 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 db/migrate/20151029030006_update_homework_common_publish_time.rb create mode 100644 lib/tasks/homework_publishtime.rake diff --git a/app/controllers/homework_common_controller.rb b/app/controllers/homework_common_controller.rb index 55807886b..3db7ada2b 100644 --- a/app/controllers/homework_common_controller.rb +++ b/app/controllers/homework_common_controller.rb @@ -47,10 +47,18 @@ class HomeworkCommonController < ApplicationController if params[:homework_common] @homework.name = params[:homework_common][:name] @homework.description = params[:homework_common][:description] + if params[:homework_common][:publish_time] == "" + @homework.publish_time = Date.today + else + @homework.publish_time = params[:homework_common][:publish_time] + end @homework.end_time = params[:homework_common][:end_time] || Time.now @homework.course_id = params[:course_id] homework_detail_manual = @homework.homework_detail_manual || HomeworkDetailManual.new + if @homework.publish_time <= Date.today && homework_detail_manual.comment_status == 0 + homework_detail_manual.comment_status = 1 + end homework_detail_manual.evaluation_start = params[:evaluation_start].blank? ? @homework.end_time + 7 : params[:evaluation_start] homework_detail_manual.evaluation_end = params[:evaluation_end].blank? ? homework_detail_manual.evaluation_start + 7 : params[:evaluation_end] diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 7c41dd038..133fcd94c 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -368,8 +368,12 @@ class UsersController < ApplicationController if User.current == @user @page = params[:page] ? params[:page].to_i + 1 : 0 user_course_ids = @user.courses.empty? ? "(-1)" :"(" + @user.courses.visible.map{|course| course.id}.join(",") + ")" - @homework_commons = HomeworkCommon.where("course_id in #{user_course_ids}").order("created_at desc").limit(10).offset(@page * 10) @is_teacher = User.current.user_extensions && User.current.user_extensions.identity == 0 && User.current.allowed_to?(:add_course, nil, :global => true) + if @is_teacher + @homework_commons = HomeworkCommon.where("course_id in #{user_course_ids}").order("created_at desc").limit(10).offset(@page * 10) + else + @homework_commons = HomeworkCommon.where("course_id in #{user_course_ids} and publish_time <= '#{Date.today}'").order("created_at desc").limit(10).offset(@page * 10) + end @is_in_course = params[:is_in_course].to_i || 0 respond_to do |format| format.js @@ -493,8 +497,12 @@ class UsersController < ApplicationController homework = HomeworkCommon.new homework.name = params[:homework_common][:name] homework.description = params[:homework_common][:description] - homework.end_time = params[:homework_common][:end_time] || Time.now - homework.publish_time = Time.now + homework.end_time = params[:homework_common][:end_time] || Date.today + if params[:homework_common][:publish_time] == "" + homework.publish_time = Date.today + else + homework.publish_time = params[:homework_common][:publish_time] + end homework.homework_type = params[:homework_type].to_i || 1 homework.late_penalty = 10 homework.teacher_priority = 1 @@ -506,7 +514,11 @@ class UsersController < ApplicationController homework_detail_manual = HomeworkDetailManual.new homework_detail_manual.ta_proportion = homework.homework_type == 1 ? 0.6 : 0.3 - homework_detail_manual.comment_status = 1 + if homework.publish_time > Date.today + homework_detail_manual.comment_status = 0 + else + homework_detail_manual.comment_status = 1 + end homework_detail_manual.evaluation_start = params[:evaluation_start].blank? ? homework.end_time + 7 : params[:evaluation_start] homework_detail_manual.evaluation_end = params[:evaluation_end].blank? ? homework_detail_manual.evaluation_start + 7 : params[:evaluation_end] homework_detail_manual.evaluation_num = params[:evaluation_num] || 3 diff --git a/app/models/homework_common.rb b/app/models/homework_common.rb index 03a7644a2..8d421a98f 100644 --- a/app/models/homework_common.rb +++ b/app/models/homework_common.rb @@ -23,7 +23,8 @@ class HomeworkCommon < ActiveRecord::Base :description => :description, :author => :author, :url => Proc.new {|o| {:controller => 'student_work', :action => 'index', :homework => o.id}} - after_create :act_as_activity, :send_mail, :act_as_course_activity, :act_as_course_message + after_create :act_as_activity, :send_mail, :act_as_course_message + after_save :act_as_course_activity after_destroy :delete_kindeditor_assets def act_as_activity @@ -33,17 +34,27 @@ class HomeworkCommon < ActiveRecord::Base #课程动态公共表记录 def act_as_course_activity if self.course - self.course_acts << CourseActivity.new(:user_id => self.user_id,:course_id => self.course_id) + if self.homework_detail_manual.comment_status == 0 + self.course_acts.destroy_all + else + if self.course_acts.size == 0 + self.course_acts << CourseActivity.new(:user_id => self.user_id,:course_id => self.course_id) + end + end end end #课程作业消息记录 def act_as_course_message if self.course - self.course.members.each do |m| - # if m.user_id != self.user_id + if self.homework_detail_manual.comment_status == 0 + self.course_messages.destroy_all + else + self.course.members.each do |m| + # if m.user_id != self.user_id self.course_messages << CourseMessage.new(:user_id => m.user_id, :course_id => self.course_id, :viewed => false) - # end + # end + end end end end @@ -54,7 +65,9 @@ class HomeworkCommon < ActiveRecord::Base end def send_mail - Mailer.run.homework_added(self) + if self.homework_detail_manual.comment_status != 0 + Mailer.run.homework_added(self) + end end def is_program_homework? diff --git a/app/views/homework_common/edit.html.erb b/app/views/homework_common/edit.html.erb index 5fc11728b..79692207f 100644 --- a/app/views/homework_common/edit.html.erb +++ b/app/views/homework_common/edit.html.erb @@ -1,6 +1,7 @@ -
      +
      <%= link_to image_tag(url_to_avatar(@article.author),:width=>50,:height => 50,:alt=>'图像' ),user_path(@article.author) %>
      <% if User.current && @article.author.id == User.current.id%> -
    • - <%= link_to User.find(ma.course_message_id), user_path(ma.course_message_id), :class => "#{ma.viewed == 0 ? "newsBlack" : "newsGrey"}", + <%= link_to User.find(ma.course_message_id).login+"("+(User.find(ma.course_message_id).realname ? User.find(ma.course_message_id).realname : User.find(ma.course_message_id).login) +")", user_path(ma.course_message_id), :class => "#{ma.viewed == 0 ? "newsBlack" : "newsGrey"}", :onmouseover => "message_titile_show($(this),event)", :onmouseout => "message_titile_hide($(this))" %>
    • From be25a57eaf16aa5c1addf9172d15bb0c165a9b90 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sun, 1 Nov 2015 21:49:07 +0800 Subject: [PATCH 150/169] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E7=94=A8=E6=88=B7bug?= =?UTF-8?q?=E8=A7=A3=E5=86=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/user.rb | 4 +++- lib/gitlab-cli/lib/gitlab/client/users.rb | 5 +++++ lib/trustie/gitlab/helper.rb | 16 ++++++++++++++-- lib/trustie/gitlab/manage_user.rb | 1 - 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index e68fc7d8e..edc29c015 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -209,7 +209,7 @@ class User < Principal validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true validate :validate_password_length # validates_email_realness_of :mail - before_create :set_mail_notification, :sync_gitlab_user + before_create :set_mail_notification before_save :update_hashed_password before_destroy :remove_references_before_destroy # added by fq @@ -218,6 +218,8 @@ class User < Principal # 更新邮箱用户或用户名的同事,同步更新邀请信息 after_update :update_invite_list + include Trustie::Gitlab::ManageUser + scope :in_group, lambda {|group| group_id = group.is_a?(Group) ? group.id : group.to_i where("#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) diff --git a/lib/gitlab-cli/lib/gitlab/client/users.rb b/lib/gitlab-cli/lib/gitlab/client/users.rb index 3fc83cd1b..37bfc0d74 100644 --- a/lib/gitlab-cli/lib/gitlab/client/users.rb +++ b/lib/gitlab-cli/lib/gitlab/client/users.rb @@ -60,6 +60,11 @@ class Gitlab::Client put("/users/#{user_id}", :body => options) end + + def delete_user(user_id) + delete("/users/#{user_id}") + end + # Creates a new user session. # # @example diff --git a/lib/trustie/gitlab/helper.rb b/lib/trustie/gitlab/helper.rb index 5ea4c13e1..57c333875 100644 --- a/lib/trustie/gitlab/helper.rb +++ b/lib/trustie/gitlab/helper.rb @@ -4,15 +4,26 @@ module Trustie module Gitlab module Helper def change_password(uid, en_pwd, salt) + return unless uid options = {:encrypted_password=>en_pwd, :password_salt=>salt} self.g.put("/users/ext/#{uid}", :body => options) # g.edit_user(uid, :encrypted_password=>en_pwd, :password_salt=>salt) end + def find_user(user) + us = self.g.get("/users?search=#{user.mail}") + if Array === us + us.each do |u| + return u if u.email == user.mail + end + end + return nil + end + def add_user(user) u = nil begin - u = self.g.get("/users?search=#{user.mail}").first + u = find_user(user) unless u u = self.g.create_user(user.mail, user.hashed_password, @@ -29,7 +40,8 @@ module Trustie end def del_user(user) - ## gitlab unimplement + return unless user.gid + self.g.delete_user(user.gid) end end diff --git a/lib/trustie/gitlab/manage_user.rb b/lib/trustie/gitlab/manage_user.rb index 76528739c..15bd7ef78 100644 --- a/lib/trustie/gitlab/manage_user.rb +++ b/lib/trustie/gitlab/manage_user.rb @@ -27,7 +27,6 @@ module Trustie change_password(self.gid, self.hashed_password, self.salt) end - private def g @g ||= ::Gitlab.client end From fa0332babd387650b5885a094fef8a8bef8e5962 Mon Sep 17 00:00:00 2001 From: guange <8863824@gmail.com> Date: Sun, 1 Nov 2015 22:25:42 +0800 Subject: [PATCH 151/169] =?UTF-8?q?=E5=8F=AA=E6=9C=89=E5=9C=A8=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E9=A1=B9=E7=9B=AE=E6=97=B6=E6=89=8D=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 16 ++--------- lib/trustie/gitlab/manage_user.rb | 4 +-- lib/trustie/gitlab/sync.rb | 33 +++++++++++++++++++++- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 4cec85833..8a1b3bbe0 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -125,20 +125,8 @@ update @repository.type = 'Repository::Gitlab' @repository.url = @repository.identifier if request.post? && @repository.save - g = ::Gitlab.client - gid = @project.owner.gid - gproject = g.create_project(@repository.identifier, - path: @repository.identifier, - description: @project.description, - wiki_enabled: false, - wall_enabled: false, - issues_enabled: false, - snippets_enabled: false, - public: false, - user_id: gid - ) - @project.gpid = gproject.id - @project.save! + s = Trustie::Gitlab::Sync.new + s.create_project(@project, @repository) redirect_to settings_project_url(@project, :tab => 'repositories') else redirect_to settings_project_url(@project, :tab => 'repositories',:repository_error_message=>@repository.errors.full_messages) diff --git a/lib/trustie/gitlab/manage_user.rb b/lib/trustie/gitlab/manage_user.rb index 15bd7ef78..a87984490 100644 --- a/lib/trustie/gitlab/manage_user.rb +++ b/lib/trustie/gitlab/manage_user.rb @@ -9,8 +9,8 @@ module Trustie def self.included(base) base.class_eval { - before_create :add_gitlab_user - before_destroy :delete_gitlab_user + #before_create :add_gitlab_user + #before_destroy :delete_gitlab_user before_save :change_gitlab_user } end diff --git a/lib/trustie/gitlab/sync.rb b/lib/trustie/gitlab/sync.rb index ae1cded6c..3a8e8380c 100644 --- a/lib/trustie/gitlab/sync.rb +++ b/lib/trustie/gitlab/sync.rb @@ -26,10 +26,37 @@ module Trustie user.mail_notification = "day" end user.save! + u + end + + + def create_project(project, repository) + gid = project.owner.gid + unless gid + gid = sync_user(project.owner).id + end + raise "unknow gid" unless gid + + gproject = g.create_project(repository.identifier, + path: repository.identifier, + description: project.description, + wiki_enabled: false, + wall_enabled: false, + issues_enabled: false, + snippets_enabled: false, + public: false, + user_id: gid + ) + project.gpid = gproject.id + project.save! end def sync_project(project, opt={}) gid = project.owner.gid + unless gid + gid = sync_user(project.owner).id + end + raise "unknow gid" unless gid path = opt[:path] raise "unknow path" unless path @@ -61,7 +88,11 @@ module Trustie project.members.each do |m| begin - self.g.add_team_member(gproject.id, m.user.gid, UserLevel::DEVELOPER) + gid = m.user.gid + unless gid + gid = sync_user(m.user).id + end + self.g.add_team_member(gproject.id, gid, UserLevel::DEVELOPER) rescue => e puts e end From 066d04016e691ed2883c65d8367cecc4b2e1a9cd Mon Sep 17 00:00:00 2001 From: huang Date: Mon, 2 Nov 2015 08:39:17 +0800 Subject: [PATCH 152/169] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E2=80=9C=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/repositories/show.html.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index abddfc30c..83f1a07ed 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -17,7 +17,6 @@ 版本库地址:<%= h @repository.url %> <% end %> -
      <% else %> From 681f33ee2ca552b4c5e4491e791a0f5dee6433a9 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Mon, 2 Nov 2015 09:35:13 +0800 Subject: [PATCH 153/169] =?UTF-8?q?=E5=8F=96=E6=B6=88=E5=A4=A7=E7=BA=B2=20?= =?UTF-8?q?=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/courses/syllabus.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/courses/syllabus.html.erb b/app/views/courses/syllabus.html.erb index 18f5817b0..bf6980dd2 100644 --- a/app/views/courses/syllabus.html.erb +++ b/app/views/courses/syllabus.html.erb @@ -58,7 +58,7 @@ '取消大纲', {:controller => 'blog_comments',:action => 'destroy',:user_id=>BlogComment.find(@course.outline).author_id,:blog_id=>BlogComment.find(@course.outline).blog_id, :id => @course.outline,:course_id=>@course.id}, :method => :delete, - :data => {:confirm => l(:text_are_you_sure)}, + :data => {:confirm => '确定取消么'}, :class => 'postOptionLink' ) if User.current && User.current.id == @article.author.id %> From 6288151f406b86ae3f06b337b793372a1ac91536 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Mon, 2 Nov 2015 09:49:21 +0800 Subject: [PATCH 154/169] =?UTF-8?q?qq=E7=BE=A4=E5=8A=A0=E5=85=A5=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/layouts/_new_feedback.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_new_feedback.html.erb b/app/views/layouts/_new_feedback.html.erb index 8814d1043..0ef55d278 100644 --- a/app/views/layouts/_new_feedback.html.erb +++ b/app/views/layouts/_new_feedback.html.erb @@ -25,7 +25,7 @@ <%#= l(:label_technical_support) %> - + 请加入:师姐答疑群 From 25e1eb75ab989c9ae9cef9fddb8a67d87be5c77f Mon Sep 17 00:00:00 2001 From: cxt Date: Mon, 2 Nov 2015 09:49:58 +0800 Subject: [PATCH 155/169] =?UTF-8?q?=E8=AF=BE=E7=A8=8B=E5=86=85=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E4=BD=9C=E4=B8=9A=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/javascripts/course.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/javascripts/course.js b/public/javascripts/course.js index ed920b48a..da24e2878 100644 --- a/public/javascripts/course.js +++ b/public/javascripts/course.js @@ -666,6 +666,7 @@ function regex_homework_end_publish_time() $("#homework_publish_time").val(myDate.toLocaleDateString()); } var publish_time = Date.parse($("#homework_publish_time").val()); + var end_time = Date.parse($("#homework_end_time").val()); if(end_time < publish_time) { $("#homework_end_time_span").text("截止日期不能小于发布日期"); @@ -1258,4 +1259,4 @@ function course_outline(id){ // $('#course_outline_search').on('input',function(){ // alert('<%= @course.id%>') // }) -//}) +//}) From 6e7a6909d619710df9ba9417348e9d9fd8732686 Mon Sep 17 00:00:00 2001 From: cxt Date: Mon, 2 Nov 2015 10:02:02 +0800 Subject: [PATCH 156/169] =?UTF-8?q?=E4=BD=9C=E4=B8=9A=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E6=9C=AA=E5=8F=91=E5=B8=83=E4=BD=9C=E4=B8=9A?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=8F=91=E5=B8=83=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/_user_homework_detail.html.erb | 5 +++++ config/locales/zh.yml | 1 + 2 files changed, 6 insertions(+) diff --git a/app/views/users/_user_homework_detail.html.erb b/app/views/users/_user_homework_detail.html.erb index cffb1d97a..fb4ec6bbe 100644 --- a/app/views/users/_user_homework_detail.html.erb +++ b/app/views/users/_user_homework_detail.html.erb @@ -41,6 +41,11 @@ <%= homework_common.language_name%> <% end %> + <% if homework_common.homework_detail_manual.comment_status == 0 %> +
      + <%= l(:label_publish_time)%>:<%= homework_common.publish_time%> +
      + <% end %>
      <%= l(:label_end_time)%>:<%= homework_common.end_time%>
      diff --git a/config/locales/zh.yml b/config/locales/zh.yml index a7274c3c2..12c1b9258 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -2024,6 +2024,7 @@ zh: label_creat: 创建 #api end + label_publish_time: 发布时间 label_end_time: 截止时间 label_send_email: 确定发送 label_input_email: 请输入邮箱地址(必填) From 6fffcec250856e82020d0a99015895371d0bfc1e Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Mon, 2 Nov 2015 10:10:32 +0800 Subject: [PATCH 157/169] =?UTF-8?q?=E5=A6=82=E6=9E=9C=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E5=A1=AB=E5=86=99=E7=9C=9F=E5=AE=9E=E5=90=8D=E5=AD=97=E7=9A=84?= =?UTF-8?q?=E8=AF=9D=EF=BC=8C=E9=82=A3=E4=B9=88=E5=B0=B1=E8=A6=81=E7=94=A8?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=90=8D=E4=BA=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/layouts/new_base_user.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/new_base_user.html.erb b/app/views/layouts/new_base_user.html.erb index 7b951f152..1eddeb517 100644 --- a/app/views/layouts/new_base_user.html.erb +++ b/app/views/layouts/new_base_user.html.erb @@ -46,7 +46,7 @@
      <% if (@user.user_extensions && (@user.user_extensions.identity != 2) ) %> From 0aa2aefe03a1ffacf5a9bad69ccaf5be7439b49c Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Mon, 2 Nov 2015 10:30:51 +0800 Subject: [PATCH 158/169] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=90=8D=E5=AF=86=E7=A0=81=E4=B8=8D=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/my/account.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index 7bc674ea6..7e39ad5fa 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -170,7 +170,7 @@
    • 确认 - +
    From fb692fad4ced5f0902167b71e23565468e8520f8 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Mon, 2 Nov 2015 12:54:58 +0800 Subject: [PATCH 159/169] =?UTF-8?q?=E5=9C=A8=E9=A1=B9=E7=9B=AE=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E4=B8=AD=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=88=96=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=88=90=E5=91=98=E6=97=B6=EF=BC=8C=E5=AE=9E=E6=97=B6?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=A1=B9=E7=9B=AE=E6=88=90=E5=91=98=E6=95=B0?= =?UTF-8?q?=E9=87=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/layouts/base_projects.html.erb | 2 +- app/views/members/create.js.erb | 1 + app/views/members/destroy.js.erb | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/base_projects.html.erb b/app/views/layouts/base_projects.html.erb index 0f622d831..b418be4ab 100644 --- a/app/views/layouts/base_projects.html.erb +++ b/app/views/layouts/base_projects.html.erb @@ -76,7 +76,7 @@
    - <%= l(:label_member) %>(<%= link_to "#{@project.members.count}", project_member_path(@project), :class => 'info_foot_num c_blue' %>) + <%= l(:label_member) %>(<%= link_to "#{@project.members.count}", project_member_path(@project), :class => 'info_foot_num c_blue', :id => 'project_members_number' %>) <%= l(:label_user_watcher) %>(<%= link_to "#{@project.watcher_users.count}", {:controller=>"projects", :action=>"watcherlist", :id => @project.id}, :class => 'info_foot_num c_blue' %>) <% attaments_num = @project.attachments.count+Attachment.where(["`container_type` = 'Version' and `container_id` in (?)",@project.versions.map{ |v| v.id}]).all.count %> diff --git a/app/views/members/create.js.erb b/app/views/members/create.js.erb index 976f5c724..097915b41 100644 --- a/app/views/members/create.js.erb +++ b/app/views/members/create.js.erb @@ -4,6 +4,7 @@ <% else%> $('#pro_st_tbc_03').html('<%= escape_javascript(render :partial => 'projects/settings/new_members') %>'); hideOnLoad(); + $("#project_members_number").html("<%= @project.members.count %>"); alert("<%= @succes_message%>"); <% end%> <%elsif @course%> diff --git a/app/views/members/destroy.js.erb b/app/views/members/destroy.js.erb index b6f206e05..ad87f8043 100644 --- a/app/views/members/destroy.js.erb +++ b/app/views/members/destroy.js.erb @@ -1,5 +1,6 @@ <%if @project%> $('#pro_st_tbc_03').html('<%= escape_javascript(render :partial => 'projects/settings/new_members') %>'); + $("#project_members_number").html("<%= @project.members.count %>"); // $('#tab-content-members').html('<%#= escape_javascript(render :partial => 'projects/settings/members') %>'); <%elsif @course%> $('#course_members_setting').html('<%= escape_javascript(render :partial => 'courses/course_members') %>'); From 583b92a842ce86d3ffaf08aa05e20ac8a116c874 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Mon, 2 Nov 2015 13:12:03 +0800 Subject: [PATCH 160/169] =?UTF-8?q?=E8=AF=BE=E7=A8=8B=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=88=96=E8=80=85=E5=88=A0=E9=99=A4=E6=88=90=E5=91=98=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E5=AE=9E=E6=97=B6=E6=9B=B4=E6=96=B0=E8=AF=BE=E7=A8=8B?= =?UTF-8?q?=E6=88=90=E5=91=98=E6=95=B0=E9=87=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/helpers/courses_helper.rb | 4 ++-- app/views/members/create.js.erb | 2 ++ app/views/members/destroy.js.erb | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb index a5acc4523..caca6fb1e 100644 --- a/app/helpers/courses_helper.rb +++ b/app/helpers/courses_helper.rb @@ -36,7 +36,7 @@ module CoursesHelper #生成课程老师成员链接 def course_teacher_link teacher_num if User.current.member_of_course?(@course) || User.current.admin? - link_to "#{teacher_num}", course_member_path(@course, :role => 1), :class => 'info_foot_num c_blue' + link_to "#{teacher_num}", course_member_path(@course, :role => 1), :class => 'info_foot_num c_blue', :id => 'teacher_number' else content_tag 'span',teacher_num, :class => 'info_foot_num c_blue' end @@ -45,7 +45,7 @@ module CoursesHelper #生成课程学生列表连接 def course_student_link student_num if (User.current.logged? && @course.open_student == 1) || (User.current.member_of_course?(@course)) || User.current.admin? - link_to "#{student_num}", course_member_path(@course, :role => 2), :class => 'info_foot_num c_blue' + link_to "#{student_num}", course_member_path(@course, :role => 2), :class => 'info_foot_num c_blue', :id => "student_number" else content_tag 'span',student_num, :class => 'info_foot_num c_blue' end diff --git a/app/views/members/create.js.erb b/app/views/members/create.js.erb index 097915b41..8a6500572 100644 --- a/app/views/members/create.js.erb +++ b/app/views/members/create.js.erb @@ -12,6 +12,8 @@ alert("<%= @create_member_error_messages%>"); <% else%> $('#course_members_setting').html('<%= escape_javascript(render :partial => 'courses/course_members') %>'); + $("#teacher_number").html("<%= searchTeacherAndAssistant(@course).count %>"); + $("#student_number").html("<%= studentCount(@course) %>"); alert("添加成功"); <% end%> hideOnLoad(); diff --git a/app/views/members/destroy.js.erb b/app/views/members/destroy.js.erb index ad87f8043..b331f15a4 100644 --- a/app/views/members/destroy.js.erb +++ b/app/views/members/destroy.js.erb @@ -4,5 +4,7 @@ // $('#tab-content-members').html('<%#= escape_javascript(render :partial => 'projects/settings/members') %>'); <%elsif @course%> $('#course_members_setting').html('<%= escape_javascript(render :partial => 'courses/course_members') %>'); + $("#teacher_number").html("<%= searchTeacherAndAssistant(@course).count %>") + $("#student_number").html("<%= studentCount(@course) %>"); <%end%> hideOnLoad(); From bd5a316842fa735d5fe5b5b55f61feb2b1d5f4d6 Mon Sep 17 00:00:00 2001 From: lizanle <491823689@qq.com> Date: Mon, 2 Nov 2015 13:47:18 +0800 Subject: [PATCH 161/169] =?UTF-8?q?=E5=8F=8D=E9=A6=88=E6=A1=86=20=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/layouts/_new_feedback.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/_new_feedback.html.erb b/app/views/layouts/_new_feedback.html.erb index 0ef55d278..305187b02 100644 --- a/app/views/layouts/_new_feedback.html.erb +++ b/app/views/layouts/_new_feedback.html.erb @@ -24,9 +24,9 @@
    From 1a7e1db3fb6caba0396ff098986d34f608f84adb Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Mon, 2 Nov 2015 14:20:14 +0800 Subject: [PATCH 162/169] =?UTF-8?q?=E8=B5=84=E6=BA=90=E5=BA=93=E9=BC=A0?= =?UTF-8?q?=E6=A0=87=E5=9C=A8=E7=A7=BB=E5=8A=A8=E6=97=B6=EF=BC=8C=E7=81=B0?= =?UTF-8?q?=E8=89=B2=E6=9D=A1=E8=B7=9F=E7=9D=80=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/user_resource.html.erb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/views/users/user_resource.html.erb b/app/views/users/user_resource.html.erb index f6bb3c8cb..5c6339e59 100644 --- a/app/views/users/user_resource.html.erb +++ b/app/views/users/user_resource.html.erb @@ -145,6 +145,7 @@ var id; //资源id var sendType; //发送到课程 1 发送到项目 2 var lastSendType; //保存上次发送的发送类型 + var last_op $("#resources_list").mousedown(function(e) { //如果是右键的话 if (3 == e.which) { @@ -179,6 +180,28 @@ last_line = line; } }); + + //鼠标经过时,背景颜色设为灰色 + $("#resources_list").mouseover(function(e){ + pageX = e.clientX; + pageY = e.clientY; + var ele = document.elementFromPoint(pageX,pageY); + line = $(ele).parent(); + if(last_op != null){ + last_op.children().css("background-color", 'white'); + restore(); + last_op == null; + } + //如果当前的tag是li,那么还要li的父级元素 + if(line.get(0).tagName === 'LI'){ + line = line.parent(); + } + //将当前的元素的所有子元素的背景色改为蓝色 + line.children().css("background-color", '#e1e1e1'); + //将当前元素赋给 上一个对象 保存起来。 + last_op = line; + }); + //元素包含关系计算 var contains = function(root, el) { if (root.compareDocumentPosition) From 4abb532fd48640a304e98caf82037832ab0a4eaf Mon Sep 17 00:00:00 2001 From: cxt Date: Mon, 2 Nov 2015 14:51:56 +0800 Subject: [PATCH 163/169] =?UTF-8?q?=E6=96=B0=E6=B3=A8=E5=86=8C=E7=9A=84?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E9=BB=98=E8=AE=A4=E8=BA=AB=E4=BB=BD=E4=B8=BA?= =?UTF-8?q?=E7=A9=BA=20=E7=BC=96=E8=BE=91=E8=B5=84=E6=96=99=E6=97=B6?= =?UTF-8?q?=E8=BA=AB=E4=BB=BD=E6=98=AF=E5=BF=85=E5=A1=AB=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/my/account.html.erb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index 7bc674ea6..c2a94a478 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -17,7 +17,7 @@
    • 登录名 : *
    • 邮箱 : *
    • -
    • 身份 : 
    • +
    • 身份 : *
    • 姓(First Name) : *
    • 名(Last Name) : *
    • 组织名 : *
    • @@ -34,7 +34,10 @@
    • <%= f.text_field :mail,:no_label=>true, :required => true,:nh_required=>"1",:class=>"w210"%>
    • - + @@ -61,6 +64,7 @@ <%= text_field_tag :no, nil, :placeholder => l(:label_account_identity_studentID),:style=>"60px" %> <% end %> +
    • <%= f.text_field :lastname,:no_label=>true, :required => true,:nh_required=>"1",:class=>"w210" %> @@ -701,6 +705,11 @@ $("#users_tb_2").click(); <% end %> $('#my_account_form_link').click(function(e){ + if($("#userIdentity").val() == -1 ) { + $("#identity_hint").html('请选择身份').show(); + e.stopImmediatePropagation(); + return; + } if( $("input[name='province']").val().trim() != '' && $("input[name='occupation']").val().trim() == ''){ //学校名字和id不对的话 $("#hint").html('学校必须是从下拉列表中选择的,不能手动修改').show(); e.stopImmediatePropagation(); From ec42f9413cb05a7bc4f355462efce89ba76bc548 Mon Sep 17 00:00:00 2001 From: cxt Date: Mon, 2 Nov 2015 14:59:17 +0800 Subject: [PATCH 164/169] =?UTF-8?q?=E5=8F=91=E5=B8=83=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E6=94=BE=E5=9C=A8=E6=88=AA=E6=AD=A2=E6=97=A5=E6=9C=9F=E7=9A=84?= =?UTF-8?q?=E5=8F=B3=E8=BE=B9=EF=BC=8C=E5=8D=A0=E4=BD=8D=E7=AC=A6=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=8F=91=E5=B8=83=E6=97=A5=E6=9C=9F(=E5=8F=AF?= =?UTF-8?q?=E9=80=89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/_user_homework_form.html.erb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/users/_user_homework_form.html.erb b/app/views/users/_user_homework_form.html.erb index e5fabee0c..c9be16521 100644 --- a/app/views/users/_user_homework_form.html.erb +++ b/app/views/users/_user_homework_form.html.erb @@ -18,13 +18,13 @@
      <%= link_to("导入作业", user_import_homeworks_user_path(User.current.id,:select_course => defined?(select_course)),:class => "BlueCirBtn fl mr10",:remote => true) unless edit_mode%>
      - - <%= calendar_for('homework_publish_time')%> -
      -
      <%= calendar_for('homework_end_time')%>
      +
      + + <%= calendar_for('homework_publish_time')%> +

      From 7980d92ff74fcd4637f8912938cdc971390d3f14 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Mon, 2 Nov 2015 15:46:21 +0800 Subject: [PATCH 165/169] =?UTF-8?q?=E6=90=9C=E7=B4=A2=E8=AF=BE=E7=A8=8B?= =?UTF-8?q?=EF=BC=8C=E4=BD=BF=E7=BB=93=E6=9E=9C=E6=8C=89=E7=85=A7=E5=BC=80?= =?UTF-8?q?=E8=AF=BE=E6=97=B6=E9=97=B4=E3=80=81=E5=88=9B=E5=BB=BA=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E5=80=92=E6=8E=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/courses_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 0a3c61c90..1a561006a 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -107,7 +107,7 @@ class CoursesController < ApplicationController courses = Course.visible @courses = paginateHelper courses,10 else - courses = Course.visible.where("LOWER(name) like '%#{params[:name].to_s.downcase}%'") + courses = Course.visible.where("LOWER(name) like '%#{params[:name].to_s.downcase}%'").order("time desc, created_at desc") @courses = paginateHelper courses,10 end @name = params[:name] From d57e01f17ee427fbeadde60411d4111161dcbdf0 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Mon, 2 Nov 2015 17:19:38 +0800 Subject: [PATCH 166/169] =?UTF-8?q?=E7=BC=96=E8=BE=91=E8=AF=BE=E7=A8=8B?= =?UTF-8?q?=E6=88=90=E5=91=98=E5=90=8E=EF=BC=8C=E6=9B=B4=E6=96=B0=E8=80=81?= =?UTF-8?q?=E5=B8=88=E6=95=B0=E9=87=8F=E5=92=8C=E5=AD=A6=E7=94=9F=E6=95=B0?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/members/update.js.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/members/update.js.erb b/app/views/members/update.js.erb index c5ef983e7..7c27e39fc 100644 --- a/app/views/members/update.js.erb +++ b/app/views/members/update.js.erb @@ -2,6 +2,8 @@ $('#pro_st_tbc_03').html('<%= escape_javascript(render :partial => 'projects/settings/new_members') %>'); <%elsif @course%> $('#course_members_setting').html('<%= escape_javascript(render :partial => 'courses/course_members') %>'); +$("#teacher_number").html("<%= searchTeacherAndAssistant(@course).count %>") +$("#student_number").html("<%= studentCount(@course) %>"); <%end%> hideOnLoad(); From 636063c6cafdc287616310661da8a45a5a7c9c11 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Mon, 2 Nov 2015 17:22:09 +0800 Subject: [PATCH 167/169] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BB=84=E7=BB=87?= =?UTF-8?q?=E8=A1=A8=E5=92=8C=E7=BB=84=E7=BB=87=E6=88=90=E5=91=98=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/migrate/20151102083844_drop_organization.rb | 8 ++++++++ .../20151102084419_create_organization.rb | 18 ++++++++++++++++++ .../20151102090519_create_org_members.rb | 14 ++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 db/migrate/20151102083844_drop_organization.rb create mode 100644 db/migrate/20151102084419_create_organization.rb create mode 100644 db/migrate/20151102090519_create_org_members.rb diff --git a/db/migrate/20151102083844_drop_organization.rb b/db/migrate/20151102083844_drop_organization.rb new file mode 100644 index 000000000..1abb0f6d9 --- /dev/null +++ b/db/migrate/20151102083844_drop_organization.rb @@ -0,0 +1,8 @@ +class DropOrganization < ActiveRecord::Migration + def up + drop_table :organizations + end + + def down + end +end diff --git a/db/migrate/20151102084419_create_organization.rb b/db/migrate/20151102084419_create_organization.rb new file mode 100644 index 000000000..af84ebee9 --- /dev/null +++ b/db/migrate/20151102084419_create_organization.rb @@ -0,0 +1,18 @@ +class CreateOrganization < ActiveRecord::Migration + def up + create_table :organizations do |t| + t.string :name + t.text :description + t.integer :creator_id + t.integer :home_id + t.string :domain + t.boolean :is_public + + t.timestamps + end + end + + def down + drop_table :organizations + end +end diff --git a/db/migrate/20151102090519_create_org_members.rb b/db/migrate/20151102090519_create_org_members.rb new file mode 100644 index 000000000..037d8a3c6 --- /dev/null +++ b/db/migrate/20151102090519_create_org_members.rb @@ -0,0 +1,14 @@ +class CreateOrgMembers < ActiveRecord::Migration + def up + create_table :org_members do |t| + t.integer :user_id + t.integer :organization_id + t.string :role + + end + end + + def down + drop_table :org_members + end +end From eadab840b61b03fe62925c37530e27c98a3bba1d Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Tue, 3 Nov 2015 11:19:38 +0800 Subject: [PATCH 168/169] =?UTF-8?q?=E5=BB=BA=E7=AB=8B=E7=BB=84=E7=BB=87?= =?UTF-8?q?=E3=80=81=E6=88=90=E5=91=98=E8=A1=A8=E5=8F=8A=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=99=A8=EF=BC=9B=20=E7=BC=96=E5=86=99?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E7=BB=84=E7=BB=87=E9=A1=B5=E9=9D=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../javascripts/organizations.js.coffee | 3 + app/assets/stylesheets/organizations.css.scss | 3 + app/controllers/organizations_controller.rb | 15 +++ app/helpers/organizations_helper.rb | 2 +- app/models/org_member.rb | 4 + app/models/organization.rb | 4 +- app/views/layouts/new_base.html.erb | 2 +- app/views/organizations/create.js.erb | 0 app/views/organizations/new.html.erb | 95 +++++++++++++++++++ config/locales/commons/zh.yml | 4 + config/routes.rb | 2 + ...b => 20151103011119_create_org_members.rb} | 7 +- public/stylesheets/org.css | 6 ++ .../organizations_controller_spec.rb | 5 + spec/factories/org_members.rb | 8 ++ spec/models/org_member_spec.rb | 5 + 16 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/organizations.js.coffee create mode 100644 app/assets/stylesheets/organizations.css.scss create mode 100644 app/controllers/organizations_controller.rb create mode 100644 app/models/org_member.rb create mode 100644 app/views/organizations/create.js.erb create mode 100644 app/views/organizations/new.html.erb rename db/migrate/{20151102090519_create_org_members.rb => 20151103011119_create_org_members.rb} (76%) create mode 100644 public/stylesheets/org.css create mode 100644 spec/controllers/organizations_controller_spec.rb create mode 100644 spec/factories/org_members.rb create mode 100644 spec/models/org_member_spec.rb diff --git a/app/assets/javascripts/organizations.js.coffee b/app/assets/javascripts/organizations.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/organizations.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/stylesheets/organizations.css.scss b/app/assets/stylesheets/organizations.css.scss new file mode 100644 index 000000000..39819880e --- /dev/null +++ b/app/assets/stylesheets/organizations.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the organizations controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb new file mode 100644 index 000000000..1d0edbd7e --- /dev/null +++ b/app/controllers/organizations_controller.rb @@ -0,0 +1,15 @@ +class OrganizationsController < ApplicationController + def new + @organization = Organization.new + render :layout => 'new_base' + end + def create + @organization = Organization.new + @organization.name = params[:organization][:name] + @organization.description = params[:organization][:description] + @organization.is_public = params[:organization][:is_public] + @organization.creator_id = User.current.id + member = OrgMember.new(:user_id => User.current.id, :role => 'Manager') + @organization.org_members << member + end +end diff --git a/app/helpers/organizations_helper.rb b/app/helpers/organizations_helper.rb index 10321ba16..24cc9a80e 100644 --- a/app/helpers/organizations_helper.rb +++ b/app/helpers/organizations_helper.rb @@ -1,2 +1,2 @@ -module EnterprisesHelper +module OrganizationsHelper end diff --git a/app/models/org_member.rb b/app/models/org_member.rb new file mode 100644 index 000000000..c4be034fe --- /dev/null +++ b/app/models/org_member.rb @@ -0,0 +1,4 @@ +class OrgMember < ActiveRecord::Base + attr_accessible :organization_id, :role, :user_id + belongs_to :organization +end diff --git a/app/models/organization.rb b/app/models/organization.rb index 5f52dee98..39b5900d9 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -1,5 +1,5 @@ class Organization < ActiveRecord::Base - attr_accessible :logo_link, :name - + attr_accessible :name, :description, :creator_id, :home_id, :domain, :is_public + has_many :org_members, :dependent => :destroy has_many :projects end diff --git a/app/views/layouts/new_base.html.erb b/app/views/layouts/new_base.html.erb index 6ab83d393..a2bbee9e3 100644 --- a/app/views/layouts/new_base.html.erb +++ b/app/views/layouts/new_base.html.erb @@ -13,7 +13,7 @@ <%= javascript_heads %> <%= heads_for_theme %> <%= call_hook :view_layouts_base_html_head %> - <%= stylesheet_link_tag 'public', 'leftside', 'courses','header','prettify'%> + <%= stylesheet_link_tag 'public', 'leftside', 'courses','header','prettify', 'org'%> <%= javascript_include_tag "course","header",'prettify' %> <%= yield :header_tags -%> diff --git a/app/views/organizations/create.js.erb b/app/views/organizations/create.js.erb new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/organizations/new.html.erb b/app/views/organizations/new.html.erb new file mode 100644 index 000000000..de8983f02 --- /dev/null +++ b/app/views/organizations/new.html.erb @@ -0,0 +1,95 @@ + +<% @nav_dispaly_organization_label = 1 + @nav_dispaly_forum_label = 1 %> +<%= error_messages_for 'organization' %> +
      +

      <%= l(:label_organization_new)%>

      +
      +
      +
        + <%= labelled_form_for @organization do |f| %> +
      • + + + + + +
      • +
        +
        +
      • + + +
        +
      • +
      • +

        + <%= f.text_field :identifier, :required => true, :size => 60, :style => "width:488px;", :maxlength => Project::IDENTIFIER_MAX_LENGTH, + value:"#{User.current.id.to_s + '_' +Time.now.to_s.gsub(' ','_').gsub(':','').gsub('+','')}" %> +

        +
      • +
      • + + + (打钩为公开,不打钩则不公开,若不公开,仅项目成员可见该项目。) +
        +
      • +
      • + 提交 + <%= link_to "取消",user_activities_path(User.current.id),:class => "blue_btn grey_btn fl c_white"%> +
        +
      • + <% end%> +
      +
      +
      + +<% html_title(l(:label_organization_new)) -%> + + + diff --git a/config/locales/commons/zh.yml b/config/locales/commons/zh.yml index 61bad2a00..54f0fbdaf 100644 --- a/config/locales/commons/zh.yml +++ b/config/locales/commons/zh.yml @@ -288,6 +288,10 @@ zh: label_tags_project_name: "项目名称:" label_projects_new_name: "项目名称" label_tags_project_description: "项目描述" + + label_organization_name: "组织名称" + label_organization_description: "组织描述" + label_organization_new: "新建组织" label_tags_user_mail: "用户邮箱:" label_tags_user_name: "用户名:" diff --git a/config/routes.rb b/config/routes.rb index 9a6b48da2..0394cdb53 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -31,6 +31,8 @@ RedmineApp::Application.routes.draw do # Enable Grack support # mount Trustie::Grack.new, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post] + resources :organizations + resources :homework_users resources :no_uses delete 'no_uses', :to => 'no_uses#delete' diff --git a/db/migrate/20151102090519_create_org_members.rb b/db/migrate/20151103011119_create_org_members.rb similarity index 76% rename from db/migrate/20151102090519_create_org_members.rb rename to db/migrate/20151103011119_create_org_members.rb index 037d8a3c6..6ef208794 100644 --- a/db/migrate/20151102090519_create_org_members.rb +++ b/db/migrate/20151103011119_create_org_members.rb @@ -1,14 +1,11 @@ class CreateOrgMembers < ActiveRecord::Migration - def up + def change create_table :org_members do |t| t.integer :user_id t.integer :organization_id t.string :role + t.timestamps end end - - def down - drop_table :org_members - end end diff --git a/public/stylesheets/org.css b/public/stylesheets/org.css new file mode 100644 index 000000000..b1dfbbcf0 --- /dev/null +++ b/public/stylesheets/org.css @@ -0,0 +1,6 @@ +@charset "utf-8"; +/* CSS Document */ + +.orgName {width:130px; color:#484848;} +.organization_r_h02{ width:970px; height:40px; background:#eaeaea; margin-bottom:10px;} +.organization_h2{ background:#64bdd9; color:#fff; height:33px; width:90px; text-align:center; font-weight:normal; padding-top:7px; font-size:16px;} \ No newline at end of file diff --git a/spec/controllers/organizations_controller_spec.rb b/spec/controllers/organizations_controller_spec.rb new file mode 100644 index 000000000..cf00f4cf9 --- /dev/null +++ b/spec/controllers/organizations_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe OrganizationsController, :type => :controller do + +end diff --git a/spec/factories/org_members.rb b/spec/factories/org_members.rb new file mode 100644 index 000000000..90997fd30 --- /dev/null +++ b/spec/factories/org_members.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :org_member do + user_id 1 +organization_id 1 +role "MyString" + end + +end diff --git a/spec/models/org_member_spec.rb b/spec/models/org_member_spec.rb new file mode 100644 index 000000000..e7058e5b3 --- /dev/null +++ b/spec/models/org_member_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe OrgMember, :type => :model do + pending "add some examples to (or delete) #{__FILE__}" +end From 3f289dcd74ead27f1afbcb0593e0416d5dedf654 Mon Sep 17 00:00:00 2001 From: ouyangxuhua Date: Tue, 3 Nov 2015 17:21:32 +0800 Subject: [PATCH 169/169] =?UTF-8?q?org=E5=BC=80=E5=8F=91=E9=83=A8=E5=88=86?= =?UTF-8?q?=EF=BC=88=E6=9C=AA=E5=AE=8C=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/organization_controller.rb | 55 ------- app/controllers/organizations_controller.rb | 12 ++ app/views/layouts/base_org.html.erb | 157 ++++++++++++++++++++ app/views/organizations/show.html.erb | 4 + 4 files changed, 173 insertions(+), 55 deletions(-) delete mode 100644 app/controllers/organization_controller.rb create mode 100644 app/views/layouts/base_org.html.erb create mode 100644 app/views/organizations/show.html.erb diff --git a/app/controllers/organization_controller.rb b/app/controllers/organization_controller.rb deleted file mode 100644 index 748fc1732..000000000 --- a/app/controllers/organization_controller.rb +++ /dev/null @@ -1,55 +0,0 @@ -class OrganizationController < ApplicationController - # layout 'base_projects' - before_filter :require_admin, :except => [:index] - - def index - #@projects = Project.find_by_sql("SELECT * FROM projects WHERE id IN (select MAX(id) from projects GROUP BY enterprise_name)") - @organizations = Organization.all - respond_to do |format| - format.html - end - end - - def new - @organizations = Organization.new - respond_to do |format| - format.html - end - end - - def create - @organizations = Organization.new - @organizations.name = params[:organization][:name] - if @organizations.save - redirect_to admin_organization_url - end - end - - def edit - @organization = Organization.find params[:id] - respond_to do |format| - format.html - end - rescue Exception => e - render_404 - end - - def update - @organization = Organization.find params[:id] - @organization.name = params[:organization][:name] - if @organization.save - redirect_to admin_organization_url - end - rescue Exception => e - render_404 - end - - def destroy - @organization = Organization.find params[:id] - if @organization.destroy - redirect_to admin_organization_url - end - rescue Exception => e - render_404 - end -end diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb index 1d0edbd7e..0def8b5f8 100644 --- a/app/controllers/organizations_controller.rb +++ b/app/controllers/organizations_controller.rb @@ -1,4 +1,5 @@ class OrganizationsController < ApplicationController + layout 'base_org' def new @organization = Organization.new render :layout => 'new_base' @@ -11,5 +12,16 @@ class OrganizationsController < ApplicationController @organization.creator_id = User.current.id member = OrgMember.new(:user_id => User.current.id, :role => 'Manager') @organization.org_members << member + if @organization.save + redirect_to organization_path(@organization) + end + end + + def show + + end + + def update + end end diff --git a/app/views/layouts/base_org.html.erb b/app/views/layouts/base_org.html.erb new file mode 100644 index 000000000..a3829ea3b --- /dev/null +++ b/app/views/layouts/base_org.html.erb @@ -0,0 +1,157 @@ +<% @nav_dispaly_project_label = 1 + @nav_dispaly_forum_label = 1 %> +<%#@nav_dispaly_project_label = 1 %> + + + + + <%= h html_title %> + + + <%= csrf_meta_tag %> + <%= favicon %> + <%= javascript_heads %> + <%= heads_for_theme %> + <%= stylesheet_link_tag 'pleft','prettify','jquery/jquery-ui-1.9.2','header','new_user','repository' %> + <%= javascript_include_tag 'cookie','project', 'header','prettify','select_list_move' %> + <%= call_hook :view_layouts_base_html_head %> + + <%= yield :header_tags -%> + + + + + + +
      + <% @organization = Organization.find(params[:id])%> +
      +
      +
      +
      + +
      组织id:<%= @organization.id %>
      + 配置 +
      +
      文章 ( 3 ) | 成员 ( 10 )
      +
      + +
      +
      +
      +
      最新动态
      + +
      +
      +
      +
      用户头像
      +
      + + +
      发帖时间:2015-11-02 11:25
      +
      罗永浩已经把他的主题演讲成功变成了一次次的重大的媒体事件,这让科技界和营销界的从业者们羡慕不已。事实上,老罗不仅从乔布斯那里继承了乔式审美,设计哲学,同时也继承了乔布斯的演讲艺术。乔布斯是人类历史上最伟大的演讲者。在一次又一次的"罗氏演讲"中,我们可以清晰看到老罗对乔布斯演讲的拆解,学习,模仿。每一次的公开演讲,都是一堂案例级的营销课程,值得每一位有志于从事营销相关的朋友分析研究。
      + 下面,我们将从"故事"、"现场"、"准备"三个方面分析学习老罗的演讲技巧,教你如何打造一场"罗永浩式"的演讲。
      +
      + +
      +
      +
      +
      +
      +
      +
      回复(2)
      + + +
      +
      +
      用户头像
      +
      +
      黄井泉 学生 2015-11-03 12:26
      +
      学习下!
      +
      +
      +
      +
      +
      用户头像
      +
      +
      陈正东 学生 2015-11-02 17:08
      +
      锤子手机,卖的就是情怀
      +
      +
      +
      +
      +
      用户头像
      +
      + + 发送
      +
      +
      +
      +
      +
      +
      +
      +
      用户头像
      +
      + + +
      创建时间:2015-10-29 11:23
      +
      +
      +
      +
      +
      +
      +
      + +
      + <%= render_flash_messages %> + <%= yield %> + <%= call_hook :view_layouts_base_content %> +
      +
      + + +
      + <%= render :partial => 'layouts/footer' %> +
      + + + diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb new file mode 100644 index 000000000..9d38aedc1 --- /dev/null +++ b/app/views/organizations/show.html.erb @@ -0,0 +1,4 @@ +<%= javascript_include_tag "jquery.infinitescroll.js" %> +
      +

      组织活动

      +
      \ No newline at end of file