Skip to content
Snippets Groups Projects
Commit 4cb5e501 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 0301a0ca
No related branches found
No related tags found
No related merge requests found
Showing
with 369 additions and 66 deletions
Loading
Loading
@@ -83,10 +83,11 @@ Releases can optionally be associated with one or more
by including a `milestones` array in your requests to the
[Releases API](../../../api/releases/index.md#create-a-release).
 
Releases display this association with the **Milestone** indicator near
the top of the Release block on the **Project overview > Releases** page.
Releases display this association with the **Milestone** indicator in the top
section of the Release block on the **Project overview > Releases** page, along
with some statistics about the issues in the milestone(s).
 
![A Release with one associated milestone](img/release_with_milestone_v12_5.png)
![A Release with one associated milestone](img/release_with_milestone_v12_9.png)
 
Below is an example of milestones with no Releases, one Release, and two
Releases, respectively.
Loading
Loading
@@ -104,7 +105,7 @@ associated with a large number of Releases.
Navigate to **Project > Releases** in order to see the list of releases for a given
project.
 
![Releases list](img/releases.png)
![Releases list](img/releases_v12_9.png)
 
### Number of Releases
 
Loading
Loading
Loading
Loading
@@ -3,7 +3,7 @@
module API
module Entities
class SSHKey < Grape::Entity
expose :id, :title, :key, :created_at
expose :id, :title, :key, :created_at, :expires_at
end
end
end
# frozen_string_literal: true
module Gitlab
module Auth
class KeyStatusChecker
include Gitlab::Utils::StrongMemoize
attr_reader :key
def initialize(key)
@key = key
end
def show_console_message?
console_message.present?
end
def console_message
strong_memoize(:console_message) do
if key.expired?
_('INFO: Your SSH key has expired. Please generate a new key.')
elsif key.expires_soon?
_('INFO: Your SSH key is expiring soon. Please generate a new key.')
end
end
end
end
end
end
Loading
Loading
@@ -81,7 +81,7 @@ module Gitlab
check_push_access!
end
 
success_result(cmd)
success_result
end
 
def guest_can_download_code?
Loading
Loading
@@ -119,12 +119,24 @@ module Gitlab
nil
end
 
def check_for_console_messages(cmd)
def check_for_console_messages
return console_messages unless key?
key_status = Gitlab::Auth::KeyStatusChecker.new(actor)
if key_status.show_console_message?
console_messages.push(key_status.console_message)
else
console_messages
end
end
def console_messages
[]
end
 
def check_valid_actor!
return unless actor.is_a?(Key)
return unless key?
 
unless actor.valid?
raise ForbiddenError, "Your SSH key #{actor.errors[:key].first}."
Loading
Loading
@@ -340,6 +352,10 @@ module Gitlab
actor == :ci
end
 
def key?
actor.is_a?(Key)
end
def can_read_project?
if deploy_key?
deploy_key.has_access_to?(project)
Loading
Loading
@@ -374,8 +390,8 @@ module Gitlab
 
protected
 
def success_result(cmd)
::Gitlab::GitAccessResult::Success.new(console_messages: check_for_console_messages(cmd))
def success_result
::Gitlab::GitAccessResult::Success.new(console_messages: check_for_console_messages)
end
 
def changes_list
Loading
Loading
Loading
Loading
@@ -333,6 +333,9 @@ msgstr ""
msgid "%{level_name} is not allowed since the fork source project has lower visibility."
msgstr ""
 
msgid "%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
 
Loading
Loading
@@ -404,10 +407,7 @@ msgstr ""
msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled"
msgstr ""
 
msgid "%{service_title} activated."
msgstr ""
msgid "%{service_title} settings saved, but not activated."
msgid "%{service_title} %{message}."
msgstr ""
 
msgid "%{size} GiB"
Loading
Loading
@@ -4249,9 +4249,6 @@ msgstr ""
msgid "ClusterIntegration|Enable Cloud Run for Anthos"
msgstr ""
 
msgid "ClusterIntegration|Enable Web Application Firewall"
msgstr ""
msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
msgstr ""
 
Loading
Loading
@@ -4429,9 +4426,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
msgstr ""
 
msgid "ClusterIntegration|Learn more about %{linkStart}ModSecurity%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}."
msgstr ""
 
Loading
Loading
@@ -4564,6 +4558,9 @@ msgstr ""
msgid "ClusterIntegration|Read our %{link_start}help page%{link_end} on Kubernetes cluster integration."
msgstr ""
 
msgid "ClusterIntegration|Real-time web application monitoring, logging and access control. %{linkStart}More information%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|Region"
msgstr ""
 
Loading
Loading
@@ -4711,13 +4708,13 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr ""
 
msgid "ClusterIntegration|Something went wrong while uninstalling %{title}"
msgid "ClusterIntegration|Something went wrong while trying to save your settings. Please try again."
msgstr ""
 
msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
msgid "ClusterIntegration|Something went wrong while uninstalling %{title}"
msgstr ""
 
msgid "ClusterIntegration|Something went wrong while updating the Web Application Firewall."
msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
msgstr ""
 
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
Loading
Loading
@@ -5663,6 +5660,9 @@ msgstr ""
msgid "Could not save prometheus manual configuration"
msgstr ""
 
msgid "Could not upload your designs as one or more files uploaded are not supported."
msgstr ""
msgid "Country"
msgstr ""
 
Loading
Loading
@@ -6794,15 +6794,9 @@ msgstr ""
msgid "DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again."
msgstr ""
 
msgid "DesignManagement|The one place for your designs"
msgstr ""
msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
msgstr ""
 
msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
msgstr ""
msgid "DesignManagement|Upload skipped."
msgstr ""
 
Loading
Loading
@@ -7120,6 +7114,9 @@ msgstr ""
msgid "Downvotes"
msgstr ""
 
msgid "Drop your designs to start your upload."
msgstr ""
msgid "Due date"
msgstr ""
 
Loading
Loading
@@ -8215,6 +8212,9 @@ msgstr ""
msgid "Expires in %{expires_at}"
msgstr ""
 
msgid "Expires:"
msgstr ""
msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
msgstr ""
 
Loading
Loading
@@ -10500,6 +10500,12 @@ msgstr ""
msgid "IDE|This option is disabled because you don't have write permissions for the current branch."
msgstr ""
 
msgid "INFO: Your SSH key has expired. Please generate a new key."
msgstr ""
msgid "INFO: Your SSH key is expiring soon. Please generate a new key."
msgstr ""
msgid "IP Address"
msgstr ""
 
Loading
Loading
@@ -10767,6 +10773,9 @@ msgstr ""
msgid "Incoming email"
msgstr ""
 
msgid "Incoming!"
msgstr ""
msgid "Incompatible Project"
msgstr ""
 
Loading
Loading
@@ -12867,9 +12876,6 @@ msgstr ""
msgid "Name new label"
msgstr ""
 
msgid "Name your individual key via a title"
msgstr ""
msgid "Name:"
msgstr ""
 
Loading
Loading
@@ -13514,6 +13520,9 @@ msgstr ""
msgid "OfSearchInADropdown|Filter"
msgstr ""
 
msgid "Oh no!"
msgstr ""
msgid "Ok let's go"
msgstr ""
 
Loading
Loading
@@ -14857,12 +14866,21 @@ msgstr ""
msgid "Profiles|Enter your name, so people you know can recognize you"
msgstr ""
 
msgid "Profiles|Expires at"
msgstr ""
msgid "Profiles|Expires:"
msgstr ""
msgid "Profiles|Feed token was successfully reset"
msgstr ""
 
msgid "Profiles|Full name"
msgstr ""
 
msgid "Profiles|Give your individual key a title"
msgstr ""
msgid "Profiles|Impersonation"
msgstr ""
 
Loading
Loading
@@ -14884,6 +14902,9 @@ msgstr ""
msgid "Profiles|Key"
msgstr ""
 
msgid "Profiles|Last used:"
msgstr ""
msgid "Profiles|Learn more"
msgstr ""
 
Loading
Loading
@@ -15040,6 +15061,9 @@ msgstr ""
msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
msgstr ""
 
msgid "Profiles|Your key has expired"
msgstr ""
msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
msgstr ""
 
Loading
Loading
@@ -22146,6 +22170,9 @@ msgstr ""
msgid "VisualReviewApp|%{stepStart}Step 4%{stepEnd}. Leave feedback in the Review App."
msgstr ""
 
msgid "VisualReviewApp|Cancel"
msgstr ""
msgid "VisualReviewApp|Copy merge request ID"
msgstr ""
 
Loading
Loading
@@ -22158,13 +22185,16 @@ msgstr ""
msgid "VisualReviewApp|Follow the steps below to enable Visual Reviews inside your application."
msgstr ""
 
msgid "VisualReviewApp|No review app found or available."
msgstr ""
msgid "VisualReviewApp|Open review app"
msgstr ""
 
msgid "VisualReviewApp|Review"
msgstr ""
 
msgid "VisualReviewApp|Steps 1 and 2 (and sometimes 3) are performed once by the developer before requesting feedback. Steps 3 (if necessary), 4, and 5 are performed by the reviewer each time they perform a review."
msgid "VisualReviewApp|Steps 1 and 2 (and sometimes 3) are performed once by the developer before requesting feedback. Steps 3 (if necessary), 4 is performed by the reviewer each time they perform a review."
msgstr ""
 
msgid "Vulnerabilities"
Loading
Loading
@@ -22682,6 +22712,9 @@ msgstr ""
msgid "You are receiving this message because you are a GitLab administrator for %{url}."
msgstr ""
 
msgid "You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico."
msgstr ""
msgid "You can %{linkStart}view the blob%{linkEnd} instead."
msgstr ""
 
Loading
Loading
@@ -23203,6 +23236,9 @@ msgid_plural "about %d hours"
msgstr[0] ""
msgstr[1] ""
 
msgid "activated"
msgstr ""
msgid "added %{created_at_timeago}"
msgstr ""
 
Loading
Loading
@@ -24258,6 +24294,9 @@ msgstr ""
msgid "security Reports|There was an error creating the merge request"
msgstr ""
 
msgid "settings saved, but not activated"
msgstr ""
msgid "severity|Critical"
msgstr ""
 
Loading
Loading
# frozen_string_literal: true
require 'spec_helper'
describe Admin::IntegrationsController do
let(:admin) { create(:admin) }
let!(:project) { create(:project) }
before do
sign_in(admin)
end
describe '#edit' do
context 'when instance_level_integrations not enabled' do
it 'returns not_found' do
allow(Feature).to receive(:enabled?).with(:instance_level_integrations) { false }
get :edit, params: { id: Service.available_services_names.sample }
expect(response).to have_gitlab_http_status(:not_found)
end
end
Service.available_services_names.each do |integration_name|
context "#{integration_name}" do
it 'successfully displays the template' do
get :edit, params: { id: integration_name }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:edit)
end
end
end
end
describe '#update' do
let(:integration) { create(:jira_service, project: project) }
before do
put :update, params: { id: integration.class.to_param, service: { url: url } }
end
context 'valid params' do
let(:url) { 'https://jira.gitlab-example.com' }
it 'updates the integration' do
expect(response).to have_gitlab_http_status(:found)
expect(integration.reload.url).to eq(url)
end
end
context 'invalid params' do
let(:url) { 'https://jira.localhost' }
it 'does not update the integration' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:edit)
expect(integration.reload.url).not_to eq(url)
end
end
end
describe '#test' do
context 'testable' do
let(:integration) { create(:jira_service, project: project) }
it 'returns ok' do
allow_any_instance_of(integration.class).to receive(:test) { { success: true } }
put :test, params: { id: integration.class.to_param }
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'not testable' do
let(:integration) { create(:alerts_service, project: project) }
it 'returns not found' do
put :test, params: { id: integration.class.to_param }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
Loading
Loading
@@ -5,6 +5,22 @@ require 'spec_helper'
describe Profiles::KeysController do
let(:user) { create(:user) }
 
