Commit 883b96ab authored by Sean McGivern's avatar Sean McGivern
Browse files

Allow project group links to be expired

parent c03747daa739
......@@ -173,6 +173,7 @@
new BuildArtifacts();
break;
case 'projects:group_links:index':
new MemberExpirationDate();
new GroupsSelect();
break;
case 'search:show':
......
......@@ -11,7 +11,9 @@ def create
return render_404 unless can?(current_user, :read_group, group)
project.project_group_links.create(
group: group, group_access: params[:link_group_access]
group: group,
group_access: params[:link_group_access],
expires_at: params[:expires_at]
)
redirect_to namespace_project_group_links_path(project.namespace, project)
......
......@@ -36,8 +36,4 @@ def leave_confirmation_message(member_source)
"Are you sure you want to leave the " \
"\"#{member_source.human_name}\" #{member_source.class.to_s.humanize(capitalize: false)}?"
end
def member_expires_soon?(member)
member.expires_at < 7.days.from_now
end
end
module Expirable
extend ActiveSupport::Concern
included do
scope :expired, -> { where('expires_at <= ?', Time.current) }
end
def expires?
expires_at.present?
end
def expires_soon?
expires_at < 7.days.from_now
end
end
class Member < ActiveRecord::Base
include Sortable
include Importable
include Expirable
include Gitlab::Access
attr_accessor :raw_invite_token
......@@ -31,7 +32,6 @@ class Member < ActiveRecord::Base
scope :non_invite, -> { where(invite_token: nil) }
scope :request, -> { where.not(requested_at: nil) }
scope :has_access, -> { where('access_level > 0') }
scope :expired, -> { where('expires_at <= ?', Time.current) }
scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) }
......@@ -125,10 +125,6 @@ def pending?
invite? || request?
end
def expires?
expires_at.present?
end
def accept_request
return false unless request?
......
class ProjectGroupLink < ActiveRecord::Base
include Expirable
GUEST = 10
REPORTER = 20
DEVELOPER = 30
......@@ -26,7 +28,7 @@ def human_access
self.class.access_options.key(self.group_access)
end
private
private
def different_group
if self.group && self.project && self.project.group == self.group
......
......@@ -17,6 +17,11 @@
.select-wrapper
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
%span.caret
.form-group
= label_tag :expires_at, 'Access expiration date', class: 'label-light'
.clearable-input
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
%i.clear-icon.js-clear-input
= submit_tag "Share", class: "btn btn-create"
.col-lg-9.col-lg-offset-3
%hr
......@@ -35,6 +40,10 @@
= group.name
%br
up to #{group_link.human_access}
- if group_link.expires?
·
%span{ class: ('text-warning' if group_link.expires_soon?) }
expires in #{distance_of_time_in_words_to_now(group_link.expires_at)}
.pull-right
= link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: "btn btn-transparent" do
%span.sr-only disable sharing
......
......@@ -61,7 +61,7 @@
Joined #{time_ago_with_tooltip(member.created_at)}
- if member.expires?
·
%span{ class: ('text-warning' if member_expires_soon?(member)) }
%span{ class: ('text-warning' if member.expires_soon?) }
Expires in #{distance_of_time_in_words_to_now(member.expires_at)}
- else
......
class RemoveExpiredGroupLinksWorker
include Sidekiq::Worker
def perform
ProjectGroupLink.expired.destroy_all
end
end
......@@ -296,6 +296,9 @@ def host(url)
Settings.cron_jobs['remove_expired_members_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['remove_expired_members_worker']['cron'] ||= '10 0 * * *'
Settings.cron_jobs['remove_expired_members_worker']['job_class'] = 'RemoveExpiredMembersWorker'
Settings.cron_jobs['remove_expired_members_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['remove_expired_members_worker']['cron'] ||= '10 0 * * *'
Settings.cron_jobs['remove_expired_members_worker']['job_class'] = 'RemoveExpiredGroupLinksWorker'
#
# GitLab Shell
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddExpiresAtToProjectGroupLinks < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
add_column :project_group_links, :expires_at, :date
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160810142633) do
ActiveRecord::Schema.define(version: 20160818205718) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -780,6 +780,7 @@
t.datetime "created_at"
t.datetime "updated_at"
t.integer "group_access", default: 30, null: false
t.date "expires_at"
end
create_table "project_import_data", force: :cascade do |t|
......
# Share Projects with other Groups
In GitLab Enterprise Edition you can share projects with other groups.
This makes it possible to add a group of users to a project with a single action.
You can share projects with other groups. This makes it possible to add a group of users
to a project with a single action.
## Groups as collections of users
In GitLab Community Edition groups are used primarily to [create collections of projects](groups.md).
In GitLab Enterprise Edition you can also take advantage of the fact that groups define collections of _users_, namely the group members.
Groups are used primarily to [create collections of projects](groups.md), but you can also
take advantage of the fact that groups define collections of _users_, namely the group
members.
## Sharing a project with a group of users
The primary mechanism to give a group of users, say 'Engineering', access to a project, say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'Project Acme'.
But what if 'Project Acme' already belongs to another group, say 'Open Source'?
This is where the (Enterprise Edition only) group sharing feature can be of use.
The primary mechanism to give a group of users, say 'Engineering', access to a project,
say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'Project
Acme'. But what if 'Project Acme' already belongs to another group, say 'Open Source'?
This is where the group sharing feature can be of use.
To share 'Project Acme' with the 'Engineering' group, go to the project settings page for 'Project Acme' and use the left navigation menu to go to the 'Groups' section.
![The 'Groups' section in the project settings screen (Enterprise Edition only)](groups/share_project_with_groups.png)
![The 'Groups' section in the project settings screen](groups/share_project_with_groups.png)
Now you can add the 'Engineering' group with the maximum access level of your choice.
After sharing 'Project Acme' with 'Engineering', the project is listed on the group dashboard.
......
require 'spec_helper'
feature 'Project group links', feature: true, js: true do
include Select2Helper
let(:master) { create(:user) }
let(:project) { create(:project) }
let!(:group) { create(:group) }
background do
project.team << [master, :master]
login_as(master)
end
context 'setting an expiration date for a group link' do
before do
visit namespace_project_group_links_path(project.namespace, project)
select2 group.id, from: '#link_group_id'
fill_in 'expires_at', with: (Time.current + 4.5.days).strftime('%Y-%m-%d')
page.find('body').click
click_on 'Share'
end
it 'shows the expiration time with a warning class' do
page.within('.enabled-groups') do
expect(page).to have_content('expires in 4 days')
expect(page).to have_selector('.text-warning')
end
end
end
end
require 'spec_helper'
describe RemoveExpiredGroupLinksWorker do
describe '#perform' do
let!(:expired_project_group_link) { create(:project_group_link, expires_at: 1.hour.ago) }
let!(:project_group_link_expiring_in_future) { create(:project_group_link, expires_at: 10.days.from_now) }
let!(:non_expiring_project_group_link) { create(:project_group_link, expires_at: nil) }
it 'removes expired group links' do
expect { subject.perform }.to change { ProjectGroupLink.count }.by(-1)
expect(ProjectGroupLink.find_by(id: expired_project_group_link.id)).to be_nil
end
it 'leaves group links that expire in the future' do
subject.perform
expect(project_group_link_expiring_in_future.reload).to be_present
end
it 'leaves group links that do not expire at all' do
subject.perform
expect(non_expiring_project_group_link.reload).to be_present
end
end
end
......@@ -14,12 +14,12 @@
expect(Member.find_by(id: expired_project_member.id)).to be_nil
end
it 'leaves members who expire in the future' do
it 'leaves members that expire in the future' do
worker.perform
expect(project_member_expiring_in_future.reload).to be_present
end
it 'leaves members who do not expire at all' do
it 'leaves members that do not expire at all' do
worker.perform
expect(non_expiring_project_member.reload).to be_present
end
......@@ -35,12 +35,12 @@
expect(Member.find_by(id: expired_group_member.id)).to be_nil
end
it 'leaves members who expire in the future' do
it 'leaves members that expire in the future' do
worker.perform
expect(group_member_expiring_in_future.reload).to be_present
end
it 'leaves members who do not expire at all' do
it 'leaves members that do not expire at all' do
worker.perform
expect(non_expiring_group_member.reload).to be_present
end
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment