Commit 2b7a5214 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 15a2d004
...@@ -76,8 +76,7 @@ graphql-reference-verify: ...@@ -76,8 +76,7 @@ graphql-reference-verify:
- .default-only - .default-only
- .default-before_script - .default-before_script
- .only:changes-graphql - .only:changes-graphql
variables: - .use-pg9
SETUP_DB: "false"
stage: test stage: test
needs: ["setup-test-env"] needs: ["setup-test-env"]
script: script:
......
...@@ -2,6 +2,8 @@ import $ from 'jquery'; ...@@ -2,6 +2,8 @@ import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { joinPaths } from './lib/utils/url_utility'; import { joinPaths } from './lib/utils/url_utility';
import flash from '~/flash';
import { __ } from '~/locale';
const Api = { const Api = {
groupsPath: '/api/:version/groups.json', groupsPath: '/api/:version/groups.json',
...@@ -29,6 +31,7 @@ const Api = { ...@@ -29,6 +31,7 @@ const Api = {
usersPath: '/api/:version/users.json', usersPath: '/api/:version/users.json',
userPath: '/api/:version/users/:id', userPath: '/api/:version/users/:id',
userStatusPath: '/api/:version/users/:id/status', userStatusPath: '/api/:version/users/:id/status',
userProjectsPath: '/api/:version/users/:id/projects',
userPostStatusPath: '/api/:version/user/status', userPostStatusPath: '/api/:version/user/status',
commitPath: '/api/:version/projects/:id/repository/commits', commitPath: '/api/:version/projects/:id/repository/commits',
applySuggestionPath: '/api/:version/suggestions/:id/apply', applySuggestionPath: '/api/:version/suggestions/:id/apply',
...@@ -239,7 +242,8 @@ const Api = { ...@@ -239,7 +242,8 @@ const Api = {
.get(url, { .get(url, {
params: Object.assign({}, defaults, options), params: Object.assign({}, defaults, options),
}) })
.then(({ data }) => callback(data)); .then(({ data }) => callback(data))
.catch(() => flash(__('Something went wrong while fetching projects')));
}, },
commitMultiple(id, data) { commitMultiple(id, data) {
...@@ -348,6 +352,20 @@ const Api = { ...@@ -348,6 +352,20 @@ const Api = {
}); });
}, },
userProjects(userId, query, options, callback) {
const url = Api.buildUrl(Api.userProjectsPath).replace(':id', userId);
const defaults = {
search: query,
per_page: 20,
};
return axios
.get(url, {
params: Object.assign({}, defaults, options),
})
.then(({ data }) => callback(data))
.catch(() => flash(__('Something went wrong while fetching projects')));
},
branches(id, query = '', options = {}) { branches(id, query = '', options = {}) {
const url = Api.buildUrl(this.createBranchPath).replace(':id', encodeURIComponent(id)); const url = Api.buildUrl(this.createBranchPath).replace(':id', encodeURIComponent(id));
......
...@@ -13,6 +13,7 @@ import ClustersService from './services/clusters_service'; ...@@ -13,6 +13,7 @@ import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store'; import ClustersStore from './stores/clusters_store';
import Applications from './components/applications.vue'; import Applications from './components/applications.vue';
import setupToggleButtons from '../toggle_buttons'; import setupToggleButtons from '../toggle_buttons';
import initProjectSelectDropdown from '~/project_select';
const Environments = () => import('ee_component/clusters/components/environments.vue'); const Environments = () => import('ee_component/clusters/components/environments.vue');
...@@ -110,8 +111,10 @@ export default class Clusters { ...@@ -110,8 +111,10 @@ export default class Clusters {
this.ingressDomainHelpText && this.ingressDomainHelpText &&
this.ingressDomainHelpText.querySelector('.js-ingress-domain-snippet'); this.ingressDomainHelpText.querySelector('.js-ingress-domain-snippet');
initProjectSelectDropdown();
Clusters.initDismissableCallout(); Clusters.initDismissableCallout();
initSettingsPanels(); initSettingsPanels();
const toggleButtonsContainer = document.querySelector('.js-cluster-enable-toggle-area'); const toggleButtonsContainer = document.querySelector('.js-cluster-enable-toggle-area');
if (toggleButtonsContainer) { if (toggleButtonsContainer) {
setupToggleButtons(toggleButtonsContainer); setupToggleButtons(toggleButtonsContainer);
......
...@@ -174,7 +174,7 @@ export default { ...@@ -174,7 +174,7 @@ export default {
return this.customMetricsAvailable && this.customMetricsPath.length; return this.customMetricsAvailable && this.customMetricsPath.length;
}, },
...mapState('monitoringDashboard', [ ...mapState('monitoringDashboard', [
'groups', 'dashboard',
'emptyState', 'emptyState',
'showEmptyState', 'showEmptyState',
'environments', 'environments',
...@@ -238,6 +238,7 @@ export default { ...@@ -238,6 +238,7 @@ export default {
'setGettingStartedEmptyState', 'setGettingStartedEmptyState',
'setEndpoints', 'setEndpoints',
'setDashboardEnabled', 'setDashboardEnabled',
'setPanelGroupMetrics',
]), ]),
chartsWithData(charts) { chartsWithData(charts) {
if (!this.useDashboardEndpoint) { if (!this.useDashboardEndpoint) {
...@@ -274,10 +275,17 @@ export default { ...@@ -274,10 +275,17 @@ export default {
this.$toast.show(__('Link copied')); this.$toast.show(__('Link copied'));
}, },
// TODO: END // TODO: END
removeGraph(metrics, graphIndex) { updateMetrics(key, metrics) {
// At present graphs will not be removed, they should removed using the vuex store this.setPanelGroupMetrics({
// See https://gitlab.com/gitlab-org/gitlab/issues/27835 metrics,
metrics.splice(graphIndex, 1); key,
});
},
removeMetric(key, metrics, graphIndex) {
this.setPanelGroupMetrics({
metrics: metrics.filter((v, i) => i !== graphIndex),
key,
});
}, },
showInvalidDateError() { showInvalidDateError() {
createFlash(s__('Metrics|Link contains an invalid time window.')); createFlash(s__('Metrics|Link contains an invalid time window.'));
...@@ -447,7 +455,7 @@ export default { ...@@ -447,7 +455,7 @@ export default {
<div v-if="!showEmptyState"> <div v-if="!showEmptyState">
<graph-group <graph-group
v-for="(groupData, index) in groups" v-for="(groupData, index) in dashboard.panel_groups"
:key="`${groupData.group}.${groupData.priority}`" :key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group" :name="groupData.group"
:show-panels="showPanels" :show-panels="showPanels"
...@@ -455,10 +463,11 @@ export default { ...@@ -455,10 +463,11 @@ export default {
> >
<template v-if="additionalPanelTypesEnabled"> <template v-if="additionalPanelTypesEnabled">
<vue-draggable <vue-draggable
:list="groupData.metrics" :value="groupData.metrics"
group="metrics-dashboard" group="metrics-dashboard"
:component-data="{ attrs: { class: 'row mx-0 w-100' } }" :component-data="{ attrs: { class: 'row mx-0 w-100' } }"
:disabled="!isRearrangingPanels" :disabled="!isRearrangingPanels"
@input="updateMetrics(groupData.key, $event)"
> >
<div <div
v-for="(graphData, graphIndex) in groupData.metrics" v-for="(graphData, graphIndex) in groupData.metrics"
...@@ -470,7 +479,7 @@ export default { ...@@ -470,7 +479,7 @@ export default {
<div <div
v-if="isRearrangingPanels" v-if="isRearrangingPanels"
class="draggable-remove js-draggable-remove p-2 w-100 position-absolute d-flex justify-content-end" class="draggable-remove js-draggable-remove p-2 w-100 position-absolute d-flex justify-content-end"
@click="removeGraph(groupData.metrics, graphIndex)" @click="removeMetric(groupData.key, groupData.metrics, graphIndex)"
> >
<a class="mx-2 p-2 draggable-remove-link" :aria-label="__('Remove')" <a class="mx-2 p-2 draggable-remove-link" :aria-label="__('Remove')"
><icon name="close" ><icon name="close"
......
...@@ -35,9 +35,9 @@ export default { ...@@ -35,9 +35,9 @@ export default {
}; };
}, },
computed: { computed: {
...mapState('monitoringDashboard', ['groups', 'metricsWithData']), ...mapState('monitoringDashboard', ['dashboard', 'metricsWithData']),
charts() { charts() {
const groupWithMetrics = this.groups.find(group => const groupWithMetrics = this.dashboard.panel_groups.find(group =>
group.metrics.find(chart => this.chartHasData(chart)), group.metrics.find(chart => this.chartHasData(chart)),
) || { metrics: [] }; ) || { metrics: [] };
......
...@@ -166,7 +166,7 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch }, params) => { ...@@ -166,7 +166,7 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch }, params) => {
commit(types.REQUEST_METRICS_DATA); commit(types.REQUEST_METRICS_DATA);
const promises = []; const promises = [];
state.groups.forEach(group => { state.dashboard.panel_groups.forEach(group => {
group.panels.forEach(panel => { group.panels.forEach(panel => {
panel.metrics.forEach(metric => { panel.metrics.forEach(metric => {
promises.push(dispatch('fetchPrometheusMetric', { metric, params })); promises.push(dispatch('fetchPrometheusMetric', { metric, params }));
...@@ -221,5 +221,15 @@ export const fetchEnvironmentsData = ({ state, dispatch }) => { ...@@ -221,5 +221,15 @@ export const fetchEnvironmentsData = ({ state, dispatch }) => {
}); });
}; };
/**
* Set a new array of metrics to a panel group
* @param {*} data An object containing
* - `key` with a unique panel key
* - `metrics` with the metrics array
*/
export const setPanelGroupMetrics = ({ commit }, data) => {
commit(types.SET_PANEL_GROUP_METRICS, data);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -16,3 +16,4 @@ export const SET_ENDPOINTS = 'SET_ENDPOINTS'; ...@@ -16,3 +16,4 @@ export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_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_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER'; export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
export const SET_PANEL_GROUP_METRICS = 'SET_PANEL_GROUP_METRICS';
import Vue from 'vue'; import Vue from 'vue';
import { slugify } from '~/lib/utils/text_utility';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { normalizeMetrics, sortMetrics, normalizeMetric, normalizeQueryResult } from './utils'; import { normalizeMetrics, normalizeMetric, normalizeQueryResult } from './utils';
const normalizePanel = panel => panel.metrics.map(normalizeMetric); const normalizePanel = panel => panel.metrics.map(normalizeMetric);
...@@ -10,10 +11,12 @@ export default { ...@@ -10,10 +11,12 @@ export default {
state.showEmptyState = true; state.showEmptyState = true;
}, },
[types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) { [types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) {
state.groups = groupData.map(group => { state.dashboard.panel_groups = groupData.map((group, i) => {
const key = `${slugify(group.group || 'default')}-${i}`;
let { metrics = [], panels = [] } = group; let { metrics = [], panels = [] } = group;
// each panel has metric information that needs to be normalized // each panel has metric information that needs to be normalized
panels = panels.map(panel => ({ panels = panels.map(panel => ({
...panel, ...panel,
metrics: normalizePanel(panel), metrics: normalizePanel(panel),
...@@ -32,11 +35,12 @@ export default { ...@@ -32,11 +35,12 @@ export default {
return { return {
...group, ...group,
panels, panels,
metrics: normalizeMetrics(sortMetrics(metrics)), key,
metrics: normalizeMetrics(metrics),
}; };
}); });
if (!state.groups.length) { if (!state.dashboard.panel_groups.length) {
state.emptyState = 'noData'; state.emptyState = 'noData';
} else { } else {
state.showEmptyState = false; state.showEmptyState = false;
...@@ -65,7 +69,7 @@ export default { ...@@ -65,7 +69,7 @@ export default {
state.showEmptyState = false; state.showEmptyState = false;
state.groups.forEach(group => { state.dashboard.panel_groups.forEach(group => {
group.metrics.forEach(metric => { group.metrics.forEach(metric => {
metric.queries.forEach(query => { metric.queries.forEach(query => {
if (query.metric_id === metricId) { if (query.metric_id === metricId) {
...@@ -105,4 +109,8 @@ export default { ...@@ -105,4 +109,8 @@ export default {
[types.SET_SHOW_ERROR_BANNER](state, enabled) { [types.SET_SHOW_ERROR_BANNER](state, enabled) {
state.showErrorBanner = enabled; state.showErrorBanner = enabled;
}, },
[types.SET_PANEL_GROUP_METRICS](state, payload) {
const panelGroup = state.dashboard.panel_groups.find(pg => payload.key === pg.key);
panelGroup.metrics = payload.metrics;
},
}; };
...@@ -12,7 +12,9 @@ export default () => ({ ...@@ -12,7 +12,9 @@ export default () => ({
emptyState: 'gettingStarted', emptyState: 'gettingStarted',
showEmptyState: true, showEmptyState: true,
showErrorBanner: true, showErrorBanner: true,
groups: [], dashboard: {
panel_groups: [],
},
deploymentData: [], deploymentData: [],
environments: [], environments: [],
metricsWithData: [], metricsWithData: [],
......
...@@ -82,12 +82,6 @@ export const normalizeMetric = (metric = {}) => ...@@ -82,12 +82,6 @@ export const normalizeMetric = (metric = {}) =>
'id', 'id',
); );
export const sortMetrics = metrics =>
_.chain(metrics)
.sortBy('title')
.sortBy('weight')
.value();
export const normalizeQueryResult = timeSeries => { export const normalizeQueryResult = timeSeries => {
let normalizedResult = {}; let normalizedResult = {};
......
...@@ -9,7 +9,9 @@ const projectSelect = () => { ...@@ -9,7 +9,9 @@ const projectSelect = () => {
$('.ajax-project-select').each(function(i, select) { $('.ajax-project-select').each(function(i, select) {
var placeholder; var placeholder;
const simpleFilter = $(select).data('simpleFilter') || false; const simpleFilter = $(select).data('simpleFilter') || false;
const isInstantiated = $(select).data('select2');
this.groupId = $(select).data('groupId'); this.groupId = $(select).data('groupId');
this.userId = $(select).data('userId');
this.includeGroups = $(select).data('includeGroups'); this.includeGroups = $(select).data('includeGroups');
this.allProjects = $(select).data('allProjects') || false; this.allProjects = $(select).data('allProjects') || false;
this.orderBy = $(select).data('orderBy') || 'id'; this.orderBy = $(select).data('orderBy') || 'id';
...@@ -63,6 +65,18 @@ const projectSelect = () => { ...@@ -63,6 +65,18 @@ const projectSelect = () => {
}, },
projectsCallback, projectsCallback,
); );
} else if (_this.userId) {
return Api.userProjects(
_this.userId,
query.term,
{
with_issues_enabled: _this.withIssuesEnabled,
with_merge_requests_enabled: _this.withMergeRequestsEnabled,
with_shared: _this.withShared,
include_subgroups: _this.includeProjectsInSubgroups,
},
projectsCallback,
);
} else { } else {
return Api.projects( return Api.projects(
query.term, query.term,
...@@ -96,7 +110,7 @@ const projectSelect = () => { ...@@ -96,7 +110,7 @@ const projectSelect = () => {
dropdownCssClass: 'ajax-project-dropdown', dropdownCssClass: 'ajax-project-dropdown',
}); });
if (simpleFilter) return select; if (isInstantiated || simpleFilter) return select;
return new ProjectSelectComboButton(select); return new ProjectSelectComboButton(select);
}); });
}; };
......
...@@ -46,7 +46,7 @@ class GitlabSchema < GraphQL::Schema ...@@ -46,7 +46,7 @@ class GitlabSchema < GraphQL::Schema
super(query_str, **kwargs) super(query_str, **kwargs)
end end
def id_from_object(object) def id_from_object(object, _type = nil, _ctx = nil)
unless object.respond_to?(:to_global_id) unless object.respond_to?(:to_global_id)
# This is an error in our schema and needs to be solved. So raise a # This is an error in our schema and needs to be solved. So raise a
# more meaningful error message # more meaningful error message
...@@ -57,7 +57,7 @@ class GitlabSchema < GraphQL::Schema ...@@ -57,7 +57,7 @@ class GitlabSchema < GraphQL::Schema
object.to_global_id object.to_global_id
end end
def object_from_id(global_id) def object_from_id(global_id, _ctx = nil)
gid = GlobalID.parse(global_id) gid = GlobalID.parse(global_id)
unless gid unless gid
......
# frozen_string_literal: true
module Mutations
module MergeRequests
class SetMilestone < Base
graphql_name 'MergeRequestSetMilestone'
argument :milestone_id,
GraphQL::ID_TYPE,
required: false,
loads: Types::MilestoneType,
description: <<~DESC
The milestone to assign to the merge request.
DESC
def resolve(project_path:, iid:, milestone: nil)
merge_request = authorized_find!(project_path: project_path, iid: iid)
project = merge_request.project
::MergeRequests::UpdateService.new(project, current_user, milestone: milestone)
.execute(merge_request)
{
merge_request: merge_request,
errors: merge_request.errors.full_messages
}
end
end
end
end
...@@ -6,6 +6,8 @@ module Types ...@@ -6,6 +6,8 @@ module Types
authorize :read_milestone authorize :read_milestone
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the milestone'
field :description, GraphQL::STRING_TYPE, null: true, field :description, GraphQL::STRING_TYPE, null: true,
description: 'Description of the milestone' description: 'Description of the milestone'
field :title, GraphQL::STRING_TYPE, null: false, field :title, GraphQL::STRING_TYPE, null: false,
......
...@@ -9,6 +9,7 @@ module Types ...@@ -9,6 +9,7 @@ module Types
mount_mutation Mutations::AwardEmojis::Add mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::MergeRequests::SetMilestone
mount_mutation Mutations::MergeRequests::SetWip, calls_gitaly: true mount_mutation Mutations::MergeRequests::SetWip, calls_gitaly: true
mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true
mount_mutation Mutations::Notes::Create::DiffNote, calls_gitaly: true mount_mutation Mutations::Notes::Create::DiffNote, calls_gitaly: true
......
...@@ -2,13 +2,6 @@ ...@@ -2,13 +2,6 @@
class ProjectSnippet < Snippet class ProjectSnippet < Snippet
belongs_to :project belongs_to :project
belongs_to :author, class_name: "User"
validates :project, presence: true validates :project, presence: true
# Scopes
scope :fresh, -> { order("created_at DESC") }
participant :author
participant :notes_with_associations
end end
...@@ -44,28 +44,52 @@ module Projects ...@@ -44,28 +44,52 @@ module Projects
end end
expose :url do |service| expose :url do |service|
service.dig('status', 'url') || "http://#{service.dig('status', 'domain')}" knative_06_07_url(service) || knative_05_url(service)
end end
expose :description do |service| expose :description do |service|
knative_07_description(service) || knative_05_06_description(service)
end
expose :image do |service|
service.dig( service.dig(
'spec', 'spec',
'runLatest', 'runLatest',
'configuration', 'configuration',
'revisionTemplate', 'build',
'template',
'name')
end
private
def knative_07_description(service)
service.dig(
'spec',
'template',
'metadata', 'metadata',
'annotations', 'annotations',
'Description') 'Description'
)
end end
expose :image do |service| def knative_05_url(service)
"http://#{service.dig('status', 'domain')}"
end
def knative_06_07_url(service)
service.dig('status', 'url')
end
def knative_05_06_description(service)
service.dig( service.dig(
'spec', 'spec',
'runLatest', 'runLatest',
'configuration', 'configuration',
'build', 'revisionTemplate',
'template', 'metadata',
'name') 'annotations',
'Description')
end end
end end
end end
......
- group_id = @cluster.group.id if @cluster.group_type?
- if @cluster.project_type?
- group_id = @cluster.project.group.id if @cluster.project.group
- user_id = @cluster.project.namespace.owner_id unless group_id
- if can?(current_user, :admin_cluster, @cluster) - if can?(current_user, :admin_cluster, @cluster)
- unless @cluster.provided_by_user? - unless @cluster.provided_by_user?
.append-bottom-20 .append-bottom-20
...@@ -7,6 +13,21 @@ ...@@ -7,6 +13,21 @@
- link_gke = link_to(s_('ClusterIntegration|Google Kubernetes Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer') - link_gke = link_to(s_('ClusterIntegration|Google Kubernetes Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke } = s_('ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke }
= form_for @cluster, url: clusterable.cluster_path(@cluster), as: :cluster, html: { class: 'cluster_management_form' } do |field|
%h5
= s_('ClusterIntegration|Cluster management project (alpha)')
.form-group
.form-text.text-muted
= project_select_tag('cluster[management_project_id]', class: 'hidden-filter-value', toggle_class: 'js-project-search js-project-filter js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
placeholder: _('Select project'), idAttribute: 'id', data: { order_by: 'last_activity_at', idattribute: 'id', simple_filter: true, allow_clear: true, include_groups: false, include_projects_in_subgroups: true, group_id: group_id, user_id: user_id }, value: @cluster.management_project_id)
.text-muted
= s_('ClusterIntegration|A cluster management project can be used to run deployment jobs with Kubernetes <code>cluster-admin</code> privileges.').html_safe
= link_to _('More information'), help_page_path('user/clusters/management_project.md'), target: '_blank'
.form-group
= field.submit _('Save changes'), class: 'btn btn-success qa-save-domain'
.sub-section.form-group .sub-section.form-group
%h4.text-danger %h4.text-danger
= s_('ClusterIntegration|Remove Kubernetes cluster integration') = s_('ClusterIntegration|Remove Kubernetes cluster integration')
......
---
title: Can directly add approvers to approval rule
merge_request: 18965
author:
type: changed
---
title: 'GraphQL: Add Merge Request milestone mutation'
merge_request: 19257
author:
type: added
---
title: Fix serverless function descriptions not showing on Knative 0.7
merge_request: 18973
author:
type: fixed
---
title: Save dashboard changes by the user into the vuex store
merge_request: 18862
author:
type: changed
---
title: Add ability to select a Cluster management project
merge_request: 18928
author:
type: added
---
title: Dependency Scanning template that doesn't rely on Docker-in-Docker
merge_request:
author:
type: other
...@@ -433,6 +433,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -433,6 +433,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
Gitlab.ee do Gitlab.ee do
get :logs get :logs
get '/pods/(:pod_name)/containers/(:container_name)/logs', to: 'environments#k8s_pod_logs', as: :k8s_pod_logs
end end
end end
......
...@@ -473,6 +473,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -473,6 +473,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `cherryPickOnCurrentMergeRequest` | Boolean! | Whether or not a user can perform `cherry_pick_on_current_merge_request` on this resource | | `cherryPickOnCurrentMergeRequest` | Boolean! | Whether or not a user can perform `cherry_pick_on_current_merge_request` on this resource |
| `revertOnCurrentMergeRequest` | Boolean! | Whether or not a user can perform `revert_on_current_merge_request` on this resource | | `revertOnCurrentMergeRequest` | Boolean! | Whether or not a user can perform `revert_on_current_merge_request` on this resource |
### MergeRequestSetMilestonePayload
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. |
| `mergeRequest` | MergeRequest | The merge request after mutation |
### MergeRequestSetWipPayload ### MergeRequestSetWipPayload
| Name | Type | Description | | Name | Type | Description |
...@@ -492,6 +500,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -492,6 +500,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `id` | ID! | ID of the milestone |
| `description` | String | Description of the milestone | | `description` | String | Description of the milestone |
| `title` | String! | Title of the milestone | | `title` | String! | Title of the milestone |
| `state` | String! | State of the milestone | | `state` | String! | State of the milestone |
......
...@@ -4,6 +4,12 @@ ...@@ -4,6 +4,12 @@
# List of the variables: https://gitlab.com/gitlab-org/security-products/dependency-scanning#settings # List of the variables: https://gitlab.com/gitlab-org/security-products/dependency-scanning#settings
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables # How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
variables:
DS_ANALYZER_IMAGE_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
DS_DEFAULT_ANALYZERS: "gemnasium, retire.js, gemnasium-python, gemnasium-maven, bundler-audit"
DS_MAJOR_VERSION: 2
DS_DISABLE_DIND: "false"
dependency_scanning: dependency_scanning:
stage: test stage: test
image: docker:stable image: docker:stable
...@@ -61,3 +67,63 @@ dependency_scanning: ...@@ -61,3 +67,63 @@ dependency_scanning:
except: except:
variables: variables:
- $DEPENDENCY_SCANNING_DISABLED - $DEPENDENCY_SCANNING_DISABLED
- $DS_DISABLE_DIND == 'true'
.analyzer:
extends: dependency_scanning
services: []
except:
variables:
- $DS_DISABLE_DIND == 'false'
script:
- /analyzer run
gemnasium-dependency_scanning:
extends: .analyzer
image:
name: "$DS_ANALYZER_IMAGE_PREFIX/gemnasium:$DS_MAJOR_VERSION"
only:
variables:
- $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
$DS_DEFAULT_ANALYZERS =~ /gemnasium/ &&
$CI_PROJECT_REPOSITORY_LANGUAGES =~ /ruby|javascript|php/
gemnasium-maven-dependency_scanning:
extends: .analyzer
image:
name: "$DS_ANALYZER_IMAGE_PREFIX/gemnasium-maven:$DS_MAJOR_VERSION"
only:
variables:
- $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
$DS_DEFAULT_ANALYZERS =~ /gemnasium-maven/ &&
$CI_PROJECT_REPOSITORY_LANGUAGES =~ /\bjava\b/
gemnasium-python-dependency_scanning:
extends: .analyzer
image:
name: "$DS_ANALYZER_IMAGE_PREFIX/gemnasium-python:$DS_MAJOR_VERSION"
only:
variables:
- $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
$DS_DEFAULT_ANALYZERS =~ /gemnasium-python/ &&
$CI_PROJECT_REPOSITORY_LANGUAGES =~ /python/
bundler-audit-dependency_scanning:
extends: .analyzer
image:
name: "$DS_ANALYZER_IMAGE_PREFIX/bundler-audit:$DS_MAJOR_VERSION"
only:
variables:
- $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
$DS_DEFAULT_ANALYZERS =~ /bundler-audit/ &&
$CI_PROJECT_REPOSITORY_LANGUAGES =~ /ruby/
retire-js-dependency_scanning:
extends: .analyzer
image:
name: "$DS_ANALYZER_IMAGE_PREFIX/retire.js:$DS_MAJOR_VERSION"
only:
variables:
- $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
$DS_DEFAULT_ANALYZERS =~ /retire.js/ &&
$CI_PROJECT_REPOSITORY_LANGUAGES =~ /javascript/
...@@ -176,19 +176,11 @@ module Gitlab ...@@ -176,19 +176,11 @@ module Gitlab
end end
def prometheus_enabled? def prometheus_enabled?
Gitlab.config.prometheus.enable if Gitlab.config.prometheus ::Gitlab::Prometheus::Internal.prometheus_enabled?
rescue Settingslogic::MissingSetting
log_error('prometheus.enable is not present in config/gitlab.yml')
false
end end
def prometheus_listen_address def prometheus_listen_address
Gitlab.config.prometheus.listen_address.to_s if Gitlab.config.prometheus ::Gitlab::Prometheus::Internal.listen_address
rescue Settingslogic::MissingSetting
log_error('Prometheus listen_address is not present in config/gitlab.yml')
nil
end end
def instance_admins def instance_admins
...@@ -231,23 +223,7 @@ module Gitlab ...@@ -231,23 +223,7 @@ module Gitlab
end end
def internal_prometheus_listen_address_uri def internal_prometheus_listen_address_uri
if prometheus_listen_address.starts_with?('0.0.0.0:') ::Gitlab::Prometheus::Internal.uri
# 0.0.0.0:9090
port = ':' + prometheus_listen_address.split(':').second
'http://localhost' + port
elsif prometheus_listen_address.starts_with?(':')
# :9090
'http://localhost' + prometheus_listen_address
elsif prometheus_listen_address.starts_with?('http')
# https://localhost:9090
prometheus_listen_address
else
# localhost:9090
'http://' + prometheus_listen_address
end
end end
def prometheus_service_attributes def prometheus_service_attributes
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
module Gitlab module Gitlab
module EtagCaching module EtagCaching
class Router class Router
prepend_if_ee('EE::Gitlab::EtagCaching::Router') # rubocop: disable Cop/InjectEnterpriseEditionModule
Route = Struct.new(:regexp, :name) Route = Struct.new(:regexp, :name)
# We enable an ETag for every request matching the regex. # We enable an ETag for every request matching the regex.
# To match a regex the path needs to match the following: # To match a regex the path needs to match the following:
...@@ -80,3 +78,5 @@ module Gitlab ...@@ -80,3 +78,5 @@ module Gitlab
end end
end end
end end
Gitlab::EtagCaching::Router.prepend_if_ee('EE::Gitlab::EtagCaching::Router')
# frozen_string_literal: true
module Gitlab
module Prometheus
class Internal
def self.uri
return if listen_address.blank?
if listen_address.starts_with?('0.0.0.0:')
# 0.0.0.0:9090
port = ':' + listen_address.split(':').second
'http://localhost' + port
elsif listen_address.starts_with?(':')
# :9090
'http://localhost' + listen_address
elsif listen_address.starts_with?('http')
# https://localhost:9090
listen_address
else
# localhost:9090
'http://' + listen_address
end
end
def self.listen_address
Gitlab.config.prometheus.listen_address.to_s if Gitlab.config.prometheus
rescue Settingslogic::MissingSetting
Gitlab::AppLogger.error('Prometheus listen_address is not present in config/gitlab.yml')
nil
end
def self.prometheus_enabled?
Gitlab.config.prometheus.enable if Gitlab.config.prometheus
rescue Settingslogic::MissingSetting
Gitlab::AppLogger.error('prometheus.enable is not present in config/gitlab.yml')
false
end
end
end
end
...@@ -3399,6 +3399,9 @@ msgstr "" ...@@ -3399,6 +3399,9 @@ msgstr ""
msgid "ClusterIntegration|%{title} updated successfully." msgid "ClusterIntegration|%{title} updated successfully."
msgstr "" msgstr ""
msgid "ClusterIntegration|A cluster management project can be used to run deployment jobs with Kubernetes <code>cluster-admin</code> privileges."
msgstr ""
msgid "ClusterIntegration|A service token scoped to %{code}kube-system%{end_code} with %{code}cluster-admin%{end_code} privileges." msgid "ClusterIntegration|A service token scoped to %{code}kube-system%{end_code} with %{code}cluster-admin%{end_code} privileges."
msgstr "" msgstr ""
...@@ -3507,6 +3510,9 @@ msgstr "" ...@@ -3507,6 +3510,9 @@ msgstr ""
msgid "ClusterIntegration|Cluster health" msgid "ClusterIntegration|Cluster health"
msgstr "" msgstr ""
msgid "ClusterIntegration|Cluster management project (alpha)"
msgstr ""
msgid "ClusterIntegration|Cluster name is required." msgid "ClusterIntegration|Cluster name is required."
msgstr "" msgstr ""
...@@ -15587,6 +15593,9 @@ msgstr "" ...@@ -15587,6 +15593,9 @@ msgstr ""
msgid "Something went wrong while fetching latest comments." msgid "Something went wrong while fetching latest comments."
msgstr "" msgstr ""
msgid "Something went wrong while fetching projects"
msgstr ""
msgid "Something went wrong while fetching related merge requests." msgid "Something went wrong while fetching related merge requests."
msgstr "" msgstr ""
......
# frozen_string_literal: true # frozen_string_literal: true
module QA module QA
context 'Create' do # Failure issue: https://gitlab.com/gitlab-org/gitlab/issues/34551
context 'Create', :quarantine do
describe 'File templates' do describe 'File templates' do
include Runtime::Fixtures include Runtime::Fixtures
......
# frozen_string_literal: true # frozen_string_literal: true
module QA module QA
context 'Create' do # Failure issue: https://gitlab.com/gitlab-org/gitlab/issues/34551
context 'Create', :quarantine do
describe 'Web IDE file templates' do describe 'Web IDE file templates' do
include Runtime::Fixtures include Runtime::Fixtures
......
...@@ -13,6 +13,10 @@ describe Projects::Serverless::FunctionsController do ...@@ -13,6 +13,10 @@ describe Projects::Serverless::FunctionsController do
let(:environment) { create(:environment, project: project) } let(:environment) { create(:environment, project: project) }
let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) } let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
let(:knative_services_finder) { environment.knative_services_finder } let(:knative_services_finder) { environment.knative_services_finder }
let(:function_description) { 'A serverless function' }
let(:knative_stub_options) do
{ namespace: namespace.namespace, name: cluster.project.name, description: function_description }
end
let(:namespace) do let(:namespace) do
create(:cluster_kubernetes_namespace, create(:cluster_kubernetes_namespace,
...@@ -114,40 +118,33 @@ describe Projects::Serverless::FunctionsController do ...@@ -114,40 +118,33 @@ describe Projects::Serverless::FunctionsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response).to include( expect(json_response).to include(
"name" => project.name, 'name' => project.name,
"url" => "http://#{project.name}.#{namespace.namespace}.example.com", 'url' => "http://#{project.name}.#{namespace.namespace}.example.com",
"podcount" => 1 'description' => function_description,
'podcount' => 1
) )
end end
end end
context 'on Knative 0.5' do context 'on Knative 0.5.0' do
before do before do
stub_kubeclient_service_pods prepare_knative_stubs(knative_05_service(knative_stub_options))
stub_reactive_cache(knative_services_finder,
{
services: kube_knative_services_body(
legacy_knative: true,
namespace: namespace.namespace,
name: cluster.project.name
)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
*knative_services_finder.cache_args)
end end
include_examples 'GET #show with valid data' include_examples 'GET #show with valid data'
end end
context 'on Knative 0.6 or 0.7' do context 'on Knative 0.6.0' do
before do before do
stub_kubeclient_service_pods prepare_knative_stubs(knative_06_service(knative_stub_options))
stub_reactive_cache(knative_services_finder, end
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], include_examples 'GET #show with valid data'
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] end
},
*knative_services_finder.cache_args) context 'on Knative 0.7.0' do
before do
prepare_knative_stubs(knative_07_service(knative_stub_options))
end end
include_examples 'GET #show with valid data' include_examples 'GET #show with valid data'
...@@ -172,11 +169,12 @@ describe Projects::Serverless::FunctionsController do ...@@ -172,11 +169,12 @@ describe Projects::Serverless::FunctionsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response).to match({ expect(json_response).to match({
"knative_installed" => "checking", 'knative_installed' => 'checking',
"functions" => [ 'functions' => [
a_hash_including( a_hash_including(
"name" => project.name, 'name' => project.name,
"url" => "http://#{project.name}.#{namespace.namespace}.example.com" 'url' => "http://#{project.name}.#{namespace.namespace}.example.com",
'description' => function_description
) )
] ]
}) })
...@@ -189,36 +187,38 @@ describe Projects::Serverless::FunctionsController do ...@@ -189,36 +187,38 @@ describe Projects::Serverless::FunctionsController do
end end
end end
context 'on Knative 0.5' do context 'on Knative 0.5.0' do
before do before do
stub_kubeclient_service_pods prepare_knative_stubs(knative_05_service(knative_stub_options))
stub_reactive_cache(knative_services_finder, end
{
services: kube_knative_services_body( include_examples 'GET #index with data'
legacy_knative: true, end
namespace: namespace.namespace,
name: cluster.project.name context 'on Knative 0.6.0' do
)["items"], before do
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] prepare_knative_stubs(knative_06_service(knative_stub_options))
},
*knative_services_finder.cache_args)
end end
include_examples 'GET #index with data' include_examples 'GET #index with data'
end end
context 'on Knative 0.6 or 0.7' do context 'on Knative 0.7.0' do
before do before do
prepare_knative_stubs(knative_07_service(knative_stub_options))
end
include_examples 'GET #index with data'
end
end
def prepare_knative_stubs(knative_service)
stub_kubeclient_service_pods stub_kubeclient_service_pods
stub_reactive_cache(knative_services_finder, stub_reactive_cache(knative_services_finder,
{ {
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], services: [knative_service],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
}, },
*knative_services_finder.cache_args) *knative_services_finder.cache_args)
end end
include_examples 'GET #index with data'
end
end
end end
...@@ -467,6 +467,26 @@ describe('Api', () => { ...@@ -467,6 +467,26 @@ describe('Api', () => {
}); });
}); });
describe('user projects', () => {
it('fetches all projects that belong to a particular user', done => {
const query = 'dummy query';
const options = { unused: 'option' };
const userId = '123456';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}/projects`;
mock.onGet(expectedUrl).reply(200, [
{
name: 'test',
},
]);
Api.userProjects(userId, query, options, response => {
expect(response.length).toBe(1);
expect(response[0].name).toBe('test');
done();
});
});
});
describe('commitPipelines', () => { describe('commitPipelines', () => {
it('fetches pipelines for a given commit', done => { it('fetches pipelines for a given commit', done => {
const projectId = 'example/foobar'; const projectId = 'example/foobar';
......
...@@ -10,8 +10,10 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -10,8 +10,10 @@ import axios from '~/lib/utils/axios_utils';
import { loadHTMLFixture } from 'helpers/fixtures'; import { loadHTMLFixture } from 'helpers/fixtures';
import { setTestTimeout } from 'helpers/timeout'; import { setTestTimeout } from 'helpers/timeout';
import $ from 'jquery'; import $ from 'jquery';
import initProjectSelectDropdown from '~/project_select';
jest.mock('~/lib/utils/poll'); jest.mock('~/lib/utils/poll');
jest.mock('~/project_select');
const { INSTALLING, INSTALLABLE, INSTALLED, UNINSTALLING } = APPLICATION_STATUS; const { INSTALLING, INSTALLABLE, INSTALLED, UNINSTALLING } = APPLICATION_STATUS;
...@@ -44,6 +46,7 @@ describe('Clusters', () => { ...@@ -44,6 +46,7 @@ describe('Clusters', () => {
afterEach(() => { afterEach(() => {
cluster.destroy(); cluster.destroy();
mock.restore(); mock.restore();
jest.clearAllMocks();
}); });
describe('class constructor', () => { describe('class constructor', () => {
...@@ -55,6 +58,10 @@ describe('Clusters', () => { ...@@ -55,6 +58,10 @@ describe('Clusters', () => {
it('should call initPolling on construct', () => { it('should call initPolling on construct', () => {
expect(cluster.initPolling).toHaveBeenCalled(); expect(cluster.initPolling).toHaveBeenCalled();
}); });
it('should call initProjectSelectDropdown on construct', () => {
expect(initProjectSelectDropdown).toHaveBeenCalled();
});
}); });
describe('toggle', () => { describe('toggle', () => {
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
class="js-kubernetes-logs" class="js-kubernetes-logs"
data-current-environment-name="production" data-current-environment-name="production"
data-environments-path="/root/my-project/environments.json" data-environments-path="/root/my-project/environments.json"
data-logs-endpoint="/root/my-project/environments/1/logs.json" data-project-full-path="root/my-project"
data-environment-id=1
> >
<div class="build-page-pod-logs"> <div class="build-page-pod-logs">
<div class="build-trace-container prepend-top-default"> <div class="build-trace-container prepend-top-default">
......
...@@ -61,8 +61,8 @@ describe('Embed', () => { ...@@ -61,8 +61,8 @@ describe('Embed', () => {
describe('metrics are available', () => { describe('metrics are available', () => {
beforeEach(() => { beforeEach(() => {
store.state.monitoringDashboard.groups = groups; store.state.monitoringDashboard.dashboard.panel_groups = groups;
store.state.monitoringDashboard.groups[0].metrics = metricsData; store.state.monitoringDashboard.dashboard.panel_groups[0].metrics = metricsData;
store.state.monitoringDashboard.metricsWithData = metricsWithData; store.state.monitoringDashboard.metricsWithData = metricsWithData;
mountComponent(); mountComponent();
......
...@@ -81,7 +81,9 @@ export const metricsData = [ ...@@ -81,7 +81,9 @@ export const metricsData = [
export const initialState = { export const initialState = {
monitoringDashboard: {}, monitoringDashboard: {},
groups: [], dashboard: {
panel_groups: [],
},
metricsWithData: [], metricsWithData: [],
useDashboardEndpoint: true, useDashboardEndpoint: true,
}; };
# frozen_string_literal: true
require 'spec_helper'
describe Mutations::MergeRequests::SetMilestone do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) }
describe '#resolve' do
let(:milestone) { create(:milestone, project: merge_request.project) }
let(:mutated_merge_request) { subject[:merge_request] }
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, milestone: milestone) }
it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
context 'when the user can update the merge request' do
before do
merge_request.project.add_developer(user)
end
it 'returns the merge request with the milestone' do
expect(mutated_merge_request).to eq(merge_request)
expect(mutated_merge_request.milestone).to eq(milestone)
expect(subject[:errors]).to be_empty
end
it 'returns errors merge request could not be updated' do
# Make the merge request invalid
merge_request.allow_broken = true
merge_request.update!(source_project: nil)
expect(subject[:errors]).not_to be_empty
end
context 'when passing milestone_id as nil' do
let(:milestone) { nil }
it 'removes the milestone' do
merge_request.update!(milestone: create(:milestone, project: merge_request.project))
expect(mutated_merge_request.milestone).to eq(nil)
end
it 'does not do anything if the MR already does not have a milestone' do
expect(mutated_merge_request.milestone).to eq(nil)
end
end
end
end
end
...@@ -23,7 +23,7 @@ describe('Time series component', () => { ...@@ -23,7 +23,7 @@ describe('Time series component', () => {
store = createStore(); store = createStore();
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data); store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
[mockGraphData] = store.state.monitoringDashboard.groups[0].metrics; [, mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[0].metrics;
makeTimeSeriesChart = (graphData, type) => makeTimeSeriesChart = (graphData, type) =>
shallowMount(TimeSeries, { shallowMount(TimeSeries, {
......
...@@ -442,6 +442,28 @@ describe('Dashboard', () => { ...@@ -442,6 +442,28 @@ describe('Dashboard', () => {
expect(findEnabledDraggables()).toEqual(findDraggables()); expect(findEnabledDraggables()).toEqual(findDraggables());
}); });
it('metrics can be swapped', done => {
const firstDraggable = findDraggables().at(0);
const mockMetrics = [...metricsGroupsAPIResponse.data[0].metrics];
const value = () => firstDraggable.props('value');
expect(value().length).toBe(mockMetrics.length);
value().forEach((metric, i) => {
expect(metric.title).toBe(mockMetrics[i].title);
});
// swap two elements and `input` them
[mockMetrics[0], mockMetrics[1]] = [mockMetrics[1], mockMetrics[0]];
firstDraggable.vm.$emit('input', mockMetrics);
firstDraggable.vm.$nextTick(() => {
value().forEach((metric, i) => {
expect(metric.title).toBe(mockMetrics[i].title);
});
done();
});
});
it('shows a remove button, which removes a panel', done => { it('shows a remove button, which removes a panel', done => {
expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false); expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false);
...@@ -449,8 +471,6 @@ describe('Dashboard', () => { ...@@ -449,8 +471,6 @@ describe('Dashboard', () => {
findFirstDraggableRemoveButton().trigger('click'); findFirstDraggableRemoveButton().trigger('click');
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
// At present graphs will not be removed in backend
// See https://gitlab.com/gitlab-org/gitlab/issues/27835
expect(findDraggablePanels().length).toEqual(expectedPanelCount - 1); expect(findDraggablePanels().length).toEqual(expectedPanelCount - 1);
done(); done();
}); });
...@@ -686,7 +706,9 @@ describe('Dashboard', () => { ...@@ -686,7 +706,9 @@ describe('Dashboard', () => {
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
MonitoringMock.data, MonitoringMock.data,
); );
[mockGraphData] = component.$store.state.monitoringDashboard.groups[0].metrics; [
mockGraphData,
] = component.$store.state.monitoringDashboard.dashboard.panel_groups[0].metrics;
}); });
describe('csvText', () => { describe('csvText', () => {
......
...@@ -291,9 +291,9 @@ describe('Monitoring store actions', () => { ...@@ -291,9 +291,9 @@ describe('Monitoring store actions', () => {
it('dispatches fetchPrometheusMetric for each panel query', done => { it('dispatches fetchPrometheusMetric for each panel query', done => {
const params = {}; const params = {};
const state = storeState(); const state = storeState();
state.groups = metricsDashboardResponse.dashboard.panel_groups; state.dashboard.panel_groups = metricsDashboardResponse.dashboard.panel_groups;
const metric = state.groups[0].panels[0].metrics[0]; const metric = state.dashboard.panel_groups[0].panels[0].metrics[0];
fetchPrometheusMetrics({ state, commit, dispatch }, params) fetchPrometheusMetrics({ state, commit, dispatch }, params)
.then(() => { .then(() => {
......
...@@ -20,16 +20,26 @@ describe('Monitoring mutations', () => { ...@@ -20,16 +20,26 @@ describe('Monitoring mutations', () => {
let groups; let groups;
beforeEach(() => { beforeEach(() => {
stateCopy.groups = []; stateCopy.dashboard.panel_groups = [];
groups = metricsGroupsAPIResponse.data; groups = metricsGroupsAPIResponse.data;
}); });
it('adds a key to the group', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
expect(stateCopy.dashboard.panel_groups[0].key).toBe('kubernetes-0');
expect(stateCopy.dashboard.panel_groups[1].key).toBe('nginx-1');
});
it('normalizes values', () => { it('normalizes values', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
const expectedTimestamp = '2017-05-25T08:22:34.925Z'; const expectedTimestamp = '2017-05-25T08:22:34.925Z';
const expectedValue = 0.0010794445585559514; const expectedValue = 8.0390625;
const [timestamp, value] = stateCopy.groups[0].metrics[0].queries[0].result[0].values[0]; const [
timestamp,
value,
] = stateCopy.dashboard.panel_groups[0].metrics[0].queries[0].result[0].values[0];
expect(timestamp).toEqual(expectedTimestamp); expect(timestamp).toEqual(expectedTimestamp);
expect(value).toEqual(expectedValue); expect(value).toEqual(expectedValue);
...@@ -38,25 +48,25 @@ describe('Monitoring mutations', () => { ...@@ -38,25 +48,25 @@ describe('Monitoring mutations', () => {
it('contains two groups that contains, one of which has two queries sorted by priority', () => { it('contains two groups that contains, one of which has two queries sorted by priority', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
expect(stateCopy.groups).toBeDefined(); expect(stateCopy.dashboard.panel_groups).toBeDefined();
expect(stateCopy.groups.length).toEqual(2); expect(stateCopy.dashboard.panel_groups.length).toEqual(2);
expect(stateCopy.groups[0].metrics.length).toEqual(2); expect(stateCopy.dashboard.panel_groups[0].metrics.length).toEqual(2);
}); });
it('assigns queries a metric id', () => { it('assigns queries a metric id', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
expect(stateCopy.groups[1].metrics[0].queries[0].metricId).toEqual('100'); expect(stateCopy.dashboard.panel_groups[1].metrics[0].queries[0].metricId).toEqual('100');
}); });
it('removes the data if all the values from a query are not defined', () => { it('removes the data if all the values from a query are not defined', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
expect(stateCopy.groups[1].metrics[0].queries[0].result.length).toEqual(0); expect(stateCopy.dashboard.panel_groups[1].metrics[0].queries[0].result.length).toEqual(0);
}); });
it('assigns metric id of null if metric has no id', () => { it('assigns metric id of null if metric has no id', () => {
stateCopy.groups = []; stateCopy.dashboard.panel_groups = [];
const noId = groups.map(group => ({ const noId = groups.map(group => ({
...group, ...group,
...{ ...{
...@@ -70,7 +80,7 @@ describe('Monitoring mutations', () => { ...@@ -70,7 +80,7 @@ describe('Monitoring mutations', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, noId); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, noId);
stateCopy.groups.forEach(group => { stateCopy.dashboard.panel_groups.forEach(group => {
group.metrics.forEach(metric => { group.metrics.forEach(metric => {
expect(metric.queries.every(query => query.metricId === null)).toBe(true); expect(metric.queries.every(query => query.metricId === null)).toBe(true);
}); });
...@@ -87,13 +97,13 @@ describe('Monitoring mutations', () => { ...@@ -87,13 +97,13 @@ describe('Monitoring mutations', () => {
it('aliases group panels to metrics for backwards compatibility', () => { it('aliases group panels to metrics for backwards compatibility', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
expect(stateCopy.groups[0].metrics[0]).toBeDefined(); expect(stateCopy.dashboard.panel_groups[0].metrics[0]).toBeDefined();
}); });
it('aliases panel metrics to queries for backwards compatibility', () => { it('aliases panel metrics to queries for backwards compatibility', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
expect(stateCopy.groups[0].metrics[0].queries).toBeDefined(); expect(stateCopy.dashboard.panel_groups[0].metrics[0].queries).toBeDefined();
}); });
}); });
}); });
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Prometheus::Internal do
let(:listen_address) { 'localhost:9090' }
let(:prometheus_settings) do
{
enable: true,
listen_address: listen_address
}
end
before do
stub_config(prometheus: prometheus_settings)
end
describe '.uri' do
shared_examples 'returns valid uri' do |uri_string|
it do
expect(described_class.uri).to eq(uri_string)
expect { Addressable::URI.parse(described_class.uri) }.not_to raise_error
end
end
it_behaves_like 'returns valid uri', 'http://localhost:9090'
context 'with non default prometheus address' do
let(:listen_address) { 'https://localhost:9090' }
it_behaves_like 'returns valid uri', 'https://localhost:9090'
context 'with :9090 symbol' do
let(:listen_address) { :':9090' }
it_behaves_like 'returns valid uri', 'http://localhost:9090'
end
context 'with 0.0.0.0:9090' do
let(:listen_address) { '0.0.0.0:9090' }
it_behaves_like 'returns valid uri', 'http://localhost:9090'
end
end
context 'when listen_address is nil' do
let(:listen_address) { nil }
it 'does not fail' do
expect(described_class.uri).to eq(nil)
end
end
context 'when prometheus listen address is blank in gitlab.yml' do
let(:listen_address) { '' }
it 'does not configure prometheus' do
expect(described_class.uri).to eq(nil)
end
end
end
describe 'prometheus_enabled?' do
it 'returns correct value' do
expect(described_class.prometheus_enabled?).to eq(true)
end
context 'when prometheus setting is disabled in gitlab.yml' do
let(:prometheus_settings) do
{
enable: false,
listen_address: listen_address
}
end
it 'returns correct value' do
expect(described_class.prometheus_enabled?).to eq(false)
end
end
context 'when prometheus setting is not present in gitlab.yml' do
before do
allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting)
end
it 'does not fail' do
expect(described_class.prometheus_enabled?).to eq(false)
end
end
end
describe '.listen_address' do
it 'returns correct value' do
expect(described_class.listen_address).to eq(listen_address)
end
context 'when prometheus setting is not present in gitlab.yml' do
before do
allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting)
end
it 'does not fail' do
expect(described_class.listen_address).to eq(nil)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe 'Setting milestone of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
let(:milestone) { create(:milestone, project: project) }
let(:input) { { milestone_id: GitlabSchema.id_from_object(milestone).to_s } }
let(:mutation) do
variables = {
project_path: project.full_path,
iid: merge_request.iid.to_s
}
graphql_mutation(:merge_request_set_milestone, variables.merge(input),
<<-QL.strip_heredoc
clientMutationId
errors
mergeRequest {
id
milestone {
id
}
}
QL
)
end
def mutation_response
graphql_mutation_response(:merge_request_set_milestone)
end
before do
project.add_developer(current_user)
end
it 'returns an error if the user is not allowed to update the merge request' do
post_graphql_mutation(mutation, current_user: create(:user))
expect(graphql_errors).not_to be_empty
end
it 'sets the merge request milestone' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['mergeRequest']['milestone']['id']).to eq(milestone.to_global_id.to_s)
end
context 'when passing milestone_id nil as input' do
let(:input) { { milestone_id: nil } }
it 'removes the merge request milestone' do
merge_request.update!(milestone: milestone)
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['mergeRequest']['milestone']).to be_nil
end
end
end
This diff is collapsed.
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