describe 'POST #create' do
before do
sign_in(user)
end
it 'creates a new key' do
expires_at = 3.days.from_now
expect do
post :create, params: { key: build(:key, expires_at: expires_at).attributes }
end.to change { Key.count }.by(1)
expect(Key.last.expires_at).to be_like_time(expires_at)
end
end
describe "#get_keys" do
describe "non existent user" do
it "does not generally work" do
Loading
Loading
import { shallowMount } from '@vue/test-utils';
import IngressModsecuritySettings from '~/clusters/components/ingress_modsecurity_settings.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import { APPLICATION_STATUS, INGRESS } from '~/clusters/constants';
import { GlAlert } from '@gitlab/ui';
import { GlAlert, GlToggle } from '@gitlab/ui';
import eventHub from '~/clusters/event_hub';
 
const { UPDATING } = APPLICATION_STATUS;
Loading
Loading
@@ -27,32 +26,55 @@ describe('IngressModsecuritySettings', () => {
});
};
 
const findSaveButton = () => wrapper.find(LoadingButton);
const findModSecurityCheckbox = () => wrapper.find('input').element;
const findSaveButton = () => wrapper.find('.btn-success');
const findCancelButton = () => wrapper.find('[variant="secondary"]');
const findModSecurityToggle = () => wrapper.find(GlToggle);
 
