Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
cb3e6b9c1b
commit
4c016ad024
31 changed files with 600 additions and 48 deletions
|
@ -0,0 +1,82 @@
|
||||||
|
<script>
|
||||||
|
import { GlButton, GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui';
|
||||||
|
import Icon from '~/vue_shared/components/icon.vue';
|
||||||
|
import { mapState, mapActions } from 'vuex';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
GlButton,
|
||||||
|
GlFormGroup,
|
||||||
|
GlFormInput,
|
||||||
|
GlLink,
|
||||||
|
Icon,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return { placeholderUrl: 'https://my-url.grafana.net/my-dashboard' };
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['operationsSettingsEndpoint', 'grafanaToken', 'grafanaUrl']),
|
||||||
|
localGrafanaToken: {
|
||||||
|
get() {
|
||||||
|
return this.grafanaToken;
|
||||||
|
},
|
||||||
|
set(token) {
|
||||||
|
this.setGrafanaToken(token);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
localGrafanaUrl: {
|
||||||
|
get() {
|
||||||
|
return this.grafanaUrl;
|
||||||
|
},
|
||||||
|
set(url) {
|
||||||
|
this.setGrafanaUrl(url);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['setGrafanaUrl', 'setGrafanaToken', 'updateGrafanaIntegration']),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section id="grafana" class="settings no-animate js-grafana-integration">
|
||||||
|
<div class="settings-header">
|
||||||
|
<h4 class="js-section-header">
|
||||||
|
{{ s__('GrafanaIntegration|Grafana Authentication') }}
|
||||||
|
</h4>
|
||||||
|
<gl-button class="js-settings-toggle">{{ __('Expand') }}</gl-button>
|
||||||
|
<p class="js-section-sub-header">
|
||||||
|
{{ s__('GrafanaIntegration|Embed Grafana charts in GitLab issues.') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="settings-content">
|
||||||
|
<form>
|
||||||
|
<gl-form-group
|
||||||
|
:label="s__('GrafanaIntegration|Grafana URL')"
|
||||||
|
label-for="grafana-url"
|
||||||
|
:description="s__('GrafanaIntegration|Enter the base URL of the Grafana instance.')"
|
||||||
|
>
|
||||||
|
<gl-form-input id="grafana-url" v-model="localGrafanaUrl" :placeholder="placeholderUrl" />
|
||||||
|
</gl-form-group>
|
||||||
|
<gl-form-group :label="s__('GrafanaIntegration|API Token')" label-for="grafana-token">
|
||||||
|
<gl-form-input id="grafana-token" v-model="localGrafanaToken" />
|
||||||
|
<p class="form-text text-muted">
|
||||||
|
{{ s__('GrafanaIntegration|Enter the Grafana API Token.') }}
|
||||||
|
<a
|
||||||
|
href="https://grafana.com/docs/http_api/auth/#create-api-token"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{{ __('More information') }}
|
||||||
|
<icon name="external-link" class="vertical-align-middle" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</gl-form-group>
|
||||||
|
<gl-button variant="success" @click="updateGrafanaIntegration">
|
||||||
|
{{ __('Save Changes') }}
|
||||||
|
</gl-button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
15
app/assets/javascripts/grafana_integration/index.js
Normal file
15
app/assets/javascripts/grafana_integration/index.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import store from './store';
|
||||||
|
import GrafanaIntegration from './components/grafana_integration.vue';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const el = document.querySelector('.js-grafana-integration');
|
||||||
|
|
||||||
|
return new Vue({
|
||||||
|
el,
|
||||||
|
store: store(el.dataset),
|
||||||
|
render(createElement) {
|
||||||
|
return createElement(GrafanaIntegration);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
38
app/assets/javascripts/grafana_integration/store/actions.js
Normal file
38
app/assets/javascripts/grafana_integration/store/actions.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
import { __ } from '~/locale';
|
||||||
|
import createFlash from '~/flash';
|
||||||
|
import { refreshCurrentPage } from '~/lib/utils/url_utility';
|
||||||
|
import * as mutationTypes from './mutation_types';
|
||||||
|
|
||||||
|
export const setGrafanaUrl = ({ commit }, url) => commit(mutationTypes.SET_GRAFANA_URL, url);
|
||||||
|
|
||||||
|
export const setGrafanaToken = ({ commit }, token) =>
|
||||||
|
commit(mutationTypes.SET_GRAFANA_TOKEN, token);
|
||||||
|
|
||||||
|
export const updateGrafanaIntegration = ({ state, dispatch }) =>
|
||||||
|
axios
|
||||||
|
.patch(state.operationsSettingsEndpoint, {
|
||||||
|
project: {
|
||||||
|
grafana_integration_attributes: {
|
||||||
|
grafana_url: state.grafanaUrl,
|
||||||
|
token: state.grafanaToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(() => dispatch('receiveGrafanaIntegrationUpdateSuccess'))
|
||||||
|
.catch(error => dispatch('receiveGrafanaIntegrationUpdateError', error));
|
||||||
|
|
||||||
|
export const receiveGrafanaIntegrationUpdateSuccess = () => {
|
||||||
|
/**
|
||||||
|
* The operations_controller currently handles successful requests
|
||||||
|
* by creating a flash banner messsage to notify the user.
|
||||||
|
*/
|
||||||
|
refreshCurrentPage();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const receiveGrafanaIntegrationUpdateError = (_, error) => {
|
||||||
|
const { response } = error;
|
||||||
|
const message = response.data && response.data.message ? response.data.message : '';
|
||||||
|
|
||||||
|
createFlash(`${__('There was an error saving your changes.')} ${message}`, 'alert');
|
||||||
|
};
|
16
app/assets/javascripts/grafana_integration/store/index.js
Normal file
16
app/assets/javascripts/grafana_integration/store/index.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Vuex from 'vuex';
|
||||||
|
import createState from './state';
|
||||||
|
import * as actions from './actions';
|
||||||
|
import mutations from './mutations';
|
||||||
|
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
export const createStore = initialState =>
|
||||||
|
new Vuex.Store({
|
||||||
|
state: createState(initialState),
|
||||||
|
actions,
|
||||||
|
mutations,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createStore;
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const SET_GRAFANA_URL = 'SET_GRAFANA_URL';
|
||||||
|
export const SET_GRAFANA_TOKEN = 'SET_GRAFANA_TOKEN';
|
|
@ -0,0 +1,10 @@
|
||||||
|
import * as types from './mutation_types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[types.SET_GRAFANA_URL](state, url) {
|
||||||
|
state.grafanaUrl = url;
|
||||||
|
},
|
||||||
|
[types.SET_GRAFANA_TOKEN](state, token) {
|
||||||
|
state.grafanaToken = token;
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
export default (initialState = {}) => ({
|
||||||
|
operationsSettingsEndpoint: initialState.operationsSettingsEndpoint,
|
||||||
|
grafanaToken: initialState.grafanaIntegrationToken || '',
|
||||||
|
grafanaUrl: initialState.grafanaIntegrationUrl || '',
|
||||||
|
});
|
|
@ -1,9 +1,13 @@
|
||||||
import mountErrorTrackingForm from '~/error_tracking_settings';
|
import mountErrorTrackingForm from '~/error_tracking_settings';
|
||||||
import mountOperationSettings from '~/operation_settings';
|
import mountOperationSettings from '~/operation_settings';
|
||||||
|
import mountGrafanaIntegration from '~/grafana_integration';
|
||||||
import initSettingsPanels from '~/settings_panels';
|
import initSettingsPanels from '~/settings_panels';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
mountErrorTrackingForm();
|
mountErrorTrackingForm();
|
||||||
mountOperationSettings();
|
mountOperationSettings();
|
||||||
|
if (gon.features.gfmGrafanaIntegration) {
|
||||||
|
mountGrafanaIntegration();
|
||||||
|
}
|
||||||
initSettingsPanels();
|
initSettingsPanels();
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,12 +20,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Save the URL fragment from the current window location. This will be present if the user was
|
// Save the URL fragment from the current window location. This will be present if the user was
|
||||||
// redirected to sign-in after attempting to access a protected URL that included a fragment.
|
// redirected to sign-in after attempting to access a protected URL that included a fragment.
|
||||||
preserveUrlFragment(window.location.hash);
|
preserveUrlFragment(window.location.hash);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function trackData() {
|
||||||
if (gon.tracking_data) {
|
if (gon.tracking_data) {
|
||||||
const tab = document.querySelector(".new-session-tabs a[href='#register-pane']");
|
const tab = document.querySelector(".new-session-tabs a[href='#register-pane']");
|
||||||
const { category, action, ...data } = gon.tracking_data;
|
const { category, action, ...data } = gon.tracking_data;
|
||||||
|
|
||||||
tab.addEventListener('click', () => {
|
tab.addEventListener('click', () => {
|
||||||
Tracking.event(category, action, data);
|
Tracking.event(category, action, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
trackData();
|
||||||
|
|
|
@ -10,6 +10,14 @@ module Analytics
|
||||||
|
|
||||||
alias_attribute :parent, :project
|
alias_attribute :parent, :project
|
||||||
alias_attribute :parent_id, :project_id
|
alias_attribute :parent_id, :project_id
|
||||||
|
|
||||||
|
def self.relative_positioning_query_base(stage)
|
||||||
|
where(project_id: stage.project_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.relative_positioning_parent_column
|
||||||
|
:project_id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@ module Analytics
|
||||||
module CycleAnalytics
|
module CycleAnalytics
|
||||||
module Stage
|
module Stage
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
include RelativePositioning
|
||||||
|
|
||||||
included do
|
included do
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
|
@ -17,6 +18,7 @@ module Analytics
|
||||||
|
|
||||||
alias_attribute :custom_stage?, :custom
|
alias_attribute :custom_stage?, :custom
|
||||||
scope :default_stages, -> { where(custom: false) }
|
scope :default_stages, -> { where(custom: false) }
|
||||||
|
scope :ordered, -> { order(:relative_position, :id) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def parent=(_)
|
def parent=(_)
|
||||||
|
@ -58,6 +60,10 @@ module Analytics
|
||||||
end_event_identifier.to_s.eql?(stage_params[:end_event_identifier].to_s)
|
end_event_identifier.to_s.eql?(stage_params[:end_event_identifier].to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_with_same_parent!(id)
|
||||||
|
parent.cycle_analytics_stages.find(id)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def validate_stage_event_pairs
|
def validate_stage_event_pairs
|
||||||
|
|
|
@ -16,13 +16,19 @@
|
||||||
.nav-icon-container
|
.nav-icon-container
|
||||||
= sprite_icon('home')
|
= sprite_icon('home')
|
||||||
%span.nav-item-name
|
%span.nav-item-name
|
||||||
= _('Overview')
|
- if @group.subgroup?
|
||||||
|
= _('Subgroup overview')
|
||||||
|
- else
|
||||||
|
= _('Group overview')
|
||||||
|
|
||||||
%ul.sidebar-sub-level-items
|
%ul.sidebar-sub-level-items
|
||||||
= nav_link(path: ['groups#show', 'groups#details', 'groups#activity', 'groups#subgroups'], html_options: { class: "fly-out-top-item" } ) do
|
= nav_link(path: ['groups#show', 'groups#details', 'groups#activity', 'groups#subgroups'], html_options: { class: "fly-out-top-item" } ) do
|
||||||
= link_to group_path(@group) do
|
= link_to group_path(@group) do
|
||||||
%strong.fly-out-top-item-name
|
%strong.fly-out-top-item-name
|
||||||
= _('Overview')
|
- if @group.subgroup?
|
||||||
|
= _('Subgroup overview')
|
||||||
|
- else
|
||||||
|
= _('Group overview')
|
||||||
%li.divider.fly-out-top-item
|
%li.divider.fly-out-top-item
|
||||||
|
|
||||||
= nav_link(path: ['groups#show', 'groups#details', 'groups#subgroups'], html_options: { class: 'home' }) do
|
= nav_link(path: ['groups#show', 'groups#details', 'groups#subgroups'], html_options: { class: 'home' }) do
|
||||||
|
|
|
@ -13,13 +13,13 @@
|
||||||
.nav-icon-container
|
.nav-icon-container
|
||||||
= sprite_icon('home')
|
= sprite_icon('home')
|
||||||
%span.nav-item-name
|
%span.nav-item-name
|
||||||
= _('Project')
|
= _('Project overview')
|
||||||
|
|
||||||
%ul.sidebar-sub-level-items
|
%ul.sidebar-sub-level-items
|
||||||
= nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do
|
= nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do
|
||||||
= link_to project_path(@project) do
|
= link_to project_path(@project) do
|
||||||
%strong.fly-out-top-item-name
|
%strong.fly-out-top-item-name
|
||||||
= _('Project')
|
= _('Project overview')
|
||||||
%li.divider.fly-out-top-item
|
%li.divider.fly-out-top-item
|
||||||
= nav_link(path: 'projects#show') do
|
= nav_link(path: 'projects#show') do
|
||||||
= link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do
|
= link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
%p= s_('Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?').html_safe % {commit_id: commit_sha}
|
%p= s_('Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?').html_safe % {commit_id: commit_sha}
|
||||||
- else
|
- else
|
||||||
%p
|
%p
|
||||||
= s_('Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?').html_safe % {commit_id: commit_sha}
|
= s_('Environments|This action will run the job defined by %{environment_name} for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?').html_safe % {commit_id: commit_sha, environment_name: @environment.name}
|
||||||
.modal-footer
|
.modal-footer
|
||||||
= button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' }
|
= button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' }
|
||||||
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-danger' do
|
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-danger' do
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
.js-grafana-integration{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
|
||||||
|
grafana_integration: { url: grafana_integration_url, token: grafana_integration_token } } }
|
|
@ -5,4 +5,5 @@
|
||||||
= render_if_exists 'projects/settings/operations/incidents'
|
= render_if_exists 'projects/settings/operations/incidents'
|
||||||
= render 'projects/settings/operations/error_tracking'
|
= render 'projects/settings/operations/error_tracking'
|
||||||
= render 'projects/settings/operations/external_dashboard'
|
= render 'projects/settings/operations/external_dashboard'
|
||||||
|
= render 'projects/settings/operations/grafana_integration'
|
||||||
= render_if_exists 'projects/settings/operations/tracing'
|
= render_if_exists 'projects/settings/operations/tracing'
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix environment name in rollback dialog
|
||||||
|
merge_request: 19209
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -353,7 +353,7 @@ configuration.
|
||||||
|
|
||||||
NOTE: **Note:** Enabling a storage driver other than `filesystem` would mean
|
NOTE: **Note:** Enabling a storage driver other than `filesystem` would mean
|
||||||
that your Docker client needs to be able to access the storage backend directly.
|
that your Docker client needs to be able to access the storage backend directly.
|
||||||
In that case, you must use an address that resolves and is accessible outside GitLab server.
|
In that case, you must use an address that resolves and is accessible outside GitLab server. The Docker client will continue to authenticate via GitLab but data transfer will be direct to and from the storage backend.
|
||||||
|
|
||||||
The different supported drivers are:
|
The different supported drivers are:
|
||||||
|
|
||||||
|
|
|
@ -6339,10 +6339,10 @@ msgstr ""
|
||||||
msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
|
msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
|
msgid "Environments|This action will run the job defined by %{environment_name} for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
|
msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Environments|Updated"
|
msgid "Environments|Updated"
|
||||||
|
@ -8210,6 +8210,24 @@ msgstr ""
|
||||||
msgid "Grafana URL"
|
msgid "Grafana URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "GrafanaIntegration|API Token"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "GrafanaIntegration|Embed Grafana charts in GitLab issues."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "GrafanaIntegration|Enter the Grafana API Token."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "GrafanaIntegration|Enter the base URL of the Grafana instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "GrafanaIntegration|Grafana Authentication"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "GrafanaIntegration|Grafana URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Grant access"
|
msgid "Grant access"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -8273,6 +8291,9 @@ msgstr ""
|
||||||
msgid "Group name"
|
msgid "Group name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Group overview"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Group overview content"
|
msgid "Group overview content"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -12737,6 +12758,9 @@ msgstr ""
|
||||||
msgid "Project name"
|
msgid "Project name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Project overview"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Project slug"
|
msgid "Project slug"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -15847,6 +15871,9 @@ msgstr ""
|
||||||
msgid "StorageSize|Unknown"
|
msgid "StorageSize|Unknown"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Subgroup overview"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "SubgroupCreationLevel|Allowed to create subgroups"
|
msgid "SubgroupCreationLevel|Allowed to create subgroups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
require 'pathname'
|
require 'pathname'
|
||||||
|
|
||||||
module QA
|
module QA
|
||||||
context 'Configure' do
|
# Issue: https://gitlab.com/gitlab-org/gitlab/issues/35156
|
||||||
|
context 'Configure', :quarantine do
|
||||||
def login
|
def login
|
||||||
Runtime::Browser.visit(:gitlab, Page::Main::Login)
|
Runtime::Browser.visit(:gitlab, Page::Main::Login)
|
||||||
Page::Main::Login.perform(&:sign_in_using_credentials)
|
Page::Main::Login.perform(&:sign_in_using_credentials)
|
||||||
|
|
|
@ -15,7 +15,7 @@ describe 'The group page' do
|
||||||
|
|
||||||
def expect_all_sidebar_links
|
def expect_all_sidebar_links
|
||||||
within('.nav-sidebar') do
|
within('.nav-sidebar') do
|
||||||
expect(page).to have_link('Overview')
|
expect(page).to have_link('Group overview')
|
||||||
expect(page).to have_link('Details')
|
expect(page).to have_link('Details')
|
||||||
expect(page).to have_link('Activity')
|
expect(page).to have_link('Activity')
|
||||||
expect(page).to have_link('Issues')
|
expect(page).to have_link('Issues')
|
||||||
|
@ -44,7 +44,7 @@ describe 'The group page' do
|
||||||
visit group_path(group)
|
visit group_path(group)
|
||||||
|
|
||||||
within('.nav-sidebar') do
|
within('.nav-sidebar') do
|
||||||
expect(page).to have_link('Overview')
|
expect(page).to have_link('Group overview')
|
||||||
expect(page).to have_link('Details')
|
expect(page).to have_link('Details')
|
||||||
expect(page).not_to have_link('Activity')
|
expect(page).not_to have_link('Activity')
|
||||||
expect(page).not_to have_link('Contribution Analytics')
|
expect(page).not_to have_link('Contribution Analytics')
|
||||||
|
|
|
@ -237,14 +237,28 @@ describe 'Group' do
|
||||||
let!(:group) { create(:group) }
|
let!(:group) { create(:group) }
|
||||||
let!(:nested_group) { create(:group, parent: group) }
|
let!(:nested_group) { create(:group, parent: group) }
|
||||||
let!(:project) { create(:project, namespace: group) }
|
let!(:project) { create(:project, namespace: group) }
|
||||||
let!(:path) { group_path(group) }
|
|
||||||
|
|
||||||
it 'renders projects and groups on the page' do
|
it 'renders projects and groups on the page' do
|
||||||
visit path
|
visit group_path(group)
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
|
|
||||||
expect(page).to have_content(nested_group.name)
|
expect(page).to have_content(nested_group.name)
|
||||||
expect(page).to have_content(project.name)
|
expect(page).to have_content(project.name)
|
||||||
|
expect(page).to have_link('Group overview')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders subgroup page with the text "Subgroup overview"' do
|
||||||
|
visit group_path(nested_group)
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
expect(page).to have_link('Subgroup overview')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders project page with the text "Project overview"' do
|
||||||
|
visit project_path(project)
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
expect(page).to have_link('Project overview')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -102,5 +102,40 @@ describe 'Projects > Settings > For a forked project', :js do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'grafana integration settings form' do
|
||||||
|
it 'is not present when the feature flag is disabled' do
|
||||||
|
stub_feature_flags(gfm_grafana_integration: false)
|
||||||
|
|
||||||
|
visit project_settings_operations_path(project)
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
expect(page).to have_no_css('.js-grafana-integration')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is present when the feature flag is enabled' do
|
||||||
|
visit project_settings_operations_path(project)
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
within '.js-grafana-integration' do
|
||||||
|
click_button('Expand')
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(page).to have_content('Grafana URL')
|
||||||
|
expect(page).to have_content('API Token')
|
||||||
|
expect(page).to have_button('Save Changes')
|
||||||
|
|
||||||
|
fill_in('grafana-url', with: 'http://gitlab-test.grafana.net')
|
||||||
|
fill_in('grafana-token', with: 'token')
|
||||||
|
|
||||||
|
click_button('Save Changes')
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
assert_text('Your changes have been saved')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,4 +5,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="#login-pane">Standard</a>
|
<a href="#login-pane">Standard</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#register-pane">Register</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`grafana integration component default state to match the default snapshot 1`] = `
|
||||||
|
<section
|
||||||
|
class="settings no-animate js-grafana-integration"
|
||||||
|
id="grafana"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="settings-header"
|
||||||
|
>
|
||||||
|
<h4
|
||||||
|
class="js-section-header"
|
||||||
|
>
|
||||||
|
|
||||||
|
Grafana Authentication
|
||||||
|
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<glbutton-stub
|
||||||
|
class="js-settings-toggle"
|
||||||
|
>
|
||||||
|
Expand
|
||||||
|
</glbutton-stub>
|
||||||
|
|
||||||
|
<p
|
||||||
|
class="js-section-sub-header"
|
||||||
|
>
|
||||||
|
|
||||||
|
Embed Grafana charts in GitLab issues.
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="settings-content"
|
||||||
|
>
|
||||||
|
<form>
|
||||||
|
<glformgroup-stub
|
||||||
|
description="Enter the base URL of the Grafana instance."
|
||||||
|
label="Grafana URL"
|
||||||
|
label-for="grafana-url"
|
||||||
|
>
|
||||||
|
<glforminput-stub
|
||||||
|
id="grafana-url"
|
||||||
|
placeholder="https://my-url.grafana.net/my-dashboard"
|
||||||
|
value="http://test.host"
|
||||||
|
/>
|
||||||
|
</glformgroup-stub>
|
||||||
|
|
||||||
|
<glformgroup-stub
|
||||||
|
label="API Token"
|
||||||
|
label-for="grafana-token"
|
||||||
|
>
|
||||||
|
<glforminput-stub
|
||||||
|
id="grafana-token"
|
||||||
|
value="someToken"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p
|
||||||
|
class="form-text text-muted"
|
||||||
|
>
|
||||||
|
|
||||||
|
Enter the Grafana API Token.
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://grafana.com/docs/http_api/auth/#create-api-token"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
|
||||||
|
More information
|
||||||
|
|
||||||
|
<icon-stub
|
||||||
|
class="vertical-align-middle"
|
||||||
|
name="external-link"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</glformgroup-stub>
|
||||||
|
|
||||||
|
<glbutton-stub
|
||||||
|
variant="success"
|
||||||
|
>
|
||||||
|
|
||||||
|
Save Changes
|
||||||
|
|
||||||
|
</glbutton-stub>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`;
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { mount, shallowMount } from '@vue/test-utils';
|
||||||
|
import { GlButton } from '@gitlab/ui';
|
||||||
|
import GrafanaIntegration from '~/grafana_integration/components/grafana_integration.vue';
|
||||||
|
import { createStore } from '~/grafana_integration/store';
|
||||||
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
import { refreshCurrentPage } from '~/lib/utils/url_utility';
|
||||||
|
import createFlash from '~/flash';
|
||||||
|
import { TEST_HOST } from 'helpers/test_constants';
|
||||||
|
|
||||||
|
jest.mock('~/lib/utils/url_utility');
|
||||||
|
jest.mock('~/flash');
|
||||||
|
|
||||||
|
describe('grafana integration component', () => {
|
||||||
|
let wrapper;
|
||||||
|
let store;
|
||||||
|
const operationsSettingsEndpoint = `${TEST_HOST}/mock/ops/settings/endpoint`;
|
||||||
|
const grafanaIntegrationUrl = `${TEST_HOST}`;
|
||||||
|
const grafanaIntegrationToken = 'someToken';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = createStore({
|
||||||
|
operationsSettingsEndpoint,
|
||||||
|
grafanaIntegrationUrl,
|
||||||
|
grafanaIntegrationToken,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (wrapper.destroy) {
|
||||||
|
wrapper.destroy();
|
||||||
|
createFlash.mockReset();
|
||||||
|
refreshCurrentPage.mockReset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('default state', () => {
|
||||||
|
it('to match the default snapshot', () => {
|
||||||
|
wrapper = shallowMount(GrafanaIntegration, { store });
|
||||||
|
|
||||||
|
expect(wrapper.element).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders header text', () => {
|
||||||
|
wrapper = shallowMount(GrafanaIntegration, { store });
|
||||||
|
|
||||||
|
expect(wrapper.find('.js-section-header').text()).toBe('Grafana Authentication');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('expand/collapse button', () => {
|
||||||
|
it('renders as an expand button by default', () => {
|
||||||
|
wrapper = shallowMount(GrafanaIntegration, { store });
|
||||||
|
|
||||||
|
const button = wrapper.find(GlButton);
|
||||||
|
|
||||||
|
expect(button.text()).toBe('Expand');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sub-header', () => {
|
||||||
|
it('renders descriptive text', () => {
|
||||||
|
wrapper = shallowMount(GrafanaIntegration, { store });
|
||||||
|
|
||||||
|
expect(wrapper.find('.js-section-sub-header').text()).toContain(
|
||||||
|
'Embed Grafana charts in GitLab issues.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('form', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(axios, 'patch').mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
axios.patch.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('submit button', () => {
|
||||||
|
const findSubmitButton = () => wrapper.find('.settings-content form').find(GlButton);
|
||||||
|
|
||||||
|
const endpointRequest = [
|
||||||
|
operationsSettingsEndpoint,
|
||||||
|
{
|
||||||
|
project: {
|
||||||
|
grafana_integration_attributes: {
|
||||||
|
grafana_url: grafanaIntegrationUrl,
|
||||||
|
token: grafanaIntegrationToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it('submits form on click', () => {
|
||||||
|
wrapper = mount(GrafanaIntegration, { store });
|
||||||
|
axios.patch.mockResolvedValue();
|
||||||
|
|
||||||
|
findSubmitButton(wrapper).trigger('click');
|
||||||
|
|
||||||
|
expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
|
||||||
|
return wrapper.vm.$nextTick().then(() => expect(refreshCurrentPage).toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates flash banner on error', () => {
|
||||||
|
const message = 'mockErrorMessage';
|
||||||
|
wrapper = mount(GrafanaIntegration, { store });
|
||||||
|
axios.patch.mockRejectedValue({ response: { data: { message } } });
|
||||||
|
|
||||||
|
findSubmitButton().trigger('click');
|
||||||
|
|
||||||
|
expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
|
||||||
|
return wrapper.vm
|
||||||
|
.$nextTick()
|
||||||
|
.then(jest.runAllTicks)
|
||||||
|
.then(() =>
|
||||||
|
expect(createFlash).toHaveBeenCalledWith(
|
||||||
|
`There was an error saving your changes. ${message}`,
|
||||||
|
'alert',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
28
spec/frontend/grafana_integration/store/mutations_spec.js
Normal file
28
spec/frontend/grafana_integration/store/mutations_spec.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import mutations from '~/grafana_integration/store/mutations';
|
||||||
|
import createState from '~/grafana_integration/store/state';
|
||||||
|
|
||||||
|
describe('grafana integration mutations', () => {
|
||||||
|
let localState;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
localState = createState();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SET_GRAFANA_URL', () => {
|
||||||
|
it('sets grafanaUrl', () => {
|
||||||
|
const mockUrl = 'mockUrl';
|
||||||
|
mutations.SET_GRAFANA_URL(localState, mockUrl);
|
||||||
|
|
||||||
|
expect(localState.grafanaUrl).toBe(mockUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SET_GRAFANA_TOKEN', () => {
|
||||||
|
it('sets grafanaToken', () => {
|
||||||
|
const mockToken = 'mockToken';
|
||||||
|
mutations.SET_GRAFANA_TOKEN(localState, mockToken);
|
||||||
|
|
||||||
|
expect(localState.grafanaToken).toBe(mockToken);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,7 @@
|
||||||
import AccessorUtilities from '~/lib/utils/accessor';
|
import AccessorUtilities from '~/lib/utils/accessor';
|
||||||
import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
|
import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
|
||||||
|
import trackData from '~/pages/sessions/new/index';
|
||||||
|
import Tracking from '~/tracking';
|
||||||
|
|
||||||
describe('SigninTabsMemoizer', () => {
|
describe('SigninTabsMemoizer', () => {
|
||||||
const fixtureTemplate = 'static/signin_tabs.html';
|
const fixtureTemplate = 'static/signin_tabs.html';
|
||||||
|
@ -93,6 +95,50 @@ describe('SigninTabsMemoizer', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('trackData', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(Tracking, 'event');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with tracking data', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
gon.tracking_data = {
|
||||||
|
category: 'Growth::Acquisition::Experiment::SignUpFlow',
|
||||||
|
action: 'start',
|
||||||
|
label: 'uuid',
|
||||||
|
property: 'control_group',
|
||||||
|
};
|
||||||
|
trackData();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should track data when the "click" event of the register tab is triggered', () => {
|
||||||
|
document.querySelector('a[href="#register-pane"]').click();
|
||||||
|
|
||||||
|
expect(Tracking.event).toHaveBeenCalledWith(
|
||||||
|
'Growth::Acquisition::Experiment::SignUpFlow',
|
||||||
|
'start',
|
||||||
|
{
|
||||||
|
label: 'uuid',
|
||||||
|
property: 'control_group',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('without tracking data', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
gon.tracking_data = undefined;
|
||||||
|
trackData();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not track data when the "click" event of the register tab is triggered', () => {
|
||||||
|
document.querySelector('a[href="#register-pane"]').click();
|
||||||
|
|
||||||
|
expect(Tracking.event).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('saveData', () => {
|
describe('saveData', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
memo = {
|
memo = {
|
||||||
|
|
|
@ -171,38 +171,7 @@ describe('test errors', () => {
|
||||||
// see: https://github.com/deepsweet/istanbul-instrumenter-loader/issues/15
|
// see: https://github.com/deepsweet/istanbul-instrumenter-loader/issues/15
|
||||||
if (process.env.BABEL_ENV === 'coverage') {
|
if (process.env.BABEL_ENV === 'coverage') {
|
||||||
// exempt these files from the coverage report
|
// exempt these files from the coverage report
|
||||||
const troubleMakers = [
|
const troubleMakers = ['./pages/admin/application_settings/general/index.js'];
|
||||||
'./blob_edit/blob_bundle.js',
|
|
||||||
'./boards/components/modal/empty_state.vue',
|
|
||||||
'./boards/components/modal/footer.js',
|
|
||||||
'./boards/components/modal/header.js',
|
|
||||||
'./cycle_analytics/cycle_analytics_bundle.js',
|
|
||||||
'./cycle_analytics/components/stage_plan_component.js',
|
|
||||||
'./cycle_analytics/components/stage_staging_component.js',
|
|
||||||
'./cycle_analytics/components/stage_test_component.js',
|
|
||||||
'./commit/pipelines/pipelines_bundle.js',
|
|
||||||
'./diff_notes/diff_notes_bundle.js',
|
|
||||||
'./diff_notes/components/jump_to_discussion.js',
|
|
||||||
'./diff_notes/components/resolve_count.js',
|
|
||||||
'./dispatcher.js',
|
|
||||||
'./environments/environments_bundle.js',
|
|
||||||
'./graphs/graphs_bundle.js',
|
|
||||||
'./issuable/time_tracking/time_tracking_bundle.js',
|
|
||||||
'./main.js',
|
|
||||||
'./merge_conflicts/merge_conflicts_bundle.js',
|
|
||||||
'./merge_conflicts/components/inline_conflict_lines.js',
|
|
||||||
'./merge_conflicts/components/parallel_conflict_lines.js',
|
|
||||||
'./monitoring/monitoring_bundle.js',
|
|
||||||
'./network/network_bundle.js',
|
|
||||||
'./network/branch_graph.js',
|
|
||||||
'./profile/profile_bundle.js',
|
|
||||||
'./protected_branches/protected_branches_bundle.js',
|
|
||||||
'./snippet/snippet_bundle.js',
|
|
||||||
'./terminal/terminal_bundle.js',
|
|
||||||
'./users/users_bundle.js',
|
|
||||||
'./issue_show/index.js',
|
|
||||||
'./pages/admin/application_settings/general/index.js',
|
|
||||||
];
|
|
||||||
|
|
||||||
describe('Uncovered files', function() {
|
describe('Uncovered files', function() {
|
||||||
const sourceFilesContexts = [require.context('~', true, /\.(js|vue)$/)];
|
const sourceFilesContexts = [require.context('~', true, /\.(js|vue)$/)];
|
||||||
|
|
|
@ -16,8 +16,16 @@ describe Analytics::CycleAnalytics::ProjectStage do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like "cycle analytics stage" do
|
it_behaves_like 'cycle analytics stage' do
|
||||||
let(:parent) { create(:project) }
|
let(:parent) { create(:project) }
|
||||||
let(:parent_name) { :project }
|
let(:parent_name) { :project }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'relative positioning' do
|
||||||
|
it_behaves_like 'a class that supports relative positioning' do
|
||||||
|
let(:project) { create(:project) }
|
||||||
|
let(:factory) { :cycle_analytics_project_stage }
|
||||||
|
let(:default_params) { { project: project } }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,7 +48,7 @@ describe 'projects/deployments/_confirm_rollback_modal' do
|
||||||
render
|
render
|
||||||
|
|
||||||
expect(rendered).to have_selector('h4', text: "Rollback environment #{environment.name}?")
|
expect(rendered).to have_selector('h4', text: "Rollback environment #{environment.name}?")
|
||||||
expect(rendered).to have_selector('p', text: "This action will run the job defined by staging for commit #{deployment.short_sha}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?")
|
expect(rendered).to have_selector('p', text: "This action will run the job defined by #{environment.name} for commit #{deployment.short_sha}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?")
|
||||||
expect(rendered).to have_selector('a.btn-danger', text: 'Rollback')
|
expect(rendered).to have_selector('a.btn-danger', text: 'Rollback')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue