Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-09-28 00:08:50 +00:00
parent bce6d50b9c
commit 5ca56fbe46
43 changed files with 151 additions and 230 deletions

View file

@ -561,3 +561,6 @@ gem 'ed25519', '~> 1.3.0'
# Error Tracking OpenAPI client
# See https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/rake_tasks.md#update-openapi-client-for-error-tracking-feature
gem 'error_tracking_open_api', path: 'vendor/gems/error_tracking_open_api'
# Vulnerability advisories
gem 'cvss-suite', '~> 3.0.1', require: 'cvss_suite'

View file

@ -91,6 +91,7 @@
{"name":"creole","version":"0.5.0","platform":"ruby","checksum":"951701e2d80760f156b1cb2a93471ca97c076289becc067a33b745133ed32c03"},
{"name":"crystalball","version":"0.7.0","platform":"ruby","checksum":"6e729f372a5071daec877adb40c5df4cb25fe21f350635e2a9624373fc151ef2"},
{"name":"css_parser","version":"1.11.0","platform":"ruby","checksum":"568926c3193579446ad3e3f9d761c73e2918ee5b3b7757a1a49ec166c67d6de1"},
{"name":"cvss-suite","version":"3.0.1","platform":"ruby","checksum":"b5ca9e9e94032a42fd0dc28c1e305378b62c949e35ed7111fc4a1d76f68ad3f9"},
{"name":"danger","version":"8.6.1","platform":"ruby","checksum":"d95eb58b41f68d3aaa9bbef697916b6b4d161a38819517c98562531be75cdfd8"},
{"name":"danger-gitlab","version":"8.0.0","platform":"ruby","checksum":"497dd7d0f6513913de651019223d8058cf494df10acbd17de92b175dfa04a3a8"},
{"name":"database_cleaner","version":"1.7.0","platform":"ruby","checksum":"bdf833c197afac7054015bcde2567c3834c366bbfe6a377c30151ca984b32016"},

View file

@ -303,6 +303,7 @@ GEM
git
css_parser (1.11.0)
addressable
cvss-suite (3.0.1)
danger (8.6.1)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
@ -1562,6 +1563,7 @@ DEPENDENCIES
countries (~> 3.0)
creole (~> 0.5.0)
crystalball (~> 0.7.0)
cvss-suite (~> 3.0.1)
database_cleaner (~> 1.7.0)
deckar01-task_list (= 2.3.1)
declarative_policy (~> 1.1.0)

View file

@ -2,7 +2,7 @@
import { GlModal, GlTabs, GlTab, GlSearchBoxByType, GlSprintf, GlBadge } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import ReviewTabContainer from '~/add_context_commits_modal/components/review_tab_container.vue';
import createFlash from '~/flash';
import { createAlert } from '~/flash';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import { s__ } from '~/locale';
import eventHub from '../event_hub';
@ -193,7 +193,7 @@ export default {
window.location.reload();
}
if (!values[0] && !values[1]) {
createFlash({
createAlert({
message: s__(
'ContextCommits|Failed to create/remove context commits. Please try again.',
),

View file

@ -1,6 +1,6 @@
import _ from 'lodash';
import Api from '~/api';
import createFlash from '~/flash';
import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
import * as types from './mutation_types';
@ -71,7 +71,7 @@ export const createContextCommits = ({ state }, { commits, forceReload = false }
})
.catch(() => {
if (forceReload) {
createFlash({
createAlert({
message: s__('ContextCommits|Failed to create context commits. Please try again.'),
});
}
@ -113,7 +113,7 @@ export const removeContextCommits = ({ state }, forceReload = false) =>
})
.catch(() => {
if (forceReload) {
createFlash({
createAlert({
message: s__('ContextCommits|Failed to delete context commits. Please try again.'),
});
}

View file

@ -5,7 +5,7 @@ import { __ } from '~/locale';
import Api, { DEFAULT_PER_PAGE } from '~/api';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
import createFlash from '~/flash';
import { createAlert } from '~/flash';
import csrf from '~/lib/utils/csrf';
export default {
@ -151,7 +151,7 @@ export default {
}),
);
} catch (error) {
createFlash({
createAlert({
message: this.$options.i18n.apiErrorMessage,
captureError: true,
error,

View file

@ -1,5 +1,5 @@
import Api from '~/api';
import createFlash from '~/flash';
import { createAlert } from '~/flash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import * as types from './mutation_types';
@ -21,7 +21,7 @@ export const receiveStatisticsSuccess = ({ commit }, statistics) =>
export const receiveStatisticsError = ({ commit }, error) => {
commit(types.RECEIVE_STATISTICS_ERROR, error);
createFlash({
createAlert({
message: s__('AdminDashboard|Error loading the statistics. Please try again'),
});
};

View file

@ -1,6 +1,6 @@
<script>
import { GlSkeletonLoader, GlTable } from '@gitlab/ui';
import createFlash from '~/flash';
import { createAlert } from '~/flash';
import { convertNodeIdsFromGraphQLIds } from '~/graphql_shared/utils';
import { thWidthPercent } from '~/lib/utils/table_utility';
import { s__, __ } from '~/locale';
@ -50,7 +50,7 @@ export default {
}, {});
},
error(error) {
createFlash({
createAlert({
message: this.$options.i18n.groupCountFetchError,
captureError: true,
error,

View file

@ -2,7 +2,7 @@
import { GlButton, GlAlert, GlTabs, GlTab } from '@gitlab/ui';
import createHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql';
import updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
import { createAlert, VARIANT_SUCCESS } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import httpStatusCodes from '~/lib/utils/http_status';
import { typeSet, i18n, tabIndices } from '../constants';
@ -75,7 +75,7 @@ export default {
return nodes;
},
error(err) {
createFlash({ message: err });
createAlert({ message: err });
},
},
currentIntegration: {
@ -125,7 +125,7 @@ export default {
.then(({ data: { httpIntegrationCreate, prometheusIntegrationCreate } = {} } = {}) => {
const error = httpIntegrationCreate?.errors[0] || prometheusIntegrationCreate?.errors[0];
if (error) {
createFlash({ message: error });
createAlert({ message: error });
return;
}
@ -140,7 +140,7 @@ export default {
}
})
.catch(() => {
createFlash({ message: ADD_INTEGRATION_ERROR });
createAlert({ message: ADD_INTEGRATION_ERROR });
})
.finally(() => {
this.isUpdating = false;
@ -161,7 +161,7 @@ export default {
.then(({ data: { httpIntegrationUpdate, prometheusIntegrationUpdate } = {} } = {}) => {
const error = httpIntegrationUpdate?.errors[0] || prometheusIntegrationUpdate?.errors[0];
if (error) {
createFlash({ message: error });
createAlert({ message: error });
return;
}
@ -174,13 +174,13 @@ export default {
this.clearCurrentIntegration({ type });
}
createFlash({
createAlert({
message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS,
variant: VARIANT_SUCCESS,
});
})
.catch(() => {
createFlash({ message: UPDATE_INTEGRATION_ERROR });
createAlert({ message: UPDATE_INTEGRATION_ERROR });
})
.finally(() => {
this.isUpdating = false;
@ -198,7 +198,7 @@ export default {
const [error] =
httpIntegrationResetToken?.errors || prometheusIntegrationResetToken.errors;
if (error) {
return createFlash({ message: error });
return createAlert({ message: error });
}
const integration =
@ -212,14 +212,14 @@ export default {
variables: integration,
});
return createFlash({
return createAlert({
message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS,
variant: VARIANT_SUCCESS,
});
},
)
.catch(() => {
createFlash({ message: RESET_INTEGRATION_TOKEN_ERROR });
createAlert({ message: RESET_INTEGRATION_TOKEN_ERROR });
})
.finally(() => {
this.isUpdating = false;
@ -252,7 +252,7 @@ export default {
);
},
error() {
createFlash({ message: DEFAULT_ERROR });
createAlert({ message: DEFAULT_ERROR });
},
});
} else {
@ -272,7 +272,7 @@ export default {
this.tabIndex = tabIndex;
})
.catch(() => {
createFlash({ message: DEFAULT_ERROR });
createAlert({ message: DEFAULT_ERROR });
});
},
deleteIntegration({ id, type }) {
@ -290,16 +290,16 @@ export default {
.then(({ data: { httpIntegrationDestroy } = {} } = {}) => {
const error = httpIntegrationDestroy?.errors[0];
if (error) {
return createFlash({ message: error });
return createAlert({ message: error });
}
this.clearCurrentIntegration({ type });
return createFlash({
return createAlert({
message: this.$options.i18n.integrationRemoved,
type: FLASH_TYPES.SUCCESS,
variant: VARIANT_SUCCESS,
});
})
.catch(() => {
createFlash({ message: DELETE_INTEGRATION_ERROR });
createAlert({ message: DELETE_INTEGRATION_ERROR });
})
.finally(() => {
this.isUpdating = false;
@ -320,9 +320,9 @@ export default {
return service
.updateTestAlert(payload)
.then(() => {
return createFlash({
return createAlert({
message: this.$options.i18n.alertSent,
type: FLASH_TYPES.SUCCESS,
variant: VARIANT_SUCCESS,
});
})
.catch((error) => {
@ -330,7 +330,7 @@ export default {
if (error.response?.status === httpStatusCodes.FORBIDDEN) {
message = INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR;
}
createFlash({ message });
createAlert({ message });
});
},
saveAndTestAlertPayload(integration, payload) {

View file

@ -1,5 +1,5 @@
import produce from 'immer';
import createFlash from '~/flash';
import { createAlert } from '~/flash';
import { DELETE_INTEGRATION_ERROR, ADD_INTEGRATION_ERROR } from './error_messages';
@ -59,7 +59,7 @@ const addIntegrationToStore = (
};
const onError = (data, message) => {
createFlash({ message });
createAlert({ message });
throw new Error(data.errors);
};

View file

@ -1,7 +1,7 @@
<script>
import { GlSkeletonLoader } from '@gitlab/ui';
import { flatten, isEqual, keyBy } from 'lodash';
import createFlash from '~/flash';
import { createAlert } from '~/flash';
import { sprintf, s__ } from '~/locale';
import { METRICS_POPOVER_CONTENT } from '../constants';
import { removeFlash, prepareTimeMetricsData } from '../utils';
@ -17,7 +17,7 @@ const requestData = ({ request, endpoint, path, params, name }) => {
),
{ requestTypeName: name },
);
createFlash({ message });
createAlert({ message });
});
};

View file

@ -1,7 +1,7 @@
<script>
import { GlSkeletonLoader } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import createFlash from '~/flash';
import { createAlert } from '~/flash';
import { number } from '~/lib/utils/unit_format';
import { __, s__ } from '~/locale';
import usageTrendsCountQuery from '../graphql/queries/usage_trends_count.query.graphql';
@ -35,7 +35,7 @@ export default {
});
},
error(error) {
createFlash({
createAlert({
message: this.$options.i18n.loadCountsError,
captureError: true,
error,

View file

@ -52,11 +52,6 @@ export default {
:compact="true"
@toggledPaused="onToggledPaused"
/>
<runner-delete-button
:disabled="!canDelete"
:runner="runner"
:compact="true"
@deleted="onDeleted"
/>
<runner-delete-button v-if="canDelete" :runner="runner" :compact="true" @deleted="onDeleted" />
</gl-button-group>
</template>

View file

@ -5,12 +5,7 @@ import { createAlert } from '~/flash';
import { sprintf } from '~/locale';
import { captureException } from '~/runner/sentry_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import {
I18N_DELETE_DISABLED_MANY_PROJECTS,
I18N_DELETE_DISABLED_UNKNOWN_REASON,
I18N_DELETE_RUNNER,
I18N_DELETED_TOAST,
} from '../constants';
import { I18N_DELETE_RUNNER, I18N_DELETED_TOAST } from '../constants';
import RunnerDeleteModal from './runner_delete_modal.vue';
export default {
@ -31,11 +26,6 @@ export default {
return runner?.id && runner?.shortSha;
},
},
disabled: {
type: Boolean,
required: false,
default: false,
},
compact: {
type: Boolean,
required: false,
@ -85,29 +75,14 @@ export default {
return null;
},
tooltip() {
if (this.disabled && this.runner.projectCount > 1) {
return I18N_DELETE_DISABLED_MANY_PROJECTS;
}
if (this.disabled) {
return I18N_DELETE_DISABLED_UNKNOWN_REASON;
}
// Only show basic "delete" tooltip when compact.
// Also prevent a "sticky" tooltip: If this button is
// disabled, mouseout listeners don't run leaving the tooltip stuck
// loading, mouseout listeners don't run leaving the tooltip stuck
if (this.compact && !this.deleting) {
return I18N_DELETE_RUNNER;
}
return '';
},
wrapperTabindex() {
if (this.disabled) {
// Trigger tooltip on keyboard-focusable wrapper
// See https://bootstrap-vue.org/docs/directives/tooltip
return '0';
}
return null;
},
},
methods: {
async onDelete() {
@ -156,14 +131,13 @@ export default {
</script>
<template>
<div v-gl-tooltip="tooltip" class="btn-group" :tabindex="wrapperTabindex">
<div v-gl-tooltip="tooltip" class="btn-group">
<gl-button
v-gl-modal="runnerDeleteModalId"
:aria-label="ariaLabel"
:icon="icon"
:class="buttonClass"
:loading="deleting"
:disabled="disabled"
variant="danger"
category="secondary"
v-bind="$attrs"

View file

@ -76,12 +76,6 @@ export const I18N_RESUME = __('Resume');
export const I18N_RESUME_TOOLTIP = s__('Runners|Resume accepting jobs');
export const I18N_DELETE_RUNNER = s__('Runners|Delete runner');
export const I18N_DELETE_DISABLED_MANY_PROJECTS = s__(
'Runners|Multi-project runners cannot be deleted',
);
export const I18N_DELETE_DISABLED_UNKNOWN_REASON = s__(
'Runners|Runner cannot be deleted, please contact your administrator',
);
export const I18N_DELETED_TOAST = s__('Runners|Runner %{name} was deleted');
// List

View file

@ -8,13 +8,15 @@
font-size: 95%;
}
.gfm-project_member {
.gfm-project_member,
.md a.gfm-project_member {
padding: 0 2px;
background-color: $blue-100;
border-radius: $border-radius-default;
color: $blue-700;
&.current-user {
background-color: $orange-50;
background-color: $orange-100;
}
}

View file

@ -19,4 +19,4 @@
.form-group
= f.gitlab_ui_checkbox_component :floc_enabled,
s_('FloC|Participate in FLoC')
= f.submit _('Save changes'), class: 'gl-button btn btn-confirm'
= f.submit _('Save changes'), pajamas_button: true

View file

@ -18,4 +18,4 @@
.form-group
= f.label :jira_connect_application_key, s_('JiraConnect|Jira Connect Application ID'), class: 'label-bold'
= f.text_field :jira_connect_application_key, class: 'form-control gl-form-input'
= f.submit _('Save changes'), class: 'gl-button btn btn-confirm'
= f.submit _('Save changes'), pajamas_button: true

View file

@ -9,4 +9,4 @@
= render_if_exists 'admin/application_settings/mirror_settings', form: f
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
= f.submit _('Save changes'), pajamas_button: true

View file

@ -29,4 +29,4 @@
= f.text_field :sourcegraph_url, class: 'form-control gl-form-input', placeholder: s_('SourcegraphAdmin|https://sourcegraph.example.com')
.form-text.text-muted
= s_('SourcegraphAdmin|Configure the URL to a Sourcegraph instance which can read your GitLab projects.')
= f.submit s_('SourcegraphAdmin|Save changes'), class: 'gl-button btn btn-confirm'
= f.submit s_('SourcegraphAdmin|Save changes'), pajamas_button: true

View file

@ -50,4 +50,4 @@
%li
= s_('AdminSettings|Restrict group access by IP address. %{link_start}Learn more%{link_end}.').html_safe % { link_start: restrict_ip_link, link_end: link_end }
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
= f.submit _('Save changes'), pajamas_button: true

View file

@ -2,8 +2,7 @@
.nav-sidebar-inner-scroll
.context-header
= link_to profile_path, title: _('Profile Settings'), class: 'has-tooltip', data: { container: 'body', placement: 'right' } do
%span{ class: ['avatar-container', 'settings-avatar', 's32'] }
= image_tag avatar_icon_for_user(current_user, 32), class: ['avatar', 'avatar-tile', 'js-sidebar-user-avatar', 's32', 'gl-rounded-full!'], alt: current_user.name, data: { testid: 'sidebar-user-avatar' }
= render Pajamas::AvatarComponent.new(current_user, size: 32, alt: current_user.name, class: 'gl-mr-3 js-sidebar-user-avatar', avatar_options: { data: { testid: 'sidebar-user-avatar' } })
%span.sidebar-context-title= _('User Settings')
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do

View file

@ -24,7 +24,7 @@
= s_("PipelineSchedules|Inactive")
%td
- if pipeline_schedule.owner
= image_tag avatar_icon_for_user(pipeline_schedule.owner, 20), class: "avatar s20"
= render Pajamas::AvatarComponent.new(pipeline_schedule.owner, size: 24, class: "gl-mr-2")
= link_to user_path(pipeline_schedule.owner) do
= pipeline_schedule.owner&.name
%td

View file

@ -1,8 +1,8 @@
%ul.bordered-list
- users.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon_for_user(user, 32), class: "avatar s32"
%strong= truncate(user.name, length: 40)
%div
= link_to user, title: user.name, class: "gl-display-flex" do
= render Pajamas::AvatarComponent.new(user, size: 32, class: "gl-mr-3")
.gl-display-flex.gl-flex-direction-column
%strong= truncate(user.name, length: 40)
%small.cgray= user.username

View file

@ -17,7 +17,7 @@
- else
.timeline-avatar.gl-float-left
%a.image-diff-avatar-link{ href: user_path(note.author) }
= image_tag avatar_icon_for_user(note.author), alt: '', class: 'avatar s32'
= render Pajamas::AvatarComponent.new(note.author, size: 32, alt: '')
- if note.is_a?(DiffNote) && note.on_image?
- if show_image_comment_badge && note_counter == 0
-# Only show this for the first comment in the discussion

View file

@ -21,10 +21,10 @@
%li.project-row.d-flex{ class: css_class }
= cache(cache_key) do
- if avatar
.avatar-container.s48.flex-grow-0.flex-shrink-0{ class: avatar_container_class }
.flex-grow-0.flex-shrink-0{ class: avatar_container_class }
= link_to project_path(project), class: dom_class(project) do
- if project.creator && use_creator_avatar
= image_tag avatar_icon_for_user(project.creator, 48), class: "avatar s48", alt: ''
= render Pajamas::AvatarComponent.new(project.creator, size: 48, alt: '', class: 'gl-mr-5')
- else
= project_icon(project, alt: '', class: 'avatar project-avatar s48', width: 48, height: 48)
.project-details.d-sm-flex.flex-sm-fill.align-items-center{ data: { qa_selector: 'project_content', qa_project_name: project.name } }

View file

@ -1,8 +0,0 @@
---
name: highlight_diffs_renewable_expiration
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95356
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/370728
milestone: '15.3'
type: development
group: group::source code
default_enabled: false

View file

@ -1,8 +0,0 @@
---
name: highlight_diffs_short_renewable_expiration
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95356
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/370728
milestone: '15.3'
type: development
group: group::source code
default_enabled: false

View file

@ -156,6 +156,20 @@ deploy:
# ... rest of your job configuration
```
### Using the agent with Auto DevOps
If Auto DevOps is enabled, you must define the `KUBE_CONTEXT` CI/CD variable. Set the value of `KUBE_CONTEXT` to the context of the agent you want to use in your Auto DevOps pipeline jobs (`<PATH_TO_AGENT_CONFIG_REPOSITORY>:<AGENT_NAME>`).
You can also use different agents for different Auto DevOps jobs. For instance, you can use one agent for `staging` jobs and a different agent for `production` jobs. To use multiple agents, define a unique CI/CD variable for each agent.
For example:
1. Add two [environment-scoped CI/CD variables](../../../ci/variables/index.md#limit-the-environment-scope-of-a-cicd-variable) and name both `KUBE_CONTEXT`.
1. Set the `environment` of the first variable to `staging`. Set the value of the variable to `<PATH_TO_AGENT_CONFIGURATION_PROJECT>:<STAGING_AGENT_NAME>`.
1. Set the `environment` of the second variable to `production`. Set the value of the variable to `<PATH_TO_AGENT_CONFIGURATION_PROJECT>:<PRODUCTION_AGENT_NAME>`.
When the `staging` job runs, it will connect to the cluster via the agent named `<STAGING_AGENT_NAME>`, and when the `production` job runs it will connect to the cluster via the agent named `<PRODUCTION_AGENT_NAME>`.
## Restrict project and group access by using impersonation **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345014) in GitLab 14.5.
@ -260,6 +274,7 @@ See the [official Kubernetes documentation for details](https://kubernetes.io/do
## Related topics
- [Self-paced classroom workshop](https://gitlab-for-eks.awsworkshop.io) (Uses AWS EKS, but you can use for other Kubernetes clusters)
- [Configure Auto DevOps](../../../topics/autodevops/cloud_deployments/auto_devops_with_gke.md#configure-auto-devops)
## Troubleshooting

View file

@ -110,6 +110,9 @@ in your cluster. You can either:
If you do not know which one to choose, we recommend starting with Helm.
NOTE:
To connect to multiple clusters, you must configure, register, and install an agent in each cluster. Make sure to give each agent a unique name.
#### Install the agent with Helm
To install the agent on your cluster using Helm:

View file

@ -4,9 +4,12 @@ module API
module Entities
module BulkImports
class EntityFailure < Grape::Entity
expose :exception_message do |failure|
::Projects::ImportErrorFilter.filter_message(failure.exception_message.truncate(72))
end
expose :exception_class
expose :pipeline_class
expose :pipeline_step
expose :exception_class
expose :correlation_id_value
expose :created_at
end

View file

@ -82,16 +82,6 @@ module Gitlab
private
def expiration
return 1.day unless Feature.enabled?(:highlight_diffs_renewable_expiration, diffable.project)
if Feature.enabled?(:highlight_diffs_short_renewable_expiration, diffable.project)
EXPIRATION
else
8.hours
end
end
def set_highlighted_diff_lines(diff_file, content)
diff_file.highlighted_diff_lines = content.map do |line|
Gitlab::Diff::Line.safe_init_from_hash(line)
@ -147,7 +137,7 @@ module Gitlab
end
# HSETs have to have their expiration date manually updated
pipeline.expire(key, expiration)
pipeline.expire(key, EXPIRATION)
end
record_memory_usage(fetch_memory_usage(redis, key))
@ -197,14 +187,12 @@ module Gitlab
return {} unless file_paths.any?
results = []
cache_key = key
highlight_diffs_renewable_expiration_enabled = Feature.enabled?(:highlight_diffs_renewable_expiration, diffable.project)
expiration_period = expiration
cache_key = key # Moving out redis calls for feature flags out of redis.pipelined
Gitlab::Redis::Cache.with do |redis|
redis.pipelined do |pipeline|
results = pipeline.hmget(cache_key, file_paths)
pipeline.expire(key, expiration_period) if highlight_diffs_renewable_expiration_enabled
pipeline.expire(key, EXPIRATION)
end
end

View file

@ -34532,9 +34532,6 @@ msgstr ""
msgid "Runners|Members of the %{type} can register runners"
msgstr ""
msgid "Runners|Multi-project runners cannot be deleted"
msgstr ""
msgid "Runners|Name"
msgstr ""
@ -34648,9 +34645,6 @@ msgstr ""
msgid "Runners|Runner authentication tokens will expire based on a set interval. They will automatically rotate once expired."
msgstr ""
msgid "Runners|Runner cannot be deleted, please contact your administrator"
msgstr ""
msgid "Runners|Runner has contacted GitLab within the last %{elapsedTime}"
msgstr ""

View file

@ -123,7 +123,7 @@ RSpec.describe "Group Runners" do
visit group_runners_path(group)
within_runner_row(runner.id) do
expect(page).to have_button 'Delete runner', disabled: true
expect(page).not_to have_button 'Delete runner'
end
end
end

View file

@ -9,7 +9,7 @@ import { stubComponent } from 'helpers/stub_component';
import DeployKeysTable from '~/admin/deploy_keys/components/table.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import Api, { DEFAULT_PER_PAGE } from '~/api';
import createFlash from '~/flash';
import { createAlert } from '~/flash';
jest.mock('~/api');
jest.mock('~/flash');
@ -243,7 +243,7 @@ describe('DeployKeysTable', () => {
itRendersTheEmptyState();
it('displays flash', () => {
expect(createFlash).toHaveBeenCalledWith({
expect(createAlert).toHaveBeenCalledWith({
message: DeployKeysTable.i18n.apiErrorMessage,
captureError: true,
error,

View file

@ -10,7 +10,7 @@ import AdminUserActions from '~/admin/users/components/user_actions.vue';
import AdminUserAvatar from '~/admin/users/components/user_avatar.vue';
import AdminUsersTable from '~/admin/users/components/users_table.vue';
import getUsersGroupCountsQuery from '~/admin/users/graphql/queries/get_users_group_counts.query.graphql';
import createFlash from '~/flash';
import { createAlert } from '~/flash';
import AdminUserDate from '~/vue_shared/components/user_date.vue';
import { users, paths, createGroupCountResponse } from '../mock_data';
@ -135,7 +135,7 @@ describe('AdminUsersTable component', () => {
});
it('creates a flash message and captures the error', () => {
expect(createFlash).toHaveBeenCalledWith({
expect(createAlert).toHaveBeenCalledWith({
message: 'Could not load user group counts. Please refresh the page to try again.',
captureError: true,
error: expect.any(Error),

View file

@ -30,7 +30,7 @@ import {
INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
DELETE_INTEGRATION_ERROR,
} from '~/alerts_settings/utils/error_messages';
import createFlash, { FLASH_TYPES } from '~/flash';
import { createAlert, VARIANT_SUCCESS } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
import {
@ -327,7 +327,7 @@ describe('AlertsSettingsWrapper', () => {
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: ADD_INTEGRATION_ERROR });
expect(createAlert).toHaveBeenCalledWith({ message: ADD_INTEGRATION_ERROR });
});
it('shows an error alert when integration token reset fails', async () => {
@ -336,7 +336,7 @@ describe('AlertsSettingsWrapper', () => {
findAlertsSettingsForm().vm.$emit('reset-token', {});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: RESET_INTEGRATION_TOKEN_ERROR });
expect(createAlert).toHaveBeenCalledWith({ message: RESET_INTEGRATION_TOKEN_ERROR });
});
it('shows an error alert when integration update fails', async () => {
@ -345,7 +345,7 @@ describe('AlertsSettingsWrapper', () => {
findAlertsSettingsForm().vm.$emit('update-integration', {});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: UPDATE_INTEGRATION_ERROR });
expect(createAlert).toHaveBeenCalledWith({ message: UPDATE_INTEGRATION_ERROR });
});
describe('Test alert failure', () => {
@ -360,17 +360,17 @@ describe('AlertsSettingsWrapper', () => {
it('shows an error alert when integration test payload is invalid', async () => {
mock.onPost(/(.*)/).replyOnce(httpStatusCodes.UNPROCESSABLE_ENTITY);
await wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' });
expect(createFlash).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createAlert).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
expect(createAlert).toHaveBeenCalledTimes(1);
});
it('shows an error alert when integration is not activated', async () => {
mock.onPost(/(.*)/).replyOnce(httpStatusCodes.FORBIDDEN);
await wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' });
expect(createFlash).toHaveBeenCalledWith({
expect(createAlert).toHaveBeenCalledWith({
message: INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
});
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createAlert).toHaveBeenCalledTimes(1);
});
});
@ -444,9 +444,9 @@ describe('AlertsSettingsWrapper', () => {
jest.spyOn(alertsUpdateService, 'updateTestAlert').mockResolvedValueOnce({});
findAlertsSettingsForm().vm.$emit('test-alert-payload', '');
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
expect(createAlert).toHaveBeenCalledWith({
message: i18n.alertSent,
type: FLASH_TYPES.SUCCESS,
variant: VARIANT_SUCCESS,
});
});
@ -454,7 +454,7 @@ describe('AlertsSettingsWrapper', () => {
jest.spyOn(alertsUpdateService, 'updateTestAlert').mockRejectedValueOnce({});
findAlertsSettingsForm().vm.$emit('test-alert-payload', '');
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
expect(createAlert).toHaveBeenCalledWith({
message: INTEGRATION_PAYLOAD_TEST_ERROR,
});
});
@ -486,7 +486,7 @@ describe('AlertsSettingsWrapper', () => {
await destroyHttpIntegration(wrapper);
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: 'Houston, we have a problem' });
expect(createAlert).toHaveBeenCalledWith({ message: 'Houston, we have a problem' });
});
it('displays flash if mutation had a non-recoverable error', async () => {
@ -497,7 +497,7 @@ describe('AlertsSettingsWrapper', () => {
await destroyHttpIntegration(wrapper);
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
expect(createAlert).toHaveBeenCalledWith({
message: DELETE_INTEGRATION_ERROR,
});
});

View file

@ -8,7 +8,7 @@ import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
import { VSA_METRICS_GROUPS, METRICS_POPOVER_CONTENT } from '~/analytics/shared/constants';
import { prepareTimeMetricsData } from '~/analytics/shared/utils';
import MetricTile from '~/analytics/shared/components/metric_tile.vue';
import createFlash from '~/flash';
import { createAlert } from '~/flash';
import { group } from './mock_data';
jest.mock('~/flash');
@ -177,7 +177,7 @@ describe('ValueStreamMetrics', () => {
});
it('should render an error message', () => {
expect(createFlash).toHaveBeenCalledWith({
expect(createAlert).toHaveBeenCalledWith({
message: `There was an error while fetching value stream analytics ${fakeReqName} data.`,
});
});

View file

@ -122,7 +122,7 @@ describe('RunnerActionsCell', () => {
expect(wrapper.emitted('deleted')).toEqual([[value]]);
});
it('Renders the runner delete disabled button when user cannot delete', () => {
it('Does not render the runner delete button when user cannot delete', () => {
createComponent({
runner: {
userPermissions: {
@ -132,7 +132,7 @@ describe('RunnerActionsCell', () => {
},
});
expect(findDeleteBtn().props('disabled')).toBe(true);
expect(findDeleteBtn().exists()).toBe(false);
});
});
});

View file

@ -9,11 +9,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { captureException } from '~/runner/sentry_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { createAlert } from '~/flash';
import {
I18N_DELETE_RUNNER,
I18N_DELETE_DISABLED_MANY_PROJECTS,
I18N_DELETE_DISABLED_UNKNOWN_REASON,
} from '~/runner/constants';
import { I18N_DELETE_RUNNER } from '~/runner/constants';
import RunnerDeleteButton from '~/runner/components/runner_delete_button.vue';
import RunnerDeleteModal from '~/runner/components/runner_delete_modal.vue';
@ -267,29 +263,4 @@ describe('RunnerDeleteButton', () => {
});
});
});
describe.each`
reason | runner | tooltip
${'runner belongs to more than 1 project'} | ${{ projectCount: 2 }} | ${I18N_DELETE_DISABLED_MANY_PROJECTS}
${'unknown reason'} | ${{}} | ${I18N_DELETE_DISABLED_UNKNOWN_REASON}
`('When button is disabled because $reason', ({ runner, tooltip }) => {
beforeEach(() => {
createComponent({
props: {
disabled: true,
runner,
},
});
});
it('Displays a disabled delete button', () => {
expect(findBtn().props('disabled')).toBe(true);
});
it(`Tooltip "${tooltip}" is shown`, () => {
// tabindex is required for a11y
expect(wrapper.attributes('tabindex')).toBe('0');
expect(getTooltip()).toBe(tooltip);
});
});
});

View file

@ -12,8 +12,23 @@ RSpec.describe API::Entities::BulkImports::EntityFailure do
:pipeline_class,
:pipeline_step,
:exception_class,
:exception_message,
:correlation_id_value,
:created_at
)
end
describe 'exception message' do
it 'truncates exception message to 72 characters' do
failure.update!(exception_message: 'a' * 100)
expect(subject[:exception_message].length).to eq(72)
end
it 'removes paths from the message' do
failure.update!(exception_message: 'Test /foo/bar')
expect(subject[:exception_message]).to eq('Test [FILTERED]')
end
end
end

View file

@ -109,58 +109,36 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
end
shared_examples 'caches missing entries' do
where(:expiration_period, :renewable_expiration_ff, :short_renewable_expiration_ff) do
[
[1.day, false, true],
[1.day, false, false],
[1.hour, true, true],
[8.hours, true, false]
]
it 'filters the key/value list of entries to be caches for each invocation' do
expect(cache).to receive(:write_to_redis_hash)
.with(hash_including(*paths))
.once
.and_call_original
2.times { cache.write_if_empty }
end
with_them do
before do
stub_feature_flags(
highlight_diffs_renewable_expiration: renewable_expiration_ff,
highlight_diffs_short_renewable_expiration: short_renewable_expiration_ff
)
end
it 'reads from cache once' do
expect(cache).to receive(:read_cache).once.and_call_original
it 'filters the key/value list of entries to be caches for each invocation' do
expect(cache).to receive(:write_to_redis_hash)
.with(hash_including(*paths))
.once
.and_call_original
cache.write_if_empty
end
2.times { cache.write_if_empty }
end
it 'refreshes TTL of the key on read' do
cache.write_if_empty
it 'reads from cache once' do
expect(cache).to receive(:read_cache).once.and_call_original
time_until_expire = 30.minutes
cache.write_if_empty
end
Gitlab::Redis::Cache.with do |redis|
# Emulate that a key is going to expire soon
redis.expire(cache.key, time_until_expire)
it 'refreshes TTL of the key on read' do
cache.write_if_empty
expect(redis.ttl(cache.key)).to be <= time_until_expire
time_until_expire = 30.minutes
cache.send(:read_cache)
Gitlab::Redis::Cache.with do |redis|
# Emulate that a key is going to expire soon
redis.expire(cache.key, time_until_expire)
expect(redis.ttl(cache.key)).to be <= time_until_expire
cache.send(:read_cache)
if renewable_expiration_ff
expect(redis.ttl(cache.key)).to be > time_until_expire
expect(redis.ttl(cache.key)).to be_within(1.minute).of(expiration_period)
else
expect(redis.ttl(cache.key)).to be <= time_until_expire
end
end
expect(redis.ttl(cache.key)).to be > time_until_expire
expect(redis.ttl(cache.key)).to be_within(1.minute).of(described_class::EXPIRATION)
end
end
end

View file

@ -13,7 +13,7 @@ RSpec.describe 'shared/projects/_project.html.haml' do
it 'renders creator avatar if project has a creator' do
render 'shared/projects/project', use_creator_avatar: true, project: project
expect(rendered).to have_selector('img.avatar')
expect(rendered).to have_selector('img.gl-avatar')
end
it 'renders a generic avatar if project does not have a creator' do