Commit 75ee59f7 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent e79918ce
......@@ -70,9 +70,10 @@ export default {
return this.logs.isLoading;
},
shouldShowElasticStackCallout() {
return (
!this.isElasticStackCalloutDismissed &&
(this.environments.isLoading || !this.showAdvancedFilters)
return !(
this.environments.isLoading ||
this.isElasticStackCalloutDismissed ||
this.showAdvancedFilters
);
},
},
......@@ -120,7 +121,8 @@ export default {
<div class="environment-logs-viewer d-flex flex-column py-3">
<gl-alert
v-if="shouldShowElasticStackCallout"
class="mb-3 js-elasticsearch-alert"
ref="elasticsearchNotice"
class="mb-3"
@dismiss="isElasticStackCalloutDismissed = true"
>
{{
......
<script>
import { __ } from '~/locale';
import { GlIcon, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import CiIcon from '../../vue_shared/components/ci_icon.vue';
import flash from '~/flash';
import Poll from '~/lib/utils/poll';
import Visibility from 'visibilityjs';
export default {
name: 'MRWidgetTerraformPlan',
components: {
CiIcon,
GlIcon,
GlLoadingIcon,
GlSprintf,
},
props: {
endpoint: {
type: String,
required: true,
},
},
data() {
return {
loading: true,
plans: {},
};
},
computed: {
addNum() {
return Number(this.plan.create);
},
changeNum() {
return Number(this.plan.update);
},
deleteNum() {
return Number(this.plan.delete);
},
iconStatusObj() {
return {
group: 'warning',
icon: 'status_warning',
};
},
logUrl() {
return this.plan.job_path;
},
plan() {
return this.plans['tfplan.json'] || {};
},
validPlanValues() {
return this.addNum + this.changeNum + this.deleteNum >= 0;
},
},
created() {
this.fetchPlans();
},
methods: {
fetchPlans() {
this.loading = true;
const poll = new Poll({
resource: {
fetchPlans: () => axios.get(this.endpoint),
},
data: this.endpoint,
method: 'fetchPlans',
successCallback: ({ data }) => {
this.plans = data;
this.loading = false;
},
errorCallback: () => {
this.plans = {};
this.loading = false;
flash(__('An error occurred while loading terraform report'));
},
});
if (!Visibility.hidden()) {
poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
poll.restart();
} else {
poll.stop();
}
});
},
},
};
</script>
<template>
<section class="mr-widget-section">
<div class="mr-widget-body media d-flex flex-row">
<span class="append-right-default align-self-start align-self-lg-center">
<ci-icon :status="iconStatusObj" :size="24" />
</span>
<div class="d-flex flex-fill flex-column flex-md-row">
<div class="terraform-mr-plan-text normal d-flex flex-column flex-lg-row">
<p class="m-0 pr-1">{{ __('A terraform report was generated in your pipelines.') }}</p>
<gl-loading-icon v-if="loading" size="md" />
<p v-else-if="validPlanValues" class="m-0">
<gl-sprintf
:message="
__(
'Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete',
)
"
>
<template #addNum>
<strong>{{ addNum }}</strong>
</template>
<template #changeNum>
<strong>{{ changeNum }}</strong>
</template>
<template #deleteNum>
<strong>{{ deleteNum }}</strong>
</template>
</gl-sprintf>
</p>
<p v-else class="m-0">{{ __('Changes are unknown') }}</p>
</div>
<div class="terraform-mr-plan-actions">
<a
v-if="logUrl"
:href="logUrl"
target="_blank"
data-track-event="click_terraform_mr_plan_button"
data-track-label="mr_widget_terraform_mr_plan_button"
data-track-property="terraform_mr_plan_button"
class="btn btn-sm js-terraform-report-link"
rel="noopener"
>
{{ __('View full log') }}
<gl-icon name="external-link" />
</a>
</div>
</div>
</div>
</section>
</template>
......@@ -36,6 +36,7 @@ import CheckingState from './components/states/mr_widget_checking.vue';
import eventHub from './event_hub';
import notify from '~/lib/utils/notify';
import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue';
import TerraformPlan from './components/mr_widget_terraform_plan.vue';
import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue';
import { setFaviconOverlay } from '../lib/utils/common_utils';
......@@ -74,6 +75,7 @@ export default {
'mr-widget-rebase': RebaseState,
SourceBranchRemovalStatus,
GroupedTestReportsApp,
TerraformPlan,
},
props: {
mrData: {
......@@ -379,6 +381,8 @@ export default {
:endpoint="mr.testResultsPath"
/>
<terraform-plan v-if="mr.terraformReportsPath" :endpoint="mr.terraformReportsPath" />
<div class="mr-widget-section">
<component :is="componentName" :mr="mr" :service="service" />
......
......@@ -101,6 +101,7 @@ export default class MergeRequestStore {
this.isPipelineActive = data.pipeline ? data.pipeline.active : false;
this.isPipelineBlocked = pipelineStatus ? pipelineStatus.group === 'manual' : false;
this.ciStatusFaviconPath = pipelineStatus ? pipelineStatus.favicon : null;
this.terraformReportsPath = data.terraform_reports_path;
this.testResultsPath = data.test_reports_path;
this.exposedArtifactsPath = data.exposed_artifacts_path;
this.cancelAutoMergePath = data.cancel_auto_merge_path;
......
# frozen_string_literal: true
class Projects::StaticSiteEditorController < Projects::ApplicationController
include ExtractsPath
layout 'fullscreen'
prepend_before_action :authenticate_user!, only: [:show]
before_action :assign_ref_and_path, only: [:show]
def show
@config = Gitlab::StaticSiteEditor::Config.new(@repository, @ref, @path, params[:return_url])
end
private
def assign_ref_and_path
@ref, @path = extract_ref(params[:id])
render_404 if @ref.blank? || @path.blank?
end
end
......@@ -12,7 +12,8 @@ module Metrics
STAGES::CommonMetricsInserter,
STAGES::EndpointInserter,
STAGES::PanelIdsInserter,
STAGES::Sorter
STAGES::Sorter,
STAGES::AlertsInserter
].freeze
def get_dashboard
......@@ -117,5 +118,3 @@ module Metrics
end
end
end
Metrics::Dashboard::BaseService.prepend_if_ee('EE::Metrics::Dashboard::BaseService')
......@@ -14,7 +14,8 @@ module Metrics
STAGES::CustomMetricsDetailsInserter,
STAGES::EndpointInserter,
STAGES::PanelIdsInserter,
STAGES::Sorter
STAGES::Sorter,
STAGES::AlertsInserter
].freeze
class << self
......@@ -30,5 +31,3 @@ module Metrics
end
end
end
Metrics::Dashboard::SystemDashboardService.prepend_if_ee('EE::Metrics::Dashboard::SystemDashboardService')
#static-site-editor{ data: { project_id: '8', path: 'README.md' } }
#static-site-editor{ data: @config.payload }
......@@ -18,7 +18,12 @@ module ProjectImportOptions
"import"
end
project.import_state.mark_as_failed(_("Every %{action} attempt has failed: %{job_error_message}. Please try again.") % { action: action, job_error_message: job['error_message'] })
if project.jira_import?
project.latest_jira_import.do_fail!
else
project.import_state.mark_as_failed(_("Every %{action} attempt has failed: %{job_error_message}. Please try again.") % { action: action, job_error_message: job['error_message'] })
end
Sidekiq.logger.warn "Failed #{job['class']} with #{job['args']}: #{job['error_message']}"
end
end
......
---
title: Add terraform report to merge request widget
merge_request: 27700
author:
type: added
---
title: Provide configuration options for Static Site Editor
merge_request: 29058
author:
type: added
---
title: Elasticsearch recommendation alert does not appears while screen is loaded
merge_request: 29097
author:
type: fixed
# frozen_string_literal: true
require 'set'
module Gitlab
module Metrics
module Dashboard
module Stages
class AlertsInserter < BaseStage
include ::Gitlab::Utils::StrongMemoize
def transform!
return if metrics_with_alerts.empty?
for_metrics do |metric|
next unless metrics_with_alerts.include?(metric[:metric_id])
metric[:alert_path] = alert_path(metric[:metric_id], project, params[:environment])
end
end
private
def metrics_with_alerts
strong_memoize(:metrics_with_alerts) do
alerts = ::Projects::Prometheus::AlertsFinder
.new(project: project, environment: params[:environment])
.execute
Set.new(alerts.map(&:prometheus_metric_id))
end
end
def alert_path(metric_id, project, environment)
::Gitlab::Routing.url_helpers.project_prometheus_alert_path(project, metric_id, environment_id: environment.id, format: :json)
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module StaticSiteEditor
class Config
def initialize(repository, ref, file_path, return_url)
@repository = repository
@ref = ref
@file_path = file_path
@return_url = return_url
end
def payload
{
branch: ref,
path: file_path,
commit: commit.id,
project_id: project.id,
project: project.path,
namespace: project.namespace.path,
return_url: return_url
}
end
private
attr_reader :repository, :ref, :file_path, :return_url
delegate :project, to: :repository
def commit
repository.commit(ref)
end
end
end
end
......@@ -888,6 +888,9 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr ""
msgid "A terraform report was generated in your pipelines."
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
......@@ -2008,6 +2011,9 @@ msgstr ""
msgid "An error occurred while loading merge requests."
msgstr ""
msgid "An error occurred while loading terraform report"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
msgstr ""
......@@ -3526,6 +3532,9 @@ msgstr ""
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
msgid "Changes are unknown"
msgstr ""
msgid "Changes suppressed. Click to show."
msgstr ""
......@@ -15632,6 +15641,12 @@ msgstr ""
msgid "Project name"
msgstr ""
msgid "Project name suffix"
msgstr ""
msgid "Project name suffix is a user-defined string which will be appended to the project path, and will form the Service Desk email address."
msgstr ""
msgid "Project order will not be saved as local storage is not available."
msgstr ""
......@@ -17019,6 +17034,9 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
msgstr ""
msgid "Reporter"
msgstr ""
......@@ -22694,6 +22712,9 @@ msgstr ""
msgid "View full dashboard"
msgstr ""
msgid "View full log"
msgstr ""
msgid "View group labels"
msgstr ""
......
......@@ -10,7 +10,8 @@ describe Projects::StaticSiteEditorController do
{
namespace_id: project.namespace,
project_id: project,
id: 'master/README.md'
id: 'master/README.md',
return_url: 'http://example.com'
}
end
......@@ -38,6 +39,18 @@ describe Projects::StaticSiteEditorController do
it 'renders the edit page' do
expect(response).to render_template(:show)
end
it 'assigns a config variable' do
expect(assigns(:config)).to be_a(Gitlab::StaticSiteEditor::Config)
end
context 'when combination of ref and file path is incorrect' do
let(:default_params) { super().merge(id: 'unknown') }
it 'responds with 404 page' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
end
......
......@@ -43,7 +43,7 @@ describe('EnvironmentLogs', () => {
const findSimpleFilters = () => wrapper.find({ ref: 'log-simple-filters' });
const findAdvancedFilters = () => wrapper.find({ ref: 'log-advanced-filters' });
const findInfoAlert = () => wrapper.find('.js-elasticsearch-alert');
const findElasticsearchNotice = () => wrapper.find({ ref: 'elasticsearchNotice' });
const findLogControlButtons = () => wrapper.find({ name: 'log-control-buttons-stub' });
const findInfiniteScroll = () => wrapper.find({ ref: 'infiniteScroll' });
......@@ -160,6 +160,10 @@ describe('EnvironmentLogs', () => {
initWrapper();
});
it('does not display an alert to upgrade to ES', () => {
expect(findElasticsearchNotice().exists()).toBe(false);
});
it('displays a disabled environments dropdown', () => {
expect(findEnvironmentsDropdown().attributes('disabled')).toBe('true');
expect(findEnvironmentsDropdown().findAll(GlDropdownItem).length).toBe(0);
......@@ -204,7 +208,7 @@ describe('EnvironmentLogs', () => {
});
it('displays an alert to upgrade to ES', () => {
expect(findInfoAlert().exists()).toBe(true);
expect(findElasticsearchNotice().exists()).toBe(true);
});
it('displays simple filters for kubernetes logs API', () => {
......@@ -235,7 +239,7 @@ describe('EnvironmentLogs', () => {
});
it('does not display an alert to upgrade to ES', () => {
expect(findInfoAlert().exists()).toBe(false);
expect(findElasticsearchNotice().exists()).toBe(false);
});
it('populates environments dropdown', () => {
......
import { GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
import MrWidgetTerraformPlan from '~/vue_merge_request_widget/components/mr_widget_terraform_plan.vue';
const plan = {
create: 10,
update: 20,
delete: 30,
job_path: '/path/to/ci/logs',
};
describe('MrWidgetTerraformPlan', () => {
let mock;
let wrapper;
const propsData = { endpoint: '/path/to/terraform/report.json' };
const mockPollingApi = (response, body, header) => {
mock.onGet(propsData.endpoint).reply(response, body, header);
};
const mountWrapper = () => {
wrapper = shallowMount(MrWidgetTerraformPlan, { propsData });
return axios.waitForAll();
};
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
wrapper.destroy();
mock.restore();
});
describe('loading poll', () => {
beforeEach(() => {
mockPollingApi(200, { 'tfplan.json': plan }, {});
return mountWrapper().then(() => {
wrapper.setData({ loading: true });
return wrapper.vm.$nextTick();
});
});
it('Diplays loading icon when loading is true', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.find(GlSprintf).exists()).toBe(false);
expect(wrapper.text()).not.toContain(
'A terraform report was generated in your pipelines. Changes are unknown',
);
});
});
describe('successful poll', () => {
beforeEach(() => {
mockPollingApi(200, { 'tfplan.json': plan }, {});
return mountWrapper();
});
it('content change text', () => {
expect(wrapper.find(GlSprintf).exists()).toBe(true);
});
it('renders button when url is found', () => {
expect(wrapper.find('a').text()).toContain('View full log');
});
});
describe('polling fails', () => {
beforeEach(() => {
mockPollingApi(500, null, {});
return mountWrapper();
});
it('does not display changes text when api fails', () => {
expect(wrapper.text()).toContain(
'A terraform report was generated in your pipelines. Changes are unknown',
);
expect(wrapper.find('.js-terraform-report-link').exists()).toBe(false);
expect(wrapper.text()).not.toContain('View full log');
});
});
});
......@@ -14,9 +14,11 @@ describe Gitlab::Metrics::Dashboard::Processor do
Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::CustomMetricsDetailsInserter,
Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter
Gitlab::Metrics::Dashboard::Stages::Sorter,
Gitlab::Metrics::Dashboard::Stages::AlertsInserter
]
end
let(:process_params) { [project, dashboard_yml, sequence, { environment: environment }] }
let(:dashboard) { described_class.new(*process_params).process }
......@@ -113,6 +115,54 @@ describe Gitlab::Metrics::Dashboard::Processor do
end
end
context 'when the dashboard references persisted metrics with alerts' do
let!(:alert) do
create(
:prometheus_alert,
environment: environment,
project: project,
prometheus_metric: persisted_metric
)
end
shared_examples_for 'has saved alerts' do
it 'includes an alert path' do
target_metric = all_metrics.find { |metric| metric[:metric_id] == persisted_metric.id }
expect(target_metric).to be_a Hash
expect(target_metric).to include(:alert_path)
expect(target_metric[:alert_path]).to include(
project.path,
persisted_metric.id.to_s,
environment.id.to_s
)
end
end
context 'that are shared across projects' do
let!(:persisted_metric) { create(:prometheus_metric, :common, identifier: 'metric_a1') }
it_behaves_like 'has saved alerts'
end
context 'when the project has associated metrics' do
let!(:persisted_metric) { create(:prometheus_metric, project: project, group: :business) }
it_behaves_like 'has saved alerts'
end
end
context 'when there are no alerts' do
let!(:persisted_metric) { create(:prometheus_metric, :common, identifier: 'metric_a1') }
it 'does not insert an alert_path' do
target_metric = all_metrics.find { |metric| metric[:metric_id] == persisted_metric.id }
expect(target_metric).to be_a Hash
expect(target_metric).not_to include(:alert_path)
end
end
shared_examples_for 'errors with message' do |expected_message|
it 'raises a DashboardLayoutError' do
error_class = Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::StaticSiteEditor::Config do
subject(:config) { described_class.new(repository, ref, file_path, return_url) }
let(:project) { create(:project, :public, :repository, name: 'project', namespace: namespace) }
let(:namespace) { create(:namespace, name: 'namespace') }
let(:repository) { project.repository }
let(:ref) { 'master' }
let(:file_path) { 'README.md' }
let(:return_url) { 'http://example.com' }
describe '#payload' do
subject { config.payload }
it 'returns data for the frontend component' do
is_expected.to eq(
branch: 'master',
commit: repository.commit.id,
namespace: 'namespace',
path: 'README.md',
project: 'project',
project_id: project.id,
return_url: 'http://example.com'
)
end
end
end
......@@ -39,6 +39,17 @@ describe ProjectImportOptions do
expect(project.import_state.reload.last_error).to include("import")
end
context 'when project is jira import' do
let(:project) { create(:project, import_type: 'jira') }
let!(:jira_import) { create(:jira_import_state, project: project) }
it 'logs the appropriate error message for forked projects' do
worker_class.sidekiq_retries_exhausted_block.call(job)
expect(project.latest_jira_import.reload.status).to eq('failed')
end
end
context 'when project does not have import_state' do
let(:project) { create(:project) }
......
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