Commit 8ac91ecf authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 7e208091
import sqljs from 'sql.js';
import { template as _template } from 'underscore';
import axios from '~/lib/utils/axios_utils';
import { successCodes } from '~/lib/utils/http_status';
const PREVIEW_TEMPLATE = _template(`
<div class="card">
......@@ -16,30 +18,25 @@ class BalsamiqViewer {
}
loadFile(endpoint) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', endpoint, true);
xhr.responseType = 'arraybuffer';
xhr.onload = loadEvent => this.fileLoaded(loadEvent, resolve, reject);
xhr.onerror = reject;
xhr.send();
});
}
fileLoaded(loadEvent, resolve, reject) {
if (loadEvent.target.status !== 200) return reject();
this.renderFile(loadEvent);
return resolve();
return axios
.get(endpoint, {
responseType: 'arraybuffer',
validateStatus(status) {
return status !== successCodes.OK;
},
})
.then(({ data }) => {
this.renderFile(data);
})
.catch(e => {
throw new Error(e);
});
}
renderFile(loadEvent) {
renderFile(fileBuffer) {
const container = document.createElement('ul');
this.initDatabase(loadEvent.target.response);
this.initDatabase(fileBuffer);
const previews = this.getPreviews();
previews.forEach(preview => {
......
......@@ -877,11 +877,16 @@ pre.light-well {
flex-direction: column;
// Disable Flexbox for admin page
&.admin-projects {
&.admin-projects,
&.group-settings-projects {
display: block;
.project-row {
display: block;
.description > p {
margin-bottom: 0;
}
}
}
......
......@@ -51,7 +51,8 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
new_file_anchor_data,
readme_anchor_data,
changelog_anchor_data,
contribution_guide_anchor_data
contribution_guide_anchor_data,
gitlab_ci_anchor_data
].compact.reject { |item| item.is_link }
end
......
- page_title "Projects"
- page_title _('Projects')
- params[:visibility_level] ||= []
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.prepend-top-default
%ul.nav-links.nav.nav-tabs
- opts = params[:visibility_level].present? ? {} : { page: admin_projects_path }
= nav_link(opts) do
= link_to _('All'), admin_projects_path
= nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do
= link_to _('Private'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
= nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do
= link_to _('Internal'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
= nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do
= link_to _('Public'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
.nav-controls
.search-holder
= render 'shared/projects/search_form', autofocus: true, admin_view: true
.dropdown
......@@ -22,20 +34,4 @@
New Project
= button_tag "Search", class: "btn btn-primary btn-search hide"
%ul.nav-links.nav.nav-tabs
- opts = params[:visibility_level].present? ? {} : { page: admin_projects_path }
= nav_link(opts) do
= link_to admin_projects_path do
All
= nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do
Private
= nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do
Internal
= nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
Public
= render 'projects'
......@@ -3,6 +3,7 @@
- breadcrumb_title @cluster.name
- page_title _('Kubernetes Cluster')
- manage_prometheus_path = edit_project_service_path(@cluster.project, 'prometheus') if @project
- cluster_environments_path = clusterable.environments_cluster_path(@cluster)
- expanded = expanded_by_default?
......@@ -16,7 +17,7 @@
install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative),
update_knative_path: clusterable.update_applications_cluster_path(@cluster, :knative),
cluster_environments_path: clusterable.environments_cluster_path(@cluster),
cluster_environments_path: cluster_environments_path,
toggle_status: @cluster.enabled? ? 'true': 'false',
has_rbac: has_rbac_enabled?(@cluster) ? 'true': 'false',
cluster_type: @cluster.cluster_type,
......@@ -37,7 +38,7 @@
%h4= @cluster.name
= render 'banner'
= render_if_exists 'clusters/clusters/group_cluster_environments', expanded: expanded
- unless Gitlab.ee?
- if cluster_environments_path.present?
= render_if_exists 'clusters/clusters/group_cluster_environments', expanded: expanded
- else
= render 'configure', expanded: expanded
......@@ -8,21 +8,38 @@
.controls
= link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do
New project
%ul.content-list
%ul.projects-list.content-list.group-settings-projects
- @projects.each do |project|
%li
.list-item-name
%span{ class: visibility_level_color(project.visibility_level) }
= visibility_level_icon(project.visibility_level)
%strong= link_to project.full_name, project
.float-right
%li.project-row{ class: ('no-description' if project.description.blank?) }
.controls
= link_to _('Members'), project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn"
= link_to _('Edit'), edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn"
= link_to _('Remove'), project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-remove"
.stats
%span.badge.badge-pill
= storage_counter(project.statistics&.storage_size)
- if project.archived
%span.badge.badge-warning archived
%span.badge.badge-pill
= storage_counter(project.statistics.storage_size)
= link_to 'Members', project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove"
.title
= link_to(project_path(project)) do
.dash-project-avatar
.avatar-container.rect-avatar.s40
= project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40)
%span.project-full-name
%span.namespace-name
- if project.namespace
= project.namespace.human_name
\/
%span.project-name
= project.name
%span{ class: visibility_level_color(project.visibility_level) }
= visibility_level_icon(project.visibility_level)
- if project.description.present?
.description
= markdown_field(project, :description)
- if @projects.blank?
.nothing-here-block This group has no projects yet
......
---
title: Improve UI for admin/projects and group/settings/projects pages
merge_request: 17247
author:
type: changed
---
title: Show the "Set up CI/CD" prompt in empty repositories when applicable.
merge_request: 17274
author: Ben McCormick
type: changed
......@@ -110,7 +110,7 @@ The following table lists available parameters for jobs:
| [`dependencies`](#dependencies) | Other jobs that a job depends on so that you can pass artifacts between them. |
| [`coverage`](#coverage) | Code coverage settings for a given job. |
| [`retry`](#retry) | When and how many times a job can be auto-retried in case of a failure. |
| [`timeout`](#timeout) | Define a custom timeout that would take precedence over the project-wide one. |
| [`timeout`](#timeout) | Define a custom job-level timeout that takes precedence over the project-wide setting. |
| [`parallel`](#parallel) | How many instances of a job should be run in parallel. |
| [`trigger`](#trigger-premium) | Defines a downstream pipeline trigger. |
| [`include`](#include) | Allows this job to include external YAML files. Also available: `include:local`, `include:file`, `include:template`, and `include:remote`. |
......@@ -1996,9 +1996,11 @@ Possible values for `when` are:
- `missing_dependency_failure`: Retry if a dependency was missing.
- `runner_unsupported`: Retry if the runner was unsupported.
### timeout
### `timeout`
`timeout` allows you to configure a timeout for a specific job:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/14887) in GitLab 12.3.
`timeout` allows you to configure a timeout for a specific job. For example:
```yaml
build:
......@@ -2129,7 +2131,7 @@ step-1:
stage: stage1
script:
- echo "Can be canceled"
step-2:
stage: stage2
script:
......
......@@ -19,7 +19,7 @@ CE specs should remain untouched as much as possible and extra specs
should be added for EE. Licensed features can be stubbed using the
spec helper `stub_licensed_features` in `EE::LicenseHelpers`.
You can force Webpack to act as CE by either deleting the `ee/` directory or by
You can force GitLab to act as CE by either deleting the `ee/` directory or by
setting the [`IS_GITLAB_EE` environment variable](https://gitlab.com/gitlab-org/gitlab/blob/master/config/helpers/is_ee_env.js)
to something that evaluates as `false`. The same works for running tests
(for example `IS_GITLAB_EE=0 yarn jest`).
......
......@@ -13,7 +13,7 @@ describe 'Clusterable > Show page' do
sign_in(current_user)
end
shared_examples 'editing domain' do
shared_examples 'show page' do
before do
clusterable.add_maintainer(current_user)
end
......@@ -53,6 +53,12 @@ describe 'Clusterable > Show page' do
end
end
end
it 'does not show the environments tab' do
visit cluster_path
expect(page).not_to have_selector('.js-cluster-nav-environments', text: 'Environments')
end
end
shared_examples 'editing a GCP cluster' do
......@@ -113,42 +119,30 @@ describe 'Clusterable > Show page' do
end
context 'when clusterable is a project' do
it_behaves_like 'editing domain' do
let(:clusterable) { create(:project) }
let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
let(:cluster_path) { project_cluster_path(clusterable, cluster) }
end
let(:clusterable) { create(:project) }
let(:cluster_path) { project_cluster_path(clusterable, cluster) }
let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
it_behaves_like 'editing a GCP cluster' do
let(:clusterable) { create(:project) }
let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
let(:cluster_path) { project_cluster_path(clusterable, cluster) }
end
it_behaves_like 'show page'
it_behaves_like 'editing a GCP cluster'
it_behaves_like 'editing a user-provided cluster' do
let(:clusterable) { create(:project) }
let(:cluster) { create(:cluster, :provided_by_user, :project, projects: [clusterable]) }
let(:cluster_path) { project_cluster_path(clusterable, cluster) }
end
end
context 'when clusterable is a group' do
it_behaves_like 'editing domain' do
let(:clusterable) { create(:group) }
let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) }
let(:cluster_path) { group_cluster_path(clusterable, cluster) }
end
let(:clusterable) { create(:group) }
let(:cluster_path) { group_cluster_path(clusterable, cluster) }
let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) }
it_behaves_like 'editing a GCP cluster' do
let(:clusterable) { create(:group) }
let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) }
let(:cluster_path) { group_cluster_path(clusterable, cluster) }
end
it_behaves_like 'show page'
it_behaves_like 'editing a GCP cluster'
it_behaves_like 'editing a user-provided cluster' do
let(:clusterable) { create(:group) }
let(:cluster) { create(:cluster, :provided_by_user, :group, groups: [clusterable]) }
let(:cluster_path) { group_cluster_path(clusterable, cluster) }
end
end
end
import sqljs from 'sql.js';
import axios from '~/lib/utils/axios_utils';
import BalsamiqViewer from '~/blob/balsamiq/balsamiq_viewer';
import ClassSpecHelper from '../../helpers/class_spec_helper';
describe('BalsamiqViewer', () => {
const mockArrayBuffer = new ArrayBuffer(10);
let balsamiqViewer;
let viewer;
......@@ -19,44 +21,65 @@ describe('BalsamiqViewer', () => {
});
describe('loadFile', () => {
let xhr;
let loadFile;
let bv;
const endpoint = 'endpoint';
const requestSuccess = Promise.resolve({
data: mockArrayBuffer,
status: 200,
});
beforeEach(() => {
xhr = jasmine.createSpyObj('xhr', ['open', 'send']);
viewer = {};
bv = new BalsamiqViewer(viewer);
});
balsamiqViewer = jasmine.createSpyObj('balsamiqViewer', ['renderFile']);
it('should call `axios.get` on `endpoint` param with responseType set to `arraybuffer', () => {
spyOn(axios, 'get').and.returnValue(requestSuccess);
spyOn(bv, 'renderFile').and.stub();
spyOn(window, 'XMLHttpRequest').and.returnValue(xhr);
bv.loadFile(endpoint);
loadFile = BalsamiqViewer.prototype.loadFile.call(balsamiqViewer, endpoint);
expect(axios.get).toHaveBeenCalledWith(
endpoint,
jasmine.objectContaining({
responseType: 'arraybuffer',
}),
);
});
it('should call .open', () => {
expect(xhr.open).toHaveBeenCalledWith('GET', endpoint, true);
});
it('should call `renderFile` on request success', done => {
spyOn(axios, 'get').and.returnValue(requestSuccess);
spyOn(bv, 'renderFile').and.callFake(() => {});
it('should set .responseType', () => {
expect(xhr.responseType).toBe('arraybuffer');
bv.loadFile(endpoint)
.then(() => {
expect(bv.renderFile).toHaveBeenCalledWith(mockArrayBuffer);
})
.then(done)
.catch(done.fail);
});
it('should call .send', () => {
expect(xhr.send).toHaveBeenCalled();
});
it('should not call `renderFile` on request failure', done => {
spyOn(axios, 'get').and.returnValue(Promise.reject());
spyOn(bv, 'renderFile');
it('should return a promise', () => {
expect(loadFile).toEqual(jasmine.any(Promise));
bv.loadFile(endpoint)
.then(() => {
done.fail('Expected loadFile to throw error!');
})
.catch(() => {
expect(bv.renderFile).not.toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
});
});
describe('renderFile', () => {
let container;
let loadEvent;
let previews;
beforeEach(() => {
loadEvent = { target: { response: {} } };
viewer = jasmine.createSpyObj('viewer', ['appendChild']);
previews = [document.createElement('ul'), document.createElement('ul')];
......@@ -73,11 +96,11 @@ describe('BalsamiqViewer', () => {
container = containerElement;
});
BalsamiqViewer.prototype.renderFile.call(balsamiqViewer, loadEvent);
BalsamiqViewer.prototype.renderFile.call(balsamiqViewer, mockArrayBuffer);
});
it('should call .initDatabase', () => {
expect(balsamiqViewer.initDatabase).toHaveBeenCalledWith(loadEvent.target.response);
expect(balsamiqViewer.initDatabase).toHaveBeenCalledWith(mockArrayBuffer);
});
it('should call .getPreviews', () => {
......
......@@ -430,4 +430,26 @@ describe ProjectPresenter do
)
end
end
describe '#empty_repo_statistics_buttons' do
let(:project) { create(:project, :repository) }
let(:presenter) { described_class.new(project, current_user: user) }
subject(:empty_repo_statistics_buttons) { presenter.empty_repo_statistics_buttons }
before do
project.add_developer(user)
allow(project).to receive(:auto_devops_enabled?).and_return(false)
end
it 'orders the items correctly in an empty project' do
expect(empty_repo_statistics_buttons.map(&:label)).to start_with(
a_string_including('New'),
a_string_including('README'),
a_string_including('CHANGELOG'),
a_string_including('CONTRIBUTING'),
a_string_including('CI/CD')
)
end
end
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