Commit 0e68afab authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 33aa02e7
......@@ -410,13 +410,10 @@ RSpec/RepeatedExample:
- 'spec/models/ability_spec.rb'
- 'spec/models/ci/build_spec.rb'
- 'spec/models/concerns/issuable_spec.rb'
- 'spec/models/member_spec.rb'
- 'spec/models/project_services/chat_message/pipeline_message_spec.rb'
- 'spec/models/wiki_page_spec.rb'
- 'spec/routing/admin_routing_spec.rb'
- 'spec/rubocop/cop/migration/update_large_table_spec.rb'
- 'spec/services/notification_service_spec.rb'
- 'spec/services/web_hook_service_spec.rb'
- 'ee/spec/models/group_spec.rb'
- 'ee/spec/services/boards/lists/update_service_spec.rb'
- 'ee/spec/services/geo/repository_verification_primary_service_spec.rb'
......@@ -250,7 +250,7 @@ export default {
},
},
created() {
this.setEndpoints({
this.setInitialState({
metricsEndpoint: this.metricsEndpoint,
deploymentsEndpoint: this.deploymentsEndpoint,
dashboardEndpoint: this.dashboardEndpoint,
......@@ -258,6 +258,7 @@ export default {
currentDashboard: this.currentDashboard,
projectPath: this.projectPath,
logsPath: this.logsPath,
currentEnvironmentName: this.currentEnvironmentName,
});
},
mounted() {
......@@ -273,7 +274,7 @@ export default {
'setTimeRange',
'fetchData',
'setGettingStartedEmptyState',
'setEndpoints',
'setInitialState',
'setPanelGroupMetrics',
'filterEnvironments',
]),
......
......@@ -64,7 +64,10 @@ export default {
},
},
mounted() {
this.setInitialState();
this.setInitialState({
dashboardEndpoint: removeTimeRangeParams(this.dashboardUrl),
});
this.setShowErrorBanner(false);
this.setTimeRange(this.timeRange);
this.fetchDashboard();
......@@ -90,11 +93,8 @@ export default {
fetchDashboard(dispatch, payload) {
return dispatch(`${this.namespace}/fetchDashboard`, payload);
},
setEndpoints(dispatch, payload) {
return dispatch(`${this.namespace}/setEndpoints`, payload);
},
setFeatureFlags(dispatch, payload) {
return dispatch(`${this.namespace}/setFeatureFlags`, payload);
setInitialState(dispatch, payload) {
return dispatch(`${this.namespace}/setInitialState`, payload);
},
setShowErrorBanner(dispatch, payload) {
return dispatch(`${this.namespace}/setShowErrorBanner`, payload);
......@@ -108,12 +108,6 @@ export default {
this.elWidth = this.$el.clientWidth;
}, sidebarAnimationDuration);
},
setInitialState() {
this.setEndpoints({
dashboardEndpoint: removeTimeRangeParams(this.dashboardUrl),
});
this.setShowErrorBanner(false);
},
},
};
</script>
......
......@@ -79,3 +79,28 @@ export const dateFormats = {
timeOfDay: 'h:MM TT',
default: 'dd mmm yyyy, h:MMTT',
};
/**
* These Vuex store properties are allowed to be
* replaced dynamically after component has been created
* and initial state has been set.
*
* Currently used in `receiveMetricsDashboardSuccess` action.
*/
export const endpointKeys = [
'metricsEndpoint',
'deploymentsEndpoint',
'dashboardEndpoint',
'dashboardsEndpoint',
'currentDashboard',
'projectPath',
'logsPath',
];
/**
* These Vuex store properties are set as soon as the
* dashboard component has been created. The values are
* passed as data-* attributes and received by dashboard
* as Vue props.
*/
export const initialStateKeys = [...endpointKeys, 'currentEnvironmentName'];
......@@ -30,8 +30,8 @@ export const setGettingStartedEmptyState = ({ commit }) => {
commit(types.SET_GETTING_STARTED_EMPTY_STATE);
};
export const setEndpoints = ({ commit }, endpoints) => {
commit(types.SET_ENDPOINTS, endpoints);
export const setInitialState = ({ commit }, initialState) => {
commit(types.SET_INITIAL_STATE, initialState);
};
export const setTimeRange = ({ commit }, timeRange) => {
......
......@@ -17,6 +17,7 @@ export const RECEIVE_METRIC_RESULT_FAILURE = 'RECEIVE_METRIC_RESULT_FAILURE';
export const SET_TIME_RANGE = 'SET_TIME_RANGE';
export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
......
......@@ -3,7 +3,7 @@ import pick from 'lodash/pick';
import * as types from './mutation_types';
import { mapToDashboardViewModel, normalizeQueryResult } from './utils';
import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils';
import { metricStates } from '../constants';
import { endpointKeys, initialStateKeys, metricStates } from '../constants';
import httpStatusCodes from '~/lib/utils/http_status';
/**
......@@ -150,19 +150,11 @@ export default {
state: emptyStateFromError(error),
});
},
[types.SET_INITIAL_STATE](state, initialState = {}) {
Object.assign(state, pick(initialState, initialStateKeys));
},
[types.SET_ENDPOINTS](state, endpoints = {}) {
const endpointKeys = [
'metricsEndpoint',
'deploymentsEndpoint',
'dashboardEndpoint',
'dashboardsEndpoint',
'currentDashboard',
'projectPath',
'logsPath',
];
Object.entries(pick(endpoints, endpointKeys)).forEach(([key, value]) => {
state[key] = value;
});
Object.assign(state, pick(endpoints, endpointKeys));
},
[types.SET_TIME_RANGE](state, timeRange) {
state.timeRange = timeRange;
......
......@@ -103,7 +103,7 @@ export default {
<div v-else-if="shouldRenderSuccessState" class="js-success-state">
<release-block
v-for="(release, index) in releases"
:key="release.tagName"
:key="index"
:release="release"
:class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }"
/>
......
......@@ -37,7 +37,11 @@ export default {
};
},
computed: {
id() {
htmlId() {
if (!this.release.tagName) {
return null;
}
return slugify(this.release.tagName);
},
assets() {
......@@ -72,7 +76,7 @@ export default {
this.renderGFM();
const hash = getLocationHash();
if (hash && slugify(hash) === this.id) {
if (hash && slugify(hash) === this.htmlId) {
this.isHighlighted = true;
setTimeout(() => {
this.isHighlighted = false;
......@@ -89,7 +93,7 @@ export default {
};
</script>
<template>
<div :id="id" :class="{ 'bg-line-target-blue': isHighlighted }" class="card release-block">
<div :id="htmlId" :class="{ 'bg-line-target-blue': isHighlighted }" class="card release-block">
<release-block-header :release="release" />
<div class="card-body">
<div v-if="shouldRenderMilestoneInfo">
......
......@@ -15,7 +15,7 @@ class ReactiveCachingWorker # rubocop:disable Scalability/IdempotentWorker
def self.context_for_arguments(arguments)
class_name, *_other_args = arguments
Gitlab::ApplicationContext.new(related_class: class_name)
Gitlab::ApplicationContext.new(related_class: class_name.to_s)
end
def perform(class_name, id, *args)
......
---
title: Fix Releases page for Guest users of private projects
merge_request: 28447
author:
type: fixed
---
title: Refactor duplicate member specs
merge_request: 28574
author: Rajendra Kadam
type: added
---
title: Refactor duplicate specs in wiki page specs
merge_request: 28551
author: Rajendra Kadam
type: added
......@@ -1460,6 +1460,15 @@ Example response:
```
````
### Fake user information
You may need to demonstrate an API call or a cURL command that includes the name
and email address of a user. Don't use real user information in API calls:
- **Email addresses**: Use an email address ending in `example.com`.
- **Names**: Use strings like `Example Username`. Alternatively, use diverse or non-gendered names with
common surnames, such as `Sidney Jones`, `Zhang Wei`. or `Maria Garcia`.
### Fake tokens
There may be times where a token is needed to demonstrate an API call using
......
......@@ -170,6 +170,7 @@ using environment variables.
| Environment Variable | Description | Default |
| ------ | ------ | ------ |
| `KLAR_TRACE` | Set to true to enable more verbose output from klar. | `"false"` |
| `CLAIR_TRACE` | Set to true to enable more verbose output from the clair server process. | `"false"` |
| `DOCKER_USER` | Username for accessing a Docker registry requiring authentication. | `$CI_REGISTRY_USER` |
| `DOCKER_PASSWORD` | Password for accessing a Docker registry requiring authentication. | `$CI_REGISTRY_PASSWORD` |
| `CLAIR_OUTPUT` | Severity level threshold. Vulnerabilities with severity level higher than or equal to this threshold will be outputted. Supported levels are `Unknown`, `Negligible`, `Low`, `Medium`, `High`, `Critical` and `Defcon1`. | `Unknown` |
......
......@@ -168,15 +168,15 @@ requires [GraphQL](../../../api/graphql/index.md) to be enabled.
### Status **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/36427) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.9.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/36427) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
To help you track the status of your issues, you can assign a status to each issue to flag work that's progressing as planned or needs attention to keep on schedule:
- `On track` (green)
- `Needs attention` (amber)
- `At risk` (red)
- **On track** (green)
- **Needs attention** (amber)
- **At risk** (red)
!["On track" health status on an issue](img/issue_health_status_v12_9.png)
!["On track" health status on an issue](img/issue_health_status_v12_10.png)
---
......
......@@ -24701,6 +24701,9 @@ msgstr ""
msgid "remove due date"
msgstr ""
msgid "remove status"
msgstr ""
msgid "remove weight"
msgstr ""
......
......@@ -51,9 +51,7 @@ module QA
metadata[:type] = :feature
end
config.around(:each) do |example|
example.run
config.append_after(:each) do |example|
if example.metadata[:screenshot]
screenshot = example.metadata[:screenshot][:image] || example.metadata[:screenshot][:html]
example.metadata[:stdout] = %{[[ATTACHMENT|#{screenshot}]]}
......
......@@ -3,41 +3,33 @@
require 'spec_helper'
describe 'User views releases', :js do
let!(:project) { create(:project, :repository) }
let!(:release) { create(:release, project: project ) }
let!(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:release) { create(:release, project: project, name: 'The first release' ) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:guest) { create(:user) }
before do
project.add_maintainer(user)
gitlab_sign_in(user)
project.add_maintainer(maintainer)
project.add_guest(guest)
end
it 'sees the release' do
visit project_releases_path(project)
expect(page).to have_content(release.name)
expect(page).to have_content(release.tag)
expect(page).not_to have_content('Upcoming Release')
end
context 'when there is a link as an asset' do
let!(:release_link) { create(:release_link, release: release, url: url ) }
let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release) << release_link.filepath }
context('when the user is a maintainer') do
before do
gitlab_sign_in(maintainer)
end
it 'sees the link' do
it 'sees the release' do
visit project_releases_path(project)
page.within('.js-assets-list') do
expect(page).to have_link release_link.name, href: direct_asset_link
expect(page).not_to have_content('(external source)')
end
expect(page).to have_content(release.name)
expect(page).to have_content(release.tag)
expect(page).not_to have_content('Upcoming Release')
end
context 'when there is a link redirect' do
let!(:release_link) { create(:release_link, release: release, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) }
context 'when there is a link as an asset' do
let!(:release_link) { create(:release_link, release: release, url: url ) }
let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release) << release_link.filepath }
it 'sees the link' do
visit project_releases_path(project)
......@@ -47,39 +39,73 @@ describe 'User views releases', :js do
expect(page).not_to have_content('(external source)')
end
end
end
context 'when url points to external resource' do
let(:url) { 'http://google.com/download' }
context 'when there is a link redirect' do
let!(:release_link) { create(:release_link, release: release, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) }
let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
it 'sees that the link is external resource' do
visit project_releases_path(project)
it 'sees the link' do
visit project_releases_path(project)
page.within('.js-assets-list') do
expect(page).to have_content('(external source)')
page.within('.js-assets-list') do
expect(page).to have_link release_link.name, href: direct_asset_link
expect(page).not_to have_content('(external source)')
end
end
end
context 'when url points to external resource' do
let(:url) { 'http://google.com/download' }
it 'sees that the link is external resource' do
visit project_releases_path(project)
page.within('.js-assets-list') do
expect(page).to have_content('(external source)')
end
end
end
end
end
context 'with an upcoming release' do
let(:tomorrow) { Time.zone.now + 1.day }
let!(:release) { create(:release, project: project, released_at: tomorrow ) }
context 'with an upcoming release' do
let(:tomorrow) { Time.zone.now + 1.day }
let!(:release) { create(:release, project: project, released_at: tomorrow ) }
it 'sees the upcoming tag' do
visit project_releases_path(project)
it 'sees the upcoming tag' do
visit project_releases_path(project)
expect(page).to have_content('Upcoming Release')
end
end
context 'with a tag containing a slash' do
it 'sees the release' do
release = create :release, :with_evidence, project: project, tag: 'debian/2.4.0-1'
visit project_releases_path(project)
expect(page).to have_content('Upcoming Release')
expect(page).to have_content(release.name)
expect(page).to have_content(release.tag)
end
end
end
context 'with a tag containing a slash' do
it 'sees the release' do
release = create :release, :with_evidence, project: project, tag: 'debian/2.4.0-1'
context('when the user is a guest') do
before do
gitlab_sign_in(guest)
end
it 'renders release info except for Git-related data' do
visit project_releases_path(project)
expect(page).to have_content(release.name)
expect(page).to have_content(release.tag)
within('.release-block') do
expect(page).to have_content(release.description)
# The following properties (sometimes) include Git info,
# so they are not rendered for Guest users
expect(page).not_to have_content(release.name)
expect(page).not_to have_content(release.tag)
expect(page).not_to have_content(release.commit.short_id)
end
end
end
end
......@@ -88,11 +88,17 @@ describe('Dashboard', () => {
expect(findEnvironmentsDropdown().exists()).toBe(true);
});
it('sets endpoints: logs path', () => {
expect(store.dispatch).toHaveBeenCalledWith(
'monitoringDashboard/setEndpoints',
expect.objectContaining({ logsPath: propsData.logsPath }),
);
it('sets initial state', () => {
expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setInitialState', {
currentDashboard: '',
currentEnvironmentName: 'production',
dashboardEndpoint: 'https://invalid',
dashboardsEndpoint: 'https://invalid',
deploymentsEndpoint: null,
logsPath: '/path/to/logs',
metricsEndpoint: 'http://test.host/monitoring/mock',
projectPath: '/path/to/project',
});
});
});
......
......@@ -26,9 +26,8 @@ describe('MetricEmbed', () => {
beforeEach(() => {
actions = {
setFeatureFlags: jest.fn(),
setInitialState: jest.fn(),
setShowErrorBanner: jest.fn(),
setEndpoints: jest.fn(),
setTimeRange: jest.fn(),
fetchDashboard: jest.fn(),
};
......
......@@ -16,7 +16,7 @@ import {
fetchEnvironmentsData,
fetchPrometheusMetrics,
fetchPrometheusMetric,
setEndpoints,
setInitialState,
filterEnvironments,
setGettingStartedEmptyState,
duplicateSystemDashboard,
......@@ -208,14 +208,14 @@ describe('Monitoring store actions', () => {
});
});
describe('Set endpoints', () => {
describe('Set initial state', () => {
let mockedState;
beforeEach(() => {
mockedState = storeState();
});
it('should commit SET_ENDPOINTS mutation', done => {
it('should commit SET_INITIAL_STATE mutation', done => {
testAction(
setEndpoints,
setInitialState,
{
metricsEndpoint: 'additional_metrics.json',
deploymentsEndpoint: 'deployments.json',
......@@ -223,7 +223,7 @@ describe('Monitoring store actions', () => {
mockedState,
[
{
type: types.SET_ENDPOINTS,
type: types.SET_INITIAL_STATE,
payload: {
metricsEndpoint: 'additional_metrics.json',
deploymentsEndpoint: 'deployments.json',
......
......@@ -86,6 +86,58 @@ describe('Monitoring mutations', () => {
expect(typeof stateCopy.deploymentData[0]).toEqual('object');
});
});
describe('SET_INITIAL_STATE', () => {
it('should set all the endpoints', () => {
mutations[types.SET_INITIAL_STATE](stateCopy, {
metricsEndpoint: 'additional_metrics.json',
deploymentsEndpoint: 'deployments.json',
dashboardEndpoint: 'dashboard.json',
projectPath: '/gitlab-org/gitlab-foss',
currentEnvironmentName: 'production',
});
expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json');
expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json');
expect(stateCopy.dashboardEndpoint).toEqual('dashboard.json');
expect(stateCopy.projectPath).toEqual('/gitlab-org/gitlab-foss');
expect(stateCopy.currentEnvironmentName).toEqual('production');
});
it('should not remove previously set properties', () => {
const defaultLogsPath = stateCopy.logsPath;
mutations[types.SET_INITIAL_STATE](stateCopy, {
logsPath: defaultLogsPath,
});
mutations[types.SET_INITIAL_STATE](stateCopy, {
dashboardEndpoint: 'dashboard.json',
});
mutations[types.SET_INITIAL_STATE](stateCopy, {
projectPath: '/gitlab-org/gitlab-foss',
});
mutations[types.SET_INITIAL_STATE](stateCopy, {
currentEnvironmentName: 'canary',
});
expect(stateCopy).toMatchObject({
logsPath: defaultLogsPath,
dashboardEndpoint: 'dashboard.json',
projectPath: '/gitlab-org/gitlab-foss',
currentEnvironmentName: 'canary',
});
});
it('should not update unknown properties', () => {
mutations[types.SET_INITIAL_STATE](stateCopy, {
dashboardEndpoint: 'dashboard.json',
someOtherProperty: 'some invalid value', // someOtherProperty is not allowed
});
expect(stateCopy.dashboardEndpoint).toBe('dashboard.json');
expect(stateCopy.someOtherProperty).toBeUndefined();
});
});
describe('SET_ENDPOINTS', () => {
it('should set all the endpoints', () => {
mutations[types.SET_ENDPOINTS](stateCopy, {
......
......@@ -165,6 +165,14 @@ describe('Release block', () => {
});
});
it('does not set the ID if tagName is missing', () => {
release.tagName = undefined;
return factory(release).then(() => {
expect(wrapper.attributes().id).toBeUndefined();
});
});
describe('evidence block', () => {
it('renders the evidence block when the evidence is available and the feature flag is true', () =>
factory(release, { releaseEvidenceCollection: true }).then(() =>
......
......@@ -38,10 +38,6 @@ describe Member do
expect(member).not_to be_valid
end
it "is valid otherwise" do
expect(member).to be_valid
end
end
context "when an invite email is not provided" do
......
......@@ -577,6 +577,8 @@ describe WikiPage do
end
it 'returns false when version is nil' do
expect(latest_page).to receive(:version) { nil }
expect(latest_page.historical?).to be_falsy
end
......
......@@ -37,5 +37,13 @@ describe ReactiveCachingWorker do
expect(scheduled_job).to include('meta.related_class' => 'Environment')
end
it 'sets the related class on the job when it was passed as a class' do
described_class.perform_async(Project, 1, 'other', 'argument')
scheduled_job = described_class.jobs.first
expect(scheduled_job).to include('meta.related_class' => 'Project')
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