describe('when ingress is installed', () => {
beforeEach(() => {
createComponent({ installed: true });
createComponent({ installed: true, status: 'installed' });
jest.spyOn(eventHub, '$emit');
});
 
it('renders save button', () => {
expect(findSaveButton().exists()).toBe(true);
expect(findModSecurityCheckbox().checked).toBe(false);
it('does not render save and cancel buttons', () => {
expect(findSaveButton().exists()).toBe(false);
expect(findCancelButton().exists()).toBe(false);
});
 
describe('and the save changes button is clicked', () => {
describe('with toggle changed by the user', () => {
beforeEach(() => {
findSaveButton().vm.$emit('click');
findModSecurityToggle().vm.$emit('change');
});
it('renders both save and cancel buttons', () => {
expect(findSaveButton().exists()).toBe(true);
expect(findCancelButton().exists()).toBe(true);
});
 
it('triggers save event and pass current modsecurity value', () =>
wrapper.vm.$nextTick().then(() => {
describe('and the save changes button is clicked', () => {
beforeEach(() => {
findSaveButton().vm.$emit('click');
});
it('triggers save event and pass current modsecurity value', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', {
id: INGRESS,
params: { modsecurity_enabled: false },
});
}));
});
});
describe('and the cancel button is clicked', () => {
beforeEach(() => {
findCancelButton().vm.$emit('click');
});
it('triggers reset event and hides both cancel and save changes button', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('resetIngressModSecurityEnabled', INGRESS);
expect(findSaveButton().exists()).toBe(false);
expect(findCancelButton().exists()).toBe(false);
});
});
});
 
it('triggers set event to be propagated with the current modsecurity value', () => {
Loading
Loading
@@ -79,7 +101,7 @@ describe('IngressModsecuritySettings', () => {
});
 
it('renders save button with "Saving" label', () => {
expect(findSaveButton().props('label')).toBe('Saving');
expect(findSaveButton().text()).toBe('Saving');
});
});
 
Loading
Loading
@@ -101,7 +123,7 @@ describe('IngressModsecuritySettings', () => {
 
it('does not render the save button', () => {
expect(findSaveButton().exists()).toBe(false);
expect(findModSecurityCheckbox().checked).toBe(false);
expect(findModSecurityToggle().props('value')).toBe(false);
});
});
});
Loading
Loading
@@ -20,6 +20,7 @@ const CLUSTERS_MOCK_DATA = {
external_ip: null,
external_hostname: null,
can_uninstall: false,
modsecurity_enabled: false,
},
{
name: 'runner',
Loading
Loading
Loading
Loading
@@ -7,6 +7,9 @@ exports[`JumpToNextDiscussionButton matches the snapshot 1`] = `
>
<button
class="btn btn-default discussion-next-btn"
data-track-event="click_button"
data-track-label="mr_next_unresolved_thread"
data-track-property="click_next_unresolved_thread"
title="Jump to next unresolved thread"
>
<icon-stub
Loading
Loading
import { shallowMount } from '@vue/test-utils';
import JumpToNextDiscussionButton from '~/notes/components/discussion_jump_to_next_button.vue';
import { mockTracking } from '../../helpers/tracking_helper';
 
describe('JumpToNextDiscussionButton', () => {
let wrapper;
const fromDiscussionId = 'abc123';
let wrapper;
let trackingSpy;
let jumpFn;
 
beforeEach(() => {
jumpFn = jest.fn();
wrapper = shallowMount(JumpToNextDiscussionButton, {
propsData: { fromDiscussionId },
});
wrapper.setMethods({ jumpToNextRelativeDiscussion: jumpFn });
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
});
 
afterEach(() => {
Loading
Loading
@@ -20,9 +27,17 @@ describe('JumpToNextDiscussionButton', () => {
});
 
it('calls jumpToNextRelativeDiscussion when clicked', () => {
const jumpToNextRelativeDiscussion = jest.fn();
wrapper.setMethods({ jumpToNextRelativeDiscussion });
wrapper.find({ ref: 'button' }).trigger('click');
expect(jumpToNextRelativeDiscussion).toHaveBeenCalledWith(fromDiscussionId);
expect(jumpFn).toHaveBeenCalledWith(fromDiscussionId);
});
it('sends the correct tracking event when clicked', () => {
wrapper.find({ ref: 'button' }).trigger('click');
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
label: 'mr_next_unresolved_thread',
property: 'click_next_unresolved_thread',
});
});
});
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Auth::KeyStatusChecker do
let_it_be(:never_expires_key) { build(:personal_key, expires_at: nil) }
let_it_be(:expired_key) { build(:personal_key, expires_at: 3.days.ago) }
let_it_be(:expiring_soon_key) { build(:personal_key, expires_at: 3.days.from_now) }
let_it_be(:expires_in_future_key) { build(:personal_key, expires_at: 14.days.from_now) }
let(:key_status_checker) { described_class.new(key) }
describe '#show_console_message?' do
subject { key_status_checker.show_console_message? }
context 'for an expired key' do
let(:key) { expired_key }
it { is_expected.to eq(true) }
end
context 'for a key expiring in the next 7 days' do
let(:key) { expiring_soon_key }
it { is_expected.to eq(true) }
end
context 'for a key expiring after the next 7 days' do
let(:key) { expires_in_future_key }
it { is_expected.to eq(false) }
end
context 'for a key that never expires' do
let(:key) { never_expires_key }
it { is_expected.to eq(false) }
end
end
describe '#console_message' do
subject { key_status_checker.console_message }
context 'for an expired key' do
let(:key) { expired_key }
it { is_expected.to eq('INFO: Your SSH key has expired. Please generate a new key.') }
end
context 'for a key expiring in the next 7 days' do
let(:key) { expiring_soon_key }
it { is_expected.to eq('INFO: Your SSH key is expiring soon. Please generate a new key.') }
end
context 'for a key expiring after the next 7 days' do
let(:key) { expires_in_future_key }
it { is_expected.to be_nil }
end
context 'for a key that never expires' do
let(:key) { never_expires_key }
it { is_expected.to be_nil }
end
end
end
Loading
Loading
@@ -177,6 +177,7 @@ describe Clusters::Applications::Ingress do
context 'when modsecurity_enabled is disabled' do
before do
allow(subject).to receive(:cluster).and_return(cluster)
allow(subject).to receive(:modsecurity_enabled).and_return(false)
end
 
it 'excludes modsecurity module enablement' do
Loading
Loading
Loading
Loading
@@ -575,30 +575,35 @@ describe API::Internal::Base do
project.add_developer(user)
end
 
context "git pull" do
context "with no console message" do
it "has the correct payload" do
context 'git pull' do
context 'with a key that has expired' do
let(:key) { create(:key, user: user, expires_at: 2.days.ago) }
it 'includes the `key expired` message in the response' do
pull(key, project)
 
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['gl_console_messages']).to eq([])
expect(json_response['gl_console_messages']).to eq(['INFO: Your SSH key has expired. Please generate a new key.'])
end
end
 
context "with a console message" do
let(:console_messages) { ['message for the console'] }
context 'with a key that will expire in the next 7 days' do
let(:key) { create(:key, user: user, expires_at: 2.days.from_now) }
 
it "has the correct payload" do
expect_next_instance_of(Gitlab::GitAccess) do |access|
expect(access).to receive(:check_for_console_messages)
.with('git-upload-pack')
.and_return(console_messages)
end
it 'includes the `key expiring soon` message in the response' do
pull(key, project)
 
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['gl_console_messages']).to eq(['INFO: Your SSH key is expiring soon. Please generate a new key.'])
end
end
context 'with a key that has no expiry' do
it 'does not include any message in the response' do
pull(key, project)
 
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['gl_console_messages']).to eq(console_messages)
expect(json_response['gl_console_messages']).to eq([])
end
end
end
Loading
Loading
Loading
Loading
@@ -5,7 +5,7 @@ require 'spec_helper'
describe API::Keys do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:key) { create(:key, user: user) }
let(:key) { create(:key, user: user, expires_at: 1.day.from_now) }
let(:email) { create(:email, user: user) }
 
describe 'GET /keys/:uid' do
Loading
Loading
@@ -28,6 +28,7 @@ describe API::Keys do
get api("/keys/#{key.id}", admin)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['title']).to eq(key.title)
expect(Time.parse(json_response['expires_at'])).to be_like_time(key.expires_at)
expect(json_response['user']['id']).to eq(user.id)
expect(json_response['user']['username']).to eq(user.username)
end
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment