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:
- .default-only
- .default-before_script
- .only:changes-graphql
variables:
SETUP_DB: "false"
- .use-pg9
stage: test
needs: ["setup-test-env"]
script:
......
......@@ -2,6 +2,8 @@ import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import { joinPaths } from './lib/utils/url_utility';
import flash from '~/flash';
import { __ } from '~/locale';
const Api = {
groupsPath: '/api/:version/groups.json',
......@@ -29,6 +31,7 @@ const Api = {
usersPath: '/api/:version/users.json',
userPath: '/api/:version/users/:id',
userStatusPath: '/api/:version/users/:id/status',
userProjectsPath: '/api/:version/users/:id/projects',
userPostStatusPath: '/api/:version/user/status',
commitPath: '/api/:version/projects/:id/repository/commits',
applySuggestionPath: '/api/:version/suggestions/:id/apply',
......@@ -239,7 +242,8 @@ const Api = {
.get(url, {
params: Object.assign({}, defaults, options),
})
.then(({ data }) => callback(data));
.then(({ data }) => callback(data))
.catch(() => flash(__('Something went wrong while fetching projects')));
},
commitMultiple(id, data) {
......@@ -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 = {}) {
const url = Api.buildUrl(this.createBranchPath).replace(':id', encodeURIComponent(id));
......
......@@ -13,6 +13,7 @@ import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
import Applications from './components/applications.vue';
import setupToggleButtons from '../toggle_buttons';
import initProjectSelectDropdown from '~/project_select';
const Environments = () => import('ee_component/clusters/components/environments.vue');
......@@ -110,8 +111,10 @@ export default class Clusters {
this.ingressDomainHelpText &&
this.ingressDomainHelpText.querySelector('.js-ingress-domain-snippet');
initProjectSelectDropdown();
Clusters.initDismissableCallout();
initSettingsPanels();
const toggleButtonsContainer = document.querySelector('.js-cluster-enable-toggle-area');
if (toggleButtonsContainer) {
setupToggleButtons(toggleButtonsContainer);
......
......@@ -174,7 +174,7 @@ export default {
return this.customMetricsAvailable && this.customMetricsPath.length;
},
...mapState('monitoringDashboard', [
'groups',
'dashboard',
'emptyState',
'showEmptyState',
'environments',
......@@ -238,6 +238,7 @@ export default {
'setGettingStartedEmptyState',
'setEndpoints',
'setDashboardEnabled',
'setPanelGroupMetrics',
]),
chartsWithData(charts) {
if (!this.useDashboardEndpoint) {
......@@ -274,10 +275,17 @@ export default {
this.$toast.show(__('Link copied'));
},
// TODO: END
removeGraph(metrics, graphIndex) {
// At present graphs will not be removed, they should removed using the vuex store
// See https://gitlab.com/gitlab-org/gitlab/issues/27835
metrics.splice(graphIndex, 1);
updateMetrics(key, metrics) {
this.setPanelGroupMetrics({
metrics,
key,
});
},
removeMetric(key, metrics, graphIndex) {
this.setPanelGroupMetrics({
metrics: metrics.filter((v, i) => i !== graphIndex),
key,
});
},
showInvalidDateError() {
createFlash(s__('Metrics|Link contains an invalid time window.'));
......@@ -447,7 +455,7 @@ export default {
<div v-if="!showEmptyState">
<graph-group
v-for="(groupData, index) in groups"
v-for="(groupData, index) in dashboard.panel_groups"
:key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group"
:show-panels="showPanels"
......@@ -455,10 +463,11 @@ export default {
>
<template v-if="additionalPanelTypesEnabled">
<vue-draggable
:list="groupData.metrics"
:value="groupData.metrics"
group="metrics-dashboard"
:component-data="{ attrs: { class: 'row mx-0 w-100' } }"
:disabled="!isRearrangingPanels"
@input="updateMetrics(groupData.key, $event)"
>
<div
v-for="(graphData, graphIndex) in groupData.metrics"
......@@ -470,7 +479,7 @@ export default {
<div
v-if="isRearrangingPanels"
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')"
><icon name="close"
......
......@@ -35,9 +35,9 @@ export default {
};
},
computed: {
...mapState('monitoringDashboard', ['groups', 'metricsWithData']),
...mapState('monitoringDashboard', ['dashboard', 'metricsWithData']),
charts() {
const groupWithMetrics = this.groups.find(group =>
const groupWithMetrics = this.dashboard.panel_groups.find(group =>
group.metrics.find(chart => this.chartHasData(chart)),
) || { metrics: [] };
......
......@@ -166,7 +166,7 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch }, params) => {
commit(types.REQUEST_METRICS_DATA);
const promises = [];
state.groups.forEach(group => {
state.dashboard.panel_groups.forEach(group => {
group.panels.forEach(panel => {
panel.metrics.forEach(metric => {
promises.push(dispatch('fetchPrometheusMetric', { metric, params }));
......@@ -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
export default () => {};
......@@ -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_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
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 { slugify } from '~/lib/utils/text_utility';
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);
......@@ -10,10 +11,12 @@ export default {
state.showEmptyState = true;
},
[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;
// each panel has metric information that needs to be normalized
panels = panels.map(panel => ({
...panel,
metrics: normalizePanel(panel),
......@@ -32,11 +35,12 @@ export default {
return {
...group,
panels,
metrics: normalizeMetrics(sortMetrics(metrics)),
key,
metrics: normalizeMetrics(metrics),
};
});
if (!state.groups.length) {
if (!state.dashboard.panel_groups.length) {
state.emptyState = 'noData';
} else {
state.showEmptyState = false;
......@@ -65,7 +69,7 @@ export default {
state.showEmptyState = false;
state.groups.forEach(group => {
state.dashboard.panel_groups.forEach(group => {
group.metrics.forEach(metric => {
metric.queries.forEach(query => {
if (query.metric_id === metricId) {
......@@ -105,4 +109,8 @@ export default {
[types.SET_SHOW_ERROR_BANNER](state, 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 () => ({
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
groups: [],
dashboard: {
panel_groups: [],
},
deploymentData: [],
environments: [],
metricsWithData: [],
......
......@@ -82,12 +82,6 @@ export const normalizeMetric = (metric = {}) =>
'id',
);
export const sortMetrics = metrics =>
_.chain(metrics)
.sortBy('title')
.sortBy('weight')
.value();
export const normalizeQueryResult = timeSeries => {
let normalizedResult = {};
......
......@@ -9,7 +9,9 @@ const projectSelect = () => {
$('.ajax-project-select').each(function(i, select) {
var placeholder;
const simpleFilter = $(select).data('simpleFilter') || false;
const isInstantiated = $(select).data('select2');
this.groupId = $(select).data('groupId');
this.userId = $(select).data('userId');
this.includeGroups = $(select).data('includeGroups');
this.allProjects = $(select).data('allProjects') || false;
this.orderBy = $(select).data('orderBy') || 'id';
......@@ -63,6 +65,18 @@ const projectSelect = () => {
},
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 {
return Api.projects(
query.term,
......@@ -96,7 +110,7 @@ const projectSelect = () => {
dropdownCssClass: 'ajax-project-dropdown',
});
if (simpleFilter) return select;
if (isInstantiated || simpleFilter) return select;
return new ProjectSelectComboButton(select);
});
};
......
......@@ -46,7 +46,7 @@ class GitlabSchema < GraphQL::Schema
super(query_str, **kwargs)
end
def id_from_object(object)
def id_from_object(object, _type = nil, _ctx = nil)
unless object.respond_to?(:to_global_id)
# This is an error in our schema and needs to be solved. So raise a
# more meaningful error message
......@@ -57,7 +57,7 @@ class GitlabSchema < GraphQL::Schema
object.to_global_id
end
def object_from_id(global_id)
def object_from_id(global_id, _ctx = nil)
gid = GlobalID.parse(global_id)
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
authorize :read_milestone
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the milestone'
field :description, GraphQL::STRING_TYPE, null: true,
description: 'Description of the milestone'
field :title, GraphQL::STRING_TYPE, null: false,
......
......@@ -9,6 +9,7 @@ module Types
mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::MergeRequests::SetMilestone
mount_mutation Mutations::MergeRequests::SetWip, calls_gitaly: true
mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true
mount_mutation Mutations::Notes::Create::DiffNote, calls_gitaly: true
......
......@@ -2,13 +2,6 @@
class ProjectSnippet < Snippet
belongs_to :project
belongs_to :author, class_name: "User"
validates :project, presence: true
# Scopes
scope :fresh, -> { order("created_at DESC") }
participant :author
participant :notes_with_associations
end
......@@ -44,28 +44,52 @@ module Projects
end
expose :url do |service|
service.dig('status', 'url') || "http://#{service.dig('status', 'domain')}"
knative_06_07_url(service) || knative_05_url(service)
end
expose :description do |service|
knative_07_description(service) || knative_05_06_description(service)
end
expose :image do |service|
service.dig(
'spec',
'runLatest',
'configuration',
'revisionTemplate',
'build',
'template',
'name')
end
private
def knative_07_description(service)
service.dig(
'spec',
'template',
'metadata',
'annotations',
'Description')
'Description'
)
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(
'spec',
'runLatest',
'configuration',
'build',
'template',
'name')
'revisionTemplate',
'metadata',
'annotations',
'Description')
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)
- unless @cluster.provided_by_user?
.append-bottom-20
......@@ -7,6 +13,21 @@
- 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 }
= 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
%h4.text-danger
= 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
Gitlab.ee do
get :logs
get '/pods/(:pod_name)/containers/(:container_name)/logs', to: 'environments#k8s_pod_logs', as: :k8s_pod_logs
end
end
......
......@@ -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 |
| `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
| Name | Type | Description |
......@@ -492,6 +500,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description |
| --- | ---- | ---------- |
| `id` | ID! | ID of the milestone |
| `description` | String | Description of the milestone |
| `title` | String! | Title of the milestone |
| `state` | String! | State of the milestone |
......
......@@ -4,6 +4,12 @@
# 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
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:
stage: test
image: docker:stable
......@@ -61,3 +67,63 @@ dependency_scanning:
except:
variables:
- $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
end
def prometheus_enabled?
Gitlab.config.prometheus.enable if Gitlab.config.prometheus
rescue Settingslogic::MissingSetting
log_error('prometheus.enable is not present in config/gitlab.yml')
false
::Gitlab::Prometheus::Internal.prometheus_enabled?
end
def prometheus_listen_address
Gitlab.config.prometheus.listen_address.to_s if Gitlab.config.prometheus
rescue Settingslogic::MissingSetting
log_error('Prometheus listen_address is not present in config/gitlab.yml')
nil
::Gitlab::Prometheus::Internal.listen_address
end
def instance_admins
......@@ -231,23 +223,7 @@ module Gitlab
end
def internal_prometheus_listen_address_uri
if prometheus_listen_address.starts_with?('0.0.0.0:')
# 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
::Gitlab::Prometheus::Internal.uri
end
def prometheus_service_attributes
......
......@@ -3,8 +3,6 @@
module Gitlab
module EtagCaching
class Router
prepend_if_ee('EE::Gitlab::EtagCaching::Router') # rubocop: disable Cop/InjectEnterpriseEditionModule
Route = Struct.new(:regexp, :name)
# We enable an ETag for every request matching the regex.
# To match a regex the path needs to match the following:
......@@ -80,3 +78,5 @@ module Gitlab
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 ""
msgid "ClusterIntegration|%{title} updated successfully."
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."
msgstr ""
......@@ -3507,6 +3510,9 @@ msgstr ""
msgid "ClusterIntegration|Cluster health"
msgstr ""
msgid "ClusterIntegration|Cluster management project (alpha)"
msgstr ""
msgid "ClusterIntegration|Cluster name is required."
msgstr ""
......@@ -15587,6 +15593,9 @@ msgstr ""
msgid "Something went wrong while fetching latest comments."
msgstr ""
msgid "Something went wrong while fetching projects"
msgstr ""
msgid "Something went wrong while fetching related merge requests."
msgstr ""
......
# frozen_string_literal: true
module QA
context 'Create' do
# Failure issue: https://gitlab.com/gitlab-org/gitlab/issues/34551
context 'Create', :quarantine do
describe 'File templates' do
include Runtime::Fixtures
......
# frozen_string_literal: true
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
include Runtime::Fixtures
......
......@@ -13,6 +13,10 @@ describe Projects::Serverless::FunctionsController do
let(:environment) { create(:environment, project: project) }
let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
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
create(:cluster_kubernetes_namespace,
......@@ -114,40 +118,33 @@ describe Projects::Serverless::FunctionsController do
expect(response).to have_gitlab_http_status(200)
expect(json_response).to include(
"name" => project.name,
"url" => "http://#{project.name}.#{namespace.namespace}.example.com",
"podcount" => 1
'name' => project.name,
'url' => "http://#{project.name}.#{namespace.namespace}.example.com",
'description' => function_description,
'podcount' => 1
)
end
end
context 'on Knative 0.5' do
context 'on Knative 0.5.0' do
before do
prepare_knative_stubs(knative_05_service(knative_stub_options))
end
include_examples 'GET #show with valid data'
end
context 'on Knative 0.6.0' do
before do
stub_kubeclient_service_pods
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)
prepare_knative_stubs(knative_06_service(knative_stub_options))
end
include_examples 'GET #show with valid data'
end
context 'on Knative 0.6 or 0.7' do
context 'on Knative 0.7.0' do
before do
stub_kubeclient_service_pods
stub_reactive_cache(knative_services_finder,
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
*knative_services_finder.cache_args)
prepare_knative_stubs(knative_07_service(knative_stub_options))
end
include_examples 'GET #show with valid data'
......@@ -172,11 +169,12 @@ describe Projects::Serverless::FunctionsController do
expect(response).to have_gitlab_http_status(200)
expect(json_response).to match({
"knative_installed" => "checking",
"functions" => [
'knative_installed' => 'checking',
'functions' => [
a_hash_including(
"name" => project.name,
"url" => "http://#{project.name}.#{namespace.namespace}.example.com"
'name' => project.name,
'url' => "http://#{project.name}.#{namespace.namespace}.example.com",
'description' => function_description
)
]
})
......@@ -189,36 +187,38 @@ describe Projects::Serverless::FunctionsController do
end
end
context 'on Knative 0.5' do
context 'on Knative 0.5.0' do
before do
stub_kubeclient_service_pods
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)
prepare_knative_stubs(knative_05_service(knative_stub_options))
end
include_examples 'GET #index with data'
end
context 'on Knative 0.6 or 0.7' do
context 'on Knative 0.6.0' do
before do
stub_kubeclient_service_pods
stub_reactive_cache(knative_services_finder,
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
*knative_services_finder.cache_args)
prepare_knative_stubs(knative_06_service(knative_stub_options))
end
include_examples 'GET #index with data'
end
context 'on Knative 0.7.0' 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_reactive_cache(knative_services_finder,
{
services: [knative_service],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
*knative_services_finder.cache_args)
end
end
......@@ -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', () => {
it('fetches pipelines for a given commit', done => {
const projectId = 'example/foobar';
......
......@@ -10,8 +10,10 @@ import axios from '~/lib/utils/axios_utils';
import { loadHTMLFixture } from 'helpers/fixtures';
import { setTestTimeout } from 'helpers/timeout';
import $ from 'jquery';
import initProjectSelectDropdown from '~/project_select';
jest.mock('~/lib/utils/poll');
jest.mock('~/project_select');
const { INSTALLING, INSTALLABLE, INSTALLED, UNINSTALLING } = APPLICATION_STATUS;
......@@ -44,6 +46,7 @@ describe('Clusters', () => {
afterEach(() => {
cluster.destroy();
mock.restore();
jest.clearAllMocks();
});
describe('class constructor', () => {
......@@ -55,6 +58,10 @@ describe('Clusters', () => {
it('should call initPolling on construct', () => {
expect(cluster.initPolling).toHaveBeenCalled();
});
it('should call initProjectSelectDropdown on construct', () => {
expect(initProjectSelectDropdown).toHaveBeenCalled();
});
});
describe('toggle', () => {
......
......@@ -2,7 +2,8 @@
class="js-kubernetes-logs"
data-current-environment-name="production"
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-trace-container prepend-top-default">
......
......@@ -61,8 +61,8 @@ describe('Embed', () => {
describe('metrics are available', () => {
beforeEach(() => {
store.state.monitoringDashboard.groups = groups;
store.state.monitoringDashboard.groups[0].metrics = metricsData;
store.state.monitoringDashboard.dashboard.panel_groups = groups;
store.state.monitoringDashboard.dashboard.panel_groups[0].metrics = metricsData;
store.state.monitoringDashboard.metricsWithData = metricsWithData;
mountComponent();
......
......@@ -81,7 +81,9 @@ export const metricsData = [
export const initialState = {
monitoringDashboard: {},
groups: [],
dashboard: {
panel_groups: [],
},
metricsWithData: [],
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', () => {
store = createStore();
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
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) =>
shallowMount(TimeSeries, {
......
......@@ -442,6 +442,28 @@ describe('Dashboard', () => {
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 => {
expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false);
......@@ -449,8 +471,6 @@ describe('Dashboard', () => {
findFirstDraggableRemoveButton().trigger('click');
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);
done();
});
......@@ -686,7 +706,9 @@ describe('Dashboard', () => {
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
MonitoringMock.data,
);
[mockGraphData] = component.$store.state.monitoringDashboard.groups[0].metrics;
[
mockGraphData,
] = component.$store.state.monitoringDashboard.dashboard.panel_groups[0].metrics;
});
describe('csvText', () => {
......
......@@ -291,9 +291,9 @@ describe('Monitoring store actions', () => {
it('dispatches fetchPrometheusMetric for each panel query', done => {
const params = {};
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)
.then(() => {
......
......@@ -20,16 +20,26 @@ describe('Monitoring mutations', () => {
let groups;
beforeEach(() => {
stateCopy.groups = [];
stateCopy.dashboard.panel_groups = [];
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', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
const expectedTimestamp = '2017-05-25T08:22:34.925Z';
const expectedValue = 0.0010794445585559514;
const [timestamp, value] = stateCopy.groups[0].metrics[0].queries[0].result[0].values[0];
const expectedValue = 8.0390625;
const [
timestamp,
value,
] = stateCopy.dashboard.panel_groups[0].metrics[0].queries[0].result[0].values[0];
expect(timestamp).toEqual(expectedTimestamp);
expect(value).toEqual(expectedValue);
......@@ -38,25 +48,25 @@ describe('Monitoring mutations', () => {
it('contains two groups that contains, one of which has two queries sorted by priority', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
expect(stateCopy.groups).toBeDefined();
expect(stateCopy.groups.length).toEqual(2);
expect(stateCopy.groups[0].metrics.length).toEqual(2);
expect(stateCopy.dashboard.panel_groups).toBeDefined();
expect(stateCopy.dashboard.panel_groups.length).toEqual(2);
expect(stateCopy.dashboard.panel_groups[0].metrics.length).toEqual(2);
});
it('assigns queries a metric id', () => {
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', () => {
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', () => {
stateCopy.groups = [];
stateCopy.dashboard.panel_groups = [];
const noId = groups.map(group => ({
...group,
...{
......@@ -70,7 +80,7 @@ describe('Monitoring mutations', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, noId);
stateCopy.groups.forEach(group => {
stateCopy.dashboard.panel_groups.forEach(group => {
group.metrics.forEach(metric => {
expect(metric.queries.every(query => query.metricId === null)).toBe(true);
});
......@@ -87,13 +97,13 @@ describe('Monitoring mutations', () => {
it('aliases group panels to metrics for backwards compatibility', () => {
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', () => {
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