diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-01 15:13:32 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-01 15:13:32 +0300 |
commit | 6c7d90ede4d2890b2ca740239cd9a5338f12ba34 (patch) | |
tree | 59a0fde813364a626fe201dc58316dee001f96e1 | |
parent | 3cb9e9a5070184e59cb6ff33a4ce381ed14a6fe5 (diff) |
Add latest changes from gitlab-org/gitlab@master
50 files changed, 629 insertions, 155 deletions
diff --git a/.gitlab/ci/static-analysis.gitlab-ci.yml b/.gitlab/ci/static-analysis.gitlab-ci.yml index 1394085b6e4..85df68e9030 100644 --- a/.gitlab/ci/static-analysis.gitlab-ci.yml +++ b/.gitlab/ci/static-analysis.gitlab-ci.yml @@ -35,6 +35,17 @@ static-analysis: paths: - tmp/feature_flags/ +static-analysis-with-database: + extends: + - .static-analysis-base + - .static-analysis:rules:ee-and-foss + - .use-pg12 + stage: test + script: + - bundle exec rake lint:static_verification_with_database + variables: + SETUP_DB: "true" + static-analysis as-if-foss: extends: - static-analysis diff --git a/app/assets/javascripts/admin/users/components/modals/delete_user_modal.vue b/app/assets/javascripts/admin/users/components/modals/delete_user_modal.vue index 413163c8536..aaf2ff313a0 100644 --- a/app/assets/javascripts/admin/users/components/modals/delete_user_modal.vue +++ b/app/assets/javascripts/admin/users/components/modals/delete_user_modal.vue @@ -117,7 +117,7 @@ export default { <p> <gl-sprintf :message="s__('AdminUsers|To confirm, type %{username}')"> <template #username> - <code>{{ username }}</code> + <code class="gl-white-space-pre-wrap">{{ username }}</code> </template> </gl-sprintf> </p> diff --git a/app/assets/javascripts/content_editor/content_editor.stories.js b/app/assets/javascripts/content_editor/content_editor.stories.js index 8f2ce8feb5d..9329bbcb2c7 100644 --- a/app/assets/javascripts/content_editor/content_editor.stories.js +++ b/app/assets/javascripts/content_editor/content_editor.stories.js @@ -2,7 +2,7 @@ import { ContentEditor } from './index'; export default { component: ContentEditor, - title: 'Components/Content Editor', + title: 'content_editor/components/content_editor', }; const Template = (_, { argTypes }) => ({ diff --git a/app/assets/javascripts/content_editor/extensions/table_of_contents.js b/app/assets/javascripts/content_editor/extensions/table_of_contents.js index 9c0316ddc08..9e31158837e 100644 --- a/app/assets/javascripts/content_editor/extensions/table_of_contents.js +++ b/app/assets/javascripts/content_editor/extensions/table_of_contents.js @@ -1,4 +1,5 @@ -import { Node, nodeInputRule } from '@tiptap/core'; +import { Node } from '@tiptap/core'; +import { InputRule } from 'prosemirror-inputrules'; import { s__ } from '~/locale'; import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; @@ -32,6 +33,19 @@ export default Node.create({ }, addInputRules() { - return inputRuleRegExps.map((regex) => nodeInputRule(regex, this.type)); + const { type } = this; + + return inputRuleRegExps.map( + (regex) => + new InputRule(regex, (state, match, start, end) => { + const { tr } = state; + + if (match) { + tr.replaceWith(start - 1, end, type.create()); + } + + return tr; + }), + ); }, }); diff --git a/app/assets/javascripts/diffs/components/diff_row.vue b/app/assets/javascripts/diffs/components/diff_row.vue index 737c4d8f33c..4e33a02ca0e 100644 --- a/app/assets/javascripts/diffs/components/diff_row.vue +++ b/app/assets/javascripts/diffs/components/diff_row.vue @@ -1,4 +1,9 @@ <script> +/* eslint-disable vue/no-v-html */ +/** +NOTE: This file uses v-html over v-safe-html for performance reasons, see: +https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57842 +* */ import { memoize } from 'lodash'; import { isLoggedIn } from '~/lib/utils/common_utils'; import { @@ -267,7 +272,9 @@ export default { ]" class="diff-td line_content with-coverage left-side" data-testid="left-content" - v-html="$options.lineContent(props.line.left) /* eslint-disable-line vue/no-v-html */" + v-html=" + $options.lineContent(props.line.left) /* v-html for performance, see top of file */ + " ></div> </template> <template @@ -389,7 +396,9 @@ export default { }, ]" class="diff-td line_content with-coverage right-side parallel" - v-html="$options.lineContent(props.line.right) /* eslint-disable-line vue/no-v-html */" + v-html=" + $options.lineContent(props.line.right) /* v-html for performance, see top of file */ + " ></div> </template> <template v-else> diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue index bf286c84d5f..7be3bba7cae 100644 --- a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue +++ b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue @@ -95,7 +95,7 @@ export default { <gl-sprintf :message=" __( - 'Save space and find images in the container Registry. remove unneeded tags and keep only the ones you want. %{linkStart}How does cleanup work?%{linkEnd}', + 'Save storage space by automatically deleting tags from the container registry and keeping the ones you want. %{linkStart}How does cleanup work?%{linkEnd}', ) " > diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index f204f0ebfaa..7fd63acbede 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -1,6 +1,7 @@ <script> import { GlSafeHtmlDirective } from '@gitlab/ui'; import { glEmojiTag } from '~/emoji'; +import { mergeUrlParams } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import AddRequest from './add_request.vue'; @@ -123,6 +124,9 @@ export default { const fileName = this.requests[0].truncatedUrl; return `${fileName}_perf_bar_${Date.now()}.json`; }, + flamegraphPath() { + return mergeUrlParams({ performance_bar: 'flamegraph' }, window.location.href); + }, }, mounted() { this.currentRequest = this.requestId; @@ -175,6 +179,11 @@ export default { s__('PerformanceBar|Download') }}</a> </div> + <div v-if="currentRequest.details" id="peek-flamegraph" class="view"> + <a class="gl-text-blue-200" :href="flamegraphPath">{{ + s__('PerformanceBar|Flamegraph') + }}</a> + </div> <a v-if="statsUrl" class="gl-text-blue-200 view" :href="statsUrl">{{ s__('PerformanceBar|Stats') }}</a> diff --git a/app/assets/javascripts/related_issues/components/issue_token.vue b/app/assets/javascripts/related_issues/components/issue_token.vue index 9665ed173b9..840a6750cd2 100644 --- a/app/assets/javascripts/related_issues/components/issue_token.vue +++ b/app/assets/javascripts/related_issues/components/issue_token.vue @@ -48,7 +48,7 @@ export default { <template> <div :class="{ - 'issue-token': isCondensed, + 'issue-token gl-display-inline-flex gl-align-items-stretch gl-max-w-full gl-line-height-24 gl-white-space-nowrap': isCondensed, 'flex-row issuable-info-container': !isCondensed, }" > @@ -57,7 +57,7 @@ export default { ref="link" v-gl-tooltip :class="{ - 'issue-token-link': isCondensed, + 'issue-token-link gl-display-inline-flex gl-min-w-0 gl-text-gray-500': isCondensed, 'issuable-main-info': !isCondensed, }" :href="computedPath" @@ -69,19 +69,19 @@ export default { v-if="hasTitle" ref="title" :class="{ - 'issue-token-title issue-token-end': isCondensed, + 'issue-token-title issue-token-end gl-overflow-hidden gl-display-flex gl-align-items-baseline gl-text-gray-500 gl-pl-3': isCondensed, 'issue-title block-truncated': !isCondensed, - 'issue-token-title-standalone': !canRemove, + 'gl-rounded-top-right-small gl-rounded-bottom-right-small gl-pr-3': !canRemove, }" class="js-issue-token-title" > - <span class="issue-token-title-text">{{ title }}</span> + <span class="gl-text-truncate">{{ title }}</span> </component> <component :is="innerComponentType" ref="reference" :class="{ - 'issue-token-reference': isCondensed, + 'issue-token-reference gl-display-flex gl-align-items-center gl-rounded-top-left-small gl-rounded-bottom-left-small gl-px-3': isCondensed, 'issuable-info': !isCondensed, }" > @@ -103,7 +103,7 @@ export default { ref="removeButton" v-gl-tooltip :class="{ - 'issue-token-remove-button': isCondensed, + 'issue-token-remove-button gl-display-flex gl-align-items-center gl-px-3 gl-border-0 gl-rounded-top-right-small gl-rounded-bottom-right-small gl-text-gray-500': isCondensed, 'btn btn-default': !isCondensed, }" :title="removeButtonLabel" diff --git a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue index fedd2519958..35476cc411e 100644 --- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue +++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue @@ -1,4 +1,5 @@ <script> +import { GlLink } from '@gitlab/ui'; import createFlash from '~/flash'; import { fetchPolicies } from '~/lib/graphql'; import { updateHistory } from '~/lib/utils/url_utility'; @@ -6,6 +7,7 @@ import { formatNumber, sprintf, __ } from '~/locale'; import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; import RunnerList from '../components/runner_list.vue'; import RunnerManualSetupHelp from '../components/runner_manual_setup_help.vue'; +import RunnerName from '../components/runner_name.vue'; import RunnerPagination from '../components/runner_pagination.vue'; import RunnerTypeHelp from '../components/runner_type_help.vue'; import { statusTokenConfig } from '../components/search_tokens/status_token_config'; @@ -23,10 +25,12 @@ import { captureException } from '../sentry_utils'; export default { name: 'AdminRunnersApp', components: { + GlLink, RunnerFilteredSearchBar, RunnerList, RunnerManualSetupHelp, RunnerTypeHelp, + RunnerName, RunnerPagination, }, props: { @@ -150,7 +154,13 @@ export default { {{ __('No runners found') }} </div> <template v-else> - <runner-list :runners="runners.items" :loading="runnersLoading" /> + <runner-list :runners="runners.items" :loading="runnersLoading"> + <template #runner-name="{ runner }"> + <gl-link :href="runner.adminUrl"> + <runner-name :runner="runner" /> + </gl-link> + </template> + </runner-list> <runner-pagination v-model="search.pagination" :page-info="runners.pageInfo" /> </template> </div> diff --git a/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue b/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue index 863f0ab995f..e26bdbf1aea 100644 --- a/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue +++ b/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue @@ -1,7 +1,6 @@ <script> import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui'; import createFlash from '~/flash'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { __, s__ } from '~/locale'; import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql'; import runnerUpdateMutation from '~/runner/graphql/runner_update.mutation.graphql'; @@ -37,13 +36,6 @@ export default { }; }, computed: { - runnerNumericalId() { - return getIdFromGraphQLId(this.runner.id); - }, - runnerUrl() { - // TODO implement using webUrl from the API - return `${gon.gitlab_url || ''}/admin/runners/${this.runnerNumericalId}`; - }, isActive() { return this.runner.active; }, @@ -119,7 +111,7 @@ export default { }, }, awaitRefetchQueries: true, - refetchQueries: ['getRunners'], + refetchQueries: ['getRunners', 'getGroupRunners'], }); if (errors && errors.length) { throw new Error(errors.join(' ')); @@ -147,12 +139,20 @@ export default { <template> <gl-button-group> + <!-- + This button appears for administratos: those with + access to the adminUrl. More advanced permissions policies + will allow more granular permissions. + + See https://gitlab.com/gitlab-org/gitlab/-/issues/334802 + --> <gl-button + v-if="runner.adminUrl" v-gl-tooltip.hover.viewport + :href="runner.adminUrl" :title="$options.i18n.I18N_EDIT" :aria-label="$options.i18n.I18N_EDIT" icon="pencil" - :href="runnerUrl" data-testid="edit-runner" /> <gl-button diff --git a/app/assets/javascripts/runner/components/cells/runner_name_cell.vue b/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue index 797a3359147..886b5cb29fc 100644 --- a/app/assets/javascripts/runner/components/cells/runner_name_cell.vue +++ b/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue @@ -1,12 +1,11 @@ <script> -import { GlLink } from '@gitlab/ui'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; +import RunnerName from '../runner_name.vue'; export default { components: { - GlLink, TooltipOnTruncate, + RunnerName, }, props: { runner: { @@ -15,26 +14,18 @@ export default { }, }, computed: { - runnerNumericalId() { - return getIdFromGraphQLId(this.runner.id); - }, - runnerUrl() { - // TODO implement using webUrl from the API - return `${gon.gitlab_url || ''}/admin/runners/${this.runnerNumericalId}`; - }, description() { return this.runner.description; }, - shortSha() { - return this.runner.shortSha; - }, }, }; </script> <template> <div> - <gl-link :href="runnerUrl"> #{{ runnerNumericalId }} ({{ shortSha }})</gl-link> + <slot :runner="runner" name="runner-name"> + <runner-name :runner="runner" /> + </slot> <tooltip-on-truncate class="gl-display-block" :title="description" truncate-target="child"> <div class="gl-text-truncate"> {{ description }} diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue index 69a1f106ca8..3f6ea389288 100644 --- a/app/assets/javascripts/runner/components/runner_list.vue +++ b/app/assets/javascripts/runner/components/runner_list.vue @@ -5,7 +5,7 @@ import { formatNumber, __, s__ } from '~/locale'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import { RUNNER_JOB_COUNT_LIMIT } from '../constants'; import RunnerActionsCell from './cells/runner_actions_cell.vue'; -import RunnerNameCell from './cells/runner_name_cell.vue'; +import RunnerSummaryCell from './cells/runner_summary_cell.vue'; import RunnerTypeCell from './cells/runner_type_cell.vue'; import RunnerTags from './runner_tags.vue'; @@ -35,7 +35,7 @@ export default { GlSkeletonLoader, TimeAgo, RunnerActionsCell, - RunnerNameCell, + RunnerSummaryCell, RunnerTags, RunnerTypeCell, }, @@ -77,7 +77,7 @@ export default { }, fields: [ tableField({ key: 'type', label: __('Type/State') }), - tableField({ key: 'name', label: s__('Runners|Runner'), width: 30 }), + tableField({ key: 'summary', label: s__('Runners|Runner'), width: 30 }), tableField({ key: 'version', label: __('Version') }), tableField({ key: 'ipAddress', label: __('IP Address') }), tableField({ key: 'projectCount', label: __('Projects'), width: 5 }), @@ -107,8 +107,12 @@ export default { <runner-type-cell :runner="item" /> </template> - <template #cell(name)="{ item }"> - <runner-name-cell :runner="item" /> + <template #cell(summary)="{ item, index }"> + <runner-summary-cell :runner="item"> + <template #runner-name="{ runner }"> + <slot name="runner-name" :runner="runner" :index="index"></slot> + </template> + </runner-summary-cell> </template> <template #cell(version)="{ item: { version } }"> diff --git a/app/assets/javascripts/runner/components/runner_name.vue b/app/assets/javascripts/runner/components/runner_name.vue new file mode 100644 index 00000000000..8e495125e03 --- /dev/null +++ b/app/assets/javascripts/runner/components/runner_name.vue @@ -0,0 +1,18 @@ +<script> +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; + +export default { + props: { + runner: { + type: Object, + required: true, + }, + }, + methods: { + getIdFromGraphQLId, + }, +}; +</script> +<template> + <span>#{{ getIdFromGraphQLId(runner.id) }} ({{ runner.shortSha }})</span> +</template> diff --git a/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql b/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql index a601ee8d611..3e5109b1ac4 100644 --- a/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql +++ b/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql @@ -24,8 +24,11 @@ query getGroupRunners( search: $search sort: $sort ) { - nodes { - ...RunnerNode + edges { + webUrl + node { + ...RunnerNode + } } pageInfo { ...PageInfo diff --git a/app/assets/javascripts/runner/graphql/get_runners.query.graphql b/app/assets/javascripts/runner/graphql/get_runners.query.graphql index 9f837197558..51a91b9eb96 100644 --- a/app/assets/javascripts/runner/graphql/get_runners.query.graphql +++ b/app/assets/javascripts/runner/graphql/get_runners.query.graphql @@ -25,6 +25,7 @@ query getRunners( ) { nodes { ...RunnerNode + adminUrl } pageInfo { ...PageInfo diff --git a/app/assets/javascripts/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/runner/group_runners/group_runners_app.vue index 42e1a9e1de9..083b2666b7b 100644 --- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue +++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue @@ -1,13 +1,17 @@ <script> +import { GlLink } from '@gitlab/ui'; import createFlash from '~/flash'; import { fetchPolicies } from '~/lib/graphql'; import { updateHistory } from '~/lib/utils/url_utility'; import { formatNumber, sprintf, s__ } from '~/locale'; + import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; import RunnerList from '../components/runner_list.vue'; import RunnerManualSetupHelp from '../components/runner_manual_setup_help.vue'; +import RunnerName from '../components/runner_name.vue'; import RunnerPagination from '../components/runner_pagination.vue'; import RunnerTypeHelp from '../components/runner_type_help.vue'; + import { statusTokenConfig } from '../components/search_tokens/status_token_config'; import { typeTokenConfig } from '../components/search_tokens/type_token_config'; import { @@ -27,9 +31,11 @@ import { captureException } from '../sentry_utils'; export default { name: 'GroupRunnersApp', components: { + GlLink, RunnerFilteredSearchBar, RunnerList, RunnerManualSetupHelp, + RunnerName, RunnerTypeHelp, RunnerPagination, }, @@ -51,6 +57,7 @@ export default { return { search: fromUrlQueryToSearch(), runners: { + webUrls: [], items: [], pageInfo: {}, }, @@ -68,8 +75,10 @@ export default { }, update(data) { const { runners } = data?.group || {}; + return { - items: runners?.nodes || [], + webUrls: runners?.edges.map(({ webUrl }) => webUrl) || [], + items: runners?.edges.map(({ node }) => node) || [], pageInfo: runners?.pageInfo || {}, }; }, @@ -163,7 +172,13 @@ export default { {{ __('No runners found') }} </div> <template v-else> - <runner-list :runners="runners.items" :loading="runnersLoading" /> + <runner-list :runners="runners.items" :loading="runnersLoading"> + <template #runner-name="{ runner, index }"> + <gl-link :href="runners.webUrls[index]"> + <runner-name :runner="runner" /> + </gl-link> + </template> + </runner-list> <runner-pagination v-model="search.pagination" :page-info="runners.pageInfo" /> </template> </div> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js index d2afc02233e..294e5bd9f90 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js @@ -4,7 +4,7 @@ import TodoButton from './todo_button.vue'; export default { component: TodoButton, - title: 'vue_shared/components/todo_toggle/todo_button', + title: 'vue_shared/components/sidebar/todo_toggle/todo_button', }; const Template = (args, { argTypes }) => ({ diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index 06eebb95438..685f1f413e6 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -1,6 +1,6 @@ // For tabbed navigation links, scrolling tabs, etc. For all top/main navigation, // please check nav.scss -.nav-links:not(.quick-links) { +.nav-links { display: flex; padding: 0; margin: 0; diff --git a/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss b/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss index 0ae3e03e72c..25a565ce2ba 100644 --- a/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss +++ b/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss @@ -44,7 +44,7 @@ background-color: var(--ide-background, $badge-bg); } - .nav-links:not(.quick-links) li:not(.md-header-toolbar) a, + .nav-links li:not(.md-header-toolbar) a, .gl-tabs-nav li a, .dropdown-menu-inner-content, .file-row .file-row-icon svg, @@ -52,7 +52,7 @@ color: var(--ide-text-color-secondary, $gl-text-color-secondary); } - .nav-links:not(.quick-links) li:not(.md-header-toolbar), + .nav-links li:not(.md-header-toolbar), .gl-tabs-nav li { &:hover a, &.active a, @@ -148,7 +148,7 @@ .md blockquote, .md table:not(.code) tbody td, .md table:not(.code) tr th, - .nav-links:not(.quick-links), + .nav-links, .gl-tabs-nav, .common-note-form .md-area.is-focused .nav-links { border-color: var(--ide-border-color-alt, $white-dark); @@ -311,7 +311,7 @@ border-color: var(--ide-background, $border-color); background-color: var(--ide-dropdown-background, $white); - .nav-links:not(.quick-links) { + .nav-links { background-color: var(--ide-dropdown-hover-background, $white); border-color: var(--ide-dropdown-hover-background, $border-color); } diff --git a/app/graphql/mutations/dependency_proxy/group_settings/update.rb b/app/graphql/mutations/dependency_proxy/group_settings/update.rb new file mode 100644 index 00000000000..d10e43cde29 --- /dev/null +++ b/app/graphql/mutations/dependency_proxy/group_settings/update.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Mutations + module DependencyProxy + module GroupSettings + class Update < Mutations::BaseMutation + include Mutations::ResolvesGroup + + graphql_name 'UpdateDependencyProxySettings' + + authorize :admin_dependency_proxy + + argument :group_path, + GraphQL::Types::ID, + required: true, + description: 'Group path for the group dependency proxy.' + + argument :enabled, + GraphQL::Types::Boolean, + required: false, + description: copy_field_description(Types::DependencyProxy::ImageTtlGroupPolicyType, :enabled) + + field :dependency_proxy_setting, + Types::DependencyProxy::GroupSettingType, + null: true, + description: 'Group dependency proxy settings after mutation.' + + def resolve(group_path:, **args) + group = authorized_find!(group_path: group_path) + + result = ::DependencyProxy::GroupSettings::UpdateService + .new(container: group, current_user: current_user, params: args) + .execute + + { + dependency_proxy_setting: result.payload[:dependency_proxy_setting], + errors: result.errors + } + end + + private + + def find_object(group_path:) + resolve_group(full_path: group_path) + end + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index b512bfe8dd6..a3be4fcba92 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -42,6 +42,7 @@ module Types mount_mutation Mutations::CustomerRelations::Organizations::Update mount_mutation Mutations::Discussions::ToggleResolve mount_mutation Mutations::DependencyProxy::ImageTtlGroupPolicy::Update + mount_mutation Mutations::DependencyProxy::GroupSettings::Update mount_mutation Mutations::Environments::CanaryIngress::Update mount_mutation Mutations::Issues::Create mount_mutation Mutations::Issues::SetAssignees diff --git a/app/services/dependency_proxy/group_settings/update_service.rb b/app/services/dependency_proxy/group_settings/update_service.rb new file mode 100644 index 00000000000..ba43452def3 --- /dev/null +++ b/app/services/dependency_proxy/group_settings/update_service.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module DependencyProxy + module GroupSettings + class UpdateService < BaseContainerService + ALLOWED_ATTRIBUTES = %i[enabled].freeze + + def execute + return ServiceResponse.error(message: 'Access Denied', http_status: 403) unless allowed? + return ServiceResponse.error(message: 'Dependency proxy setting not found', http_status: 404) unless dependency_proxy_setting + + if dependency_proxy_setting.update(dependency_proxy_setting_params) + ServiceResponse.success(payload: { dependency_proxy_setting: dependency_proxy_setting }) + else + ServiceResponse.error( + message: dependency_proxy_setting.errors.full_messages.to_sentence || 'Bad request', + http_status: 400 + ) + end + end + + private + + def dependency_proxy_setting + container.dependency_proxy_setting + end + + def allowed? + Ability.allowed?(current_user, :admin_dependency_proxy, container) + end + + def dependency_proxy_setting_params + params.slice(*ALLOWED_ATTRIBUTES) + end + end + end +end diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index f2cee618849..1f2c16324fb 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -45,11 +45,10 @@ - if can?(current_user, :download_code, @project) = cache_if(cache_enabled, [@project, :download_code], expires_in: 1.minute) do %nav.project-stats - .nav-links.quick-links - - if @project.empty_repo? - = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors - - else - = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout) + - if @project.empty_repo? + = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors + - else + = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout) .home-panel-home-desc.mt-1 - if @project.description.present? diff --git a/config/feature_flags/development/operational_vulnerabilities.yml b/config/feature_flags/development/operational_vulnerabilities.yml new file mode 100644 index 00000000000..f1e19a626fb --- /dev/null +++ b/config/feature_flags/development/operational_vulnerabilities.yml @@ -0,0 +1,8 @@ +--- +name: operational_vulnerabilities +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70732 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/341423 +milestone: '14.4' +type: development +group: group::container security +default_enabled: false diff --git a/doc/administration/monitoring/performance/img/performance_bar_v14_0.png b/doc/administration/monitoring/performance/img/performance_bar_v14_0.png Binary files differdeleted file mode 100644 index 42261ddd720..00000000000 --- a/doc/administration/monitoring/performance/img/performance_bar_v14_0.png +++ /dev/null diff --git a/doc/administration/monitoring/performance/img/performance_bar_v14_4.png b/doc/administration/monitoring/performance/img/performance_bar_v14_4.png Binary files differnew file mode 100644 index 00000000000..1d3cefeb574 --- /dev/null +++ b/doc/administration/monitoring/performance/img/performance_bar_v14_4.png diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md index ef4db93d5fc..9e27ea5697b 100644 --- a/doc/administration/monitoring/performance/performance_bar.md +++ b/doc/administration/monitoring/performance/performance_bar.md @@ -8,11 +8,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - The **Stats** field [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/271551) in GitLab 13.9. > - The **Memory** field [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330736) in GitLab 14.0. +> - The **Flamegraph** field [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30275) in GitLab 14.4. You can display the performance bar to see statistics for the performance of a GitLab UI page. For example: -![Performance bar](img/performance_bar_v14_0.png) +![Performance bar](img/performance_bar_v14_4.png) ## Available information @@ -64,6 +65,7 @@ From left to right, the performance bar displays: can be added by its full URL (authenticated as the current user), or by the value of its `X-Request-Id` header. - **Download**: a link to download the raw JSON used to generate the Performance Bar reports. +- **Flamegraph**: a link to generate a [flamegraph](../../../development/profiling.md#speedscope-flamegraphs) of the current URL. - **Request Selector**: a select box displayed on the right-hand side of the Performance Bar which enables you to view these metrics for any requests made while the current page was open. Only the first two requests per unique URL are captured. diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md index c249f48b768..055f40ead64 100644 --- a/doc/administration/reply_by_email.md +++ b/doc/administration/reply_by_email.md @@ -45,6 +45,8 @@ following headers, in this order: 1. `To` header 1. `References` header +1. `Delivered-To` header +1. `Envelope-To` header If it finds a reply key, it leaves your reply as a comment on the entity the notification was about (issue, merge request, commit...). diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 16b3ce2f941..75e2a463cfa 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -4276,6 +4276,26 @@ Input type: `UpdateDependencyProxyImageTtlGroupPolicyInput` | <a id="mutationupdatedependencyproxyimagettlgrouppolicydependencyproxyimagettlpolicy"></a>`dependencyProxyImageTtlPolicy` | [`DependencyProxyImageTtlGroupPolicy`](#dependencyproxyimagettlgrouppolicy) | Group image TTL policy after mutation. | | <a id="mutationupdatedependencyproxyimagettlgrouppolicyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +### `Mutation.updateDependencyProxySettings` + +Input type: `UpdateDependencyProxySettingsInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationupdatedependencyproxysettingsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationupdatedependencyproxysettingsenabled"></a>`enabled` | [`Boolean`](#boolean) | Indicates whether the policy is enabled or disabled. | +| <a id="mutationupdatedependencyproxysettingsgrouppath"></a>`groupPath` | [`ID!`](#id) | Group path for the group dependency proxy. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationupdatedependencyproxysettingsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationupdatedependencyproxysettingsdependencyproxysetting"></a>`dependencyProxySetting` | [`DependencyProxySetting`](#dependencyproxysetting) | Group dependency proxy settings after mutation. | +| <a id="mutationupdatedependencyproxysettingserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | + ### `Mutation.updateEpic` Input type: `UpdateEpicInput` diff --git a/doc/api/integrations.md b/doc/api/integrations.md index fedfeef19b5..d59b0c8b4c1 100644 --- a/doc/api/integrations.md +++ b/doc/api/integrations.md @@ -304,12 +304,12 @@ GET /projects/:id/integrations/campfire Datadog system monitoring. -### Create/Edit Datadog service +### Create/Edit Datadog integration -Set Datadog service for a project. +Set Datadog integration for a project. ```plaintext -PUT /projects/:id/services/datadog +PUT /projects/:id/integrations/datadog ``` Parameters: @@ -322,20 +322,20 @@ Parameters: | `datadog_service` | string | false | Name of this GitLab instance that all data will be tagged with | | `datadog_env` | string | false | The environment tag that traces will be tagged with | -### Delete Datadog service +### Delete Datadog integration -Delete Datadog service for a project. +Delete Datadog integration for a project. ```plaintext -DELETE /projects/:id/services/datadog +DELETE /projects/:id/integrations/datadog ``` -### Get Datadog service settings +### Get Datadog integration settings -Get Datadog service settings for a project. +Get Datadog integration settings for a project. ```plaintext -GET /projects/:id/services/datadog +GET /projects/:id/integrations/datadog ``` ## Unify Circuit @@ -549,12 +549,12 @@ GET /projects/:id/integrations/emails-on-push Use IBM Engineering Workflow Management (EWM) as a project's issue tracker. -### Create/Edit EWM service +### Create/Edit EWM integration -Set EWM service for a project. +Set EWM integration for a project. ```plaintext -PUT /projects/:id/services/ewm +PUT /projects/:id/integrations/ewm ``` Parameters: @@ -565,20 +565,20 @@ Parameters: | `project_url` | string | true | The URL to the project in EWM | | `issues_url` | string | true | The URL to view an issue in EWM. Must contain `:id` | -### Delete EWM service +### Delete EWM integration -Delete EWM service for a project. +Delete EWM integration for a project. ```plaintext -DELETE /projects/:id/services/ewm +DELETE /projects/:id/integrations/ewm ``` -### Get EWM service settings +### Get EWM integration settings -Get EWM service settings for a project. +Get EWM integration settings for a project. ```plaintext -GET /projects/:id/services/ewm +GET /projects/:id/integrations/ewm ``` ## Confluence integration diff --git a/doc/development/fe_guide/storybook.md b/doc/development/fe_guide/storybook.md index d4b65f3232a..a46157d2cad 100644 --- a/doc/development/fe_guide/storybook.md +++ b/doc/development/fe_guide/storybook.md @@ -39,16 +39,16 @@ To add a story: vue_shared/ ├─ components/ │ ├─ sidebar - │ │ ├─ todo_button.vue - │ │ ├─ todo_button.stories.js + │ | ├─ todo_toggle + │ | | ├─ todo_button.vue + │ │ | ├─ todo_button.stories.js ``` 1. Write the story as per the [official Storybook instructions](https://storybook.js.org/docs/vue/writing-stories/introduction/) Notes: - Specify the `title` field of the story as the component's file path from the `javascripts/` directory, - e.g. if the component is located at `app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.vue`, specify the `title` as - `vue_shared/components/To-do Button`. This will ensure the Storybook navigation maps closely to our internal directory structure. + e.g. if the component is located at `app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.vue`, specify the story `title` as `vue_shared/components/sidebar/todo_toggle/todo_button`. This will ensure the Storybook navigation maps closely to our internal directory structure. ## Mock backend APIs diff --git a/doc/development/profiling.md b/doc/development/profiling.md index a58e1d60cc5..4753ce81821 100644 --- a/doc/development/profiling.md +++ b/doc/development/profiling.md @@ -98,7 +98,7 @@ profile and log output to S3. ## Speedscope flamegraphs -You can generate a flamegraph for a particular URL by adding the `performance_bar=flamegraph` parameter to the request. +You can generate a flamegraph for a particular URL by selecting the flamegraph button in the performance bar or by adding the `performance_bar=flamegraph` parameter to the request. ![Speedscope](img/speedscope_v13_12.png) diff --git a/lib/atlassian/jira_connect/serializers/environment_entity.rb b/lib/atlassian/jira_connect/serializers/environment_entity.rb index f3699e4d0ee..b6b5db40ba6 100644 --- a/lib/atlassian/jira_connect/serializers/environment_entity.rb +++ b/lib/atlassian/jira_connect/serializers/environment_entity.rb @@ -21,13 +21,13 @@ module Atlassian def type case environment.name - when /prod/i + when /\A(.*[^a-z0-9])?(staging|stage|stg|preprod|pre-prod|model|internal)([^a-z0-9].*)?\z/i + 'staging' + when /\A(.*[^a-z0-9])?(prod|production|prd|live)([^a-z0-9].*)?\z/i 'production' - when /test/i + when /\A(.*[^a-z0-9])?(test|testing|tests|tst|integration|integ|intg|int|acceptance|accept|acpt|qa|qc|control|quality)([^a-z0-9].*)?\z/i 'testing' - when /staging/i - 'staging' - when /(dev|review)/i + when /\A(.*[^a-z0-9])?(dev|review|development)([^a-z0-9].*)?\z/i 'development' else 'unmapped' diff --git a/lib/gitlab/utils/delegator_override/validator.rb b/lib/gitlab/utils/delegator_override/validator.rb index 825b3efa203..402154b41c2 100644 --- a/lib/gitlab/utils/delegator_override/validator.rb +++ b/lib/gitlab/utils/delegator_override/validator.rb @@ -51,7 +51,13 @@ module Gitlab # Workaround to fully load the instance methods in the target class. # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69823#note_678887402 - target_classes.map(&:new) rescue nil + begin + target_classes.map(&:new) + rescue ArgumentError + # Some models might raise ArgumentError here, but it's fine in this case, + # because this is enough to force ActiveRecord to generate the methods we + # need to verify, so it's safe to ignore it. + end (delegator_class.instance_methods - allowlist).each do |method_name| target_classes.each do |target_class| diff --git a/lib/tasks/lint.rake b/lib/tasks/lint.rake index 7d1721f1df8..62d31803f6e 100644 --- a/lib/tasks/lint.rake +++ b/lib/tasks/lint.rake @@ -12,6 +12,13 @@ unless Rails.env.production? dev:load ] do Gitlab::Utils::Override.verify! + end + + desc "GitLab | Lint | Static verification with database" + task static_verification_with_database: %w[ + lint:static_verification_env + dev:load + ] do Gitlab::Utils::DelegatorOverride.verify! end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b32f8c5f566..469cf57afce 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -24646,6 +24646,9 @@ msgstr "" msgid "PerformanceBar|First Contentful Paint" msgstr "" +msgid "PerformanceBar|Flamegraph" +msgstr "" + msgid "PerformanceBar|Frontend resources" msgstr "" @@ -29562,7 +29565,7 @@ msgstr "" msgid "Save pipeline schedule" msgstr "" -msgid "Save space and find images in the container Registry. remove unneeded tags and keep only the ones you want. %{linkStart}How does cleanup work?%{linkEnd}" +msgid "Save storage space by automatically deleting tags from the container registry and keeping the ones you want. %{linkStart}How does cleanup work?%{linkEnd}" msgstr "" msgid "Saving" @@ -30250,6 +30253,9 @@ msgstr "" msgid "SecurityReports|Create issue" msgstr "" +msgid "SecurityReports|Development vulnerabilities" +msgstr "" + msgid "SecurityReports|Dismiss vulnerability" msgstr "" @@ -30391,6 +30397,9 @@ msgstr "" msgid "SecurityReports|Take survey" msgstr "" +msgid "SecurityReports|The Vulnerability Report shows the results of the lastest successful pipeline on your project's default branch, as well as vulnerabilities from your latest container scan. %{linkStart}Learn more.%{linkEnd}" +msgstr "" + msgid "SecurityReports|The security reports below contain one or more vulnerability findings that could not be parsed and were not recorded. Download the artifacts in the job output to investigate. Ensure any security report created conforms to the relevant %{helpPageLinkStart}JSON schema%{helpPageLinkEnd}." msgstr "" @@ -33666,9 +33675,6 @@ msgstr "" msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")." msgstr "" -msgid "The Vulnerability Report shows the results of the last successful pipeline run on the default branch." -msgstr "" - msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS." msgstr "" diff --git a/package.json b/package.json index d8fc66adc50..ea30ed8b984 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@rails/ujs": "6.1.3-2", "@sentry/browser": "5.30.0", "@sourcegraph/code-host-integration": "0.0.60", - "@tiptap/core": "^2.0.0-beta.110", + "@tiptap/core": "^2.0.0-beta.111", "@tiptap/extension-blockquote": "^2.0.0-beta.15", "@tiptap/extension-bold": "^2.0.0-beta.15", "@tiptap/extension-bullet-list": "^2.0.0-beta.15", @@ -92,7 +92,7 @@ "@tiptap/extension-task-item": "^2.0.0-beta.18", "@tiptap/extension-task-list": "^2.0.0-beta.17", "@tiptap/extension-text": "^2.0.0-beta.13", - "@tiptap/vue-2": "^2.0.0-beta.52", + "@tiptap/vue-2": "^2.0.0-beta.53", "@toast-ui/editor": "^2.5.2", "@toast-ui/vue-editor": "^2.5.2", "apollo-cache-inmemory": "^1.6.6", diff --git a/spec/factories/dependency_proxy/group_settings.rb b/spec/factories/dependency_proxy/group_settings.rb new file mode 100644 index 00000000000..c15cddf7430 --- /dev/null +++ b/spec/factories/dependency_proxy/group_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :dependency_proxy_group_setting, class: 'DependencyProxy::GroupSetting' do + group + + enabled { true } + end +end diff --git a/spec/frontend/content_editor/extensions/table_of_contents_spec.js b/spec/frontend/content_editor/extensions/table_of_contents_spec.js index a73fb05e9fd..83818899c17 100644 --- a/spec/frontend/content_editor/extensions/table_of_contents_spec.js +++ b/spec/frontend/content_editor/extensions/table_of_contents_spec.js @@ -1,34 +1,35 @@ import TableOfContents from '~/content_editor/extensions/table_of_contents'; -import { createTestEditor } from '../test_utils'; +import { createTestEditor, createDocBuilder } from '../test_utils'; describe('content_editor/extensions/emoji', () => { let tiptapEditor; + let builders; beforeEach(() => { tiptapEditor = createTestEditor({ extensions: [TableOfContents] }); + ({ builders } = createDocBuilder({ + tiptapEditor, + names: { tableOfContents: { nodeType: TableOfContents.name } }, + })); }); it.each` - input | insertedNodeName - ${'[[_TOC_]]'} | ${TableOfContents.name} - ${'[TOC]'} | ${TableOfContents.name} - ${'[toc]'} | ${'paragraph'} - ${'TOC'} | ${'paragraph'} - ${'[_TOC_]'} | ${'paragraph'} - ${'[[TOC]]'} | ${'paragraph'} - `('with input=$input, then should insert a $insertedNodeName', ({ input, insertedNodeName }) => { + input | insertedNode + ${'[[_TOC_]]'} | ${'tableOfContents'} + ${'[TOC]'} | ${'tableOfContents'} + ${'[toc]'} | ${'p'} + ${'TOC'} | ${'p'} + ${'[_TOC_]'} | ${'p'} + ${'[[TOC]]'} | ${'p'} + `('with input=$input, then should insert a $insertedNode', ({ input, insertedNode }) => { + const { doc } = builders; const { view } = tiptapEditor; const { selection } = view.state; + const expectedDoc = doc(builders[insertedNode]()); // Triggers the event handler that input rules listen to view.someProp('handleTextInput', (f) => f(view, selection.from, selection.to, input)); - expect(tiptapEditor.state.doc.content.content).toEqual([ - expect.objectContaining({ - type: expect.objectContaining({ - name: insertedNodeName, - }), - }), - ]); + expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); }); }); diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js index 3292f635f6b..ba332e0f918 100644 --- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js +++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js @@ -1,3 +1,4 @@ +import { GlLink } from '@gitlab/ui'; import { createLocalVue, mount, shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; @@ -5,6 +6,7 @@ import setWindowLocation from 'helpers/set_window_location_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { updateHistory } from '~/lib/utils/url_utility'; import AdminRunnersApp from '~/runner/admin_runners/admin_runners_app.vue'; @@ -98,6 +100,20 @@ describe('AdminRunnersApp', () => { expect(findRunnerList().props('runners')).toEqual(runnersData.data.runners.nodes); }); + it('runner item links to the runner admin page', async () => { + createComponent({ mountFn: mount }); + + await waitForPromises(); + + const { id, shortSha } = runnersData.data.runners.nodes[0]; + const numericId = getIdFromGraphQLId(id); + + const runnerLink = wrapper.find('tr [data-testid="td-summary"]').find(GlLink); + + expect(runnerLink.text()).toBe(`#${numericId} (${shortSha})`); + expect(runnerLink.attributes('href')).toBe(`http://localhost/admin/runners/${numericId}`); + }); + it('requests the runners with no filters', () => { expect(mockRunnersQuery).toHaveBeenLastCalledWith({ status: undefined, diff --git a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js index 95f7c38cafc..5aa3879ac3e 100644 --- a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js +++ b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js @@ -5,15 +5,18 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue'; +import getGroupRunnersQuery from '~/runner/graphql/get_group_runners.query.graphql'; import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql'; import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql'; import runnerUpdateMutation from '~/runner/graphql/runner_update.mutation.graphql'; import { captureException } from '~/runner/sentry_utils'; -import { runnerData } from '../../mock_data'; +import { runnersData, runnerData } from '../../mock_data'; -const mockRunner = runnerData.data.runner; +const mockRunner = runnersData.data.runners.nodes[0]; +const mockRunnerDetails = runnerData.data.runner; const getRunnersQueryName = getRunnersQuery.definitions[0].name.value; +const getGroupRunnersQueryName = getGroupRunnersQuery.definitions[0].name.value; const localVue = createLocalVue(); localVue.use(VueApollo); @@ -36,6 +39,7 @@ describe('RunnerTypeCell', () => { propsData: { runner: { id: mockRunner.id, + adminUrl: mockRunner.adminUrl, active, }, }, @@ -61,7 +65,7 @@ describe('RunnerTypeCell', () => { runnerUpdateMutationHandler.mockResolvedValue({ data: { runnerUpdate: { - runner: runnerData.data.runner, + runner: mockRunnerDetails, errors: [], }, }, @@ -78,7 +82,7 @@ describe('RunnerTypeCell', () => { it('Displays the runner edit link with the correct href', () => { createComponent(); - expect(findEditBtn().attributes('href')).toBe('/admin/runners/1'); + expect(findEditBtn().attributes('href')).toBe(mockRunner.adminUrl); }); describe.each` @@ -231,7 +235,7 @@ describe('RunnerTypeCell', () => { }, }, awaitRefetchQueries: true, - refetchQueries: [getRunnersQueryName], + refetchQueries: [getRunnersQueryName, getGroupRunnersQueryName], }); }); diff --git a/spec/frontend/runner/components/cells/runner_name_cell_spec.js b/spec/frontend/runner/components/cells/runner_summary_cell_spec.js index 26055fc0faf..1c9282e0acd 100644 --- a/spec/frontend/runner/components/cells/runner_name_cell_spec.js +++ b/spec/frontend/runner/components/cells/runner_summary_cell_spec.js @@ -1,6 +1,5 @@ -import { GlLink } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; -import RunnerNameCell from '~/runner/components/cells/runner_name_cell.vue'; +import RunnerSummaryCell from '~/runner/components/cells/runner_summary_cell.vue'; const mockId = '1'; const mockShortSha = '2P6oDVDm'; @@ -9,10 +8,8 @@ const mockDescription = 'runner-1'; describe('RunnerTypeCell', () => { let wrapper; - const findLink = () => wrapper.findComponent(GlLink); - - const createComponent = () => { - wrapper = mount(RunnerNameCell, { + const createComponent = (options) => { + wrapper = mount(RunnerSummaryCell, { propsData: { runner: { id: `gid://gitlab/Ci::Runner/${mockId}`, @@ -20,6 +17,7 @@ describe('RunnerTypeCell', () => { description: mockDescription, }, }, + ...options, }); }; @@ -31,12 +29,23 @@ describe('RunnerTypeCell', () => { wrapper.destroy(); }); - it('Displays the runner link with id and short token', () => { - expect(findLink().text()).toBe(`#${mockId} (${mockShortSha})`); - expect(findLink().attributes('href')).toBe(`/admin/runners/${mockId}`); + it('Displays the runner name as id and short token', () => { + expect(wrapper.text()).toContain(`#${mockId} (${mockShortSha})`); }); it('Displays the runner description', () => { expect(wrapper.text()).toContain(mockDescription); }); + + it('Displays a custom slot', () => { + const slotContent = 'My custom runner summary'; + + createComponent({ + slots: { + 'runner-name': slotContent, + }, + }); + + expect(wrapper.text()).toContain(slotContent); + }); }); diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js index 344d1e5c150..e24dffea1eb 100644 --- a/spec/frontend/runner/components/runner_list_spec.js +++ b/spec/frontend/runner/components/runner_list_spec.js @@ -1,4 +1,4 @@ -import { GlLink, GlTable, GlSkeletonLoader } from '@gitlab/ui'; +import { GlTable, GlSkeletonLoader } from '@gitlab/ui'; import { mount, shallowMount } from '@vue/test-utils'; import { cloneDeep } from 'lodash'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; @@ -67,11 +67,11 @@ describe('RunnerList', () => { // Badges expect(findCell({ fieldKey: 'type' }).text()).toMatchInterpolatedText('specific paused'); - // Runner identifier - expect(findCell({ fieldKey: 'name' }).text()).toContain( + // Runner summary + expect(findCell({ fieldKey: 'summary' }).text()).toContain( `#${getIdFromGraphQLId(id)} (${shortSha})`, ); - expect(findCell({ fieldKey: 'name' }).text()).toContain(description); + expect(findCell({ fieldKey: 'summary' }).text()).toContain(description); // Other fields expect(findCell({ fieldKey: 'version' }).text()).toBe(version); @@ -136,12 +136,11 @@ describe('RunnerList', () => { }); }); - it('Links to the runner page', () => { - const { id } = mockRunners[0]; + it('Shows runner identifier', () => { + const { id, shortSha } = mockRunners[0]; + const numericId = getIdFromGraphQLId(id); - expect(findCell({ fieldKey: 'name' }).find(GlLink).attributes('href')).toBe( - `/admin/runners/${getIdFromGraphQLId(id)}`, - ); + expect(findCell({ fieldKey: 'summary' }).text()).toContain(`#${numericId} (${shortSha})`); }); describe('When data is loading', () => { diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js index e80da40e3bd..aeab297c8f4 100644 --- a/spec/frontend/runner/group_runners/group_runners_app_spec.js +++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js @@ -1,3 +1,4 @@ +import { GlLink } from '@gitlab/ui'; import { createLocalVue, shallowMount, mount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; @@ -5,6 +6,7 @@ import setWindowLocation from 'helpers/set_window_location_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { updateHistory } from '~/lib/utils/url_utility'; import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue'; @@ -34,8 +36,7 @@ localVue.use(VueApollo); const mockGroupFullPath = 'group1'; const mockRegistrationToken = 'AABBCC'; -const mockRunners = groupRunnersData.data.group.runners.nodes; -const mockGroupRunnersLimitedCount = mockRunners.length; +const mockGroupRunnersLimitedCount = groupRunnersData.data.group.runners.edges.length; jest.mock('~/flash'); jest.mock('~/runner/sentry_utils'); @@ -91,7 +92,22 @@ describe('GroupRunnersApp', () => { }); it('shows the runners list', () => { - expect(findRunnerList().props('runners')).toEqual(groupRunnersData.data.group.runners.nodes); + expect(findRunnerList().props('runners')).toEqual( + groupRunnersData.data.group.runners.edges.map(({ node }) => node), + ); + }); + + it('runner item links to the runner group page', async () => { + const { webUrl, node } = groupRunnersData.data.group.runners.edges[0]; + const { id, shortSha } = node; + + createComponent({ mountFn: mount }); + + await waitForPromises(); + + const runnerLink = wrapper.find('tr [data-testid="td-summary"]').find(GlLink); + expect(runnerLink.text()).toBe(`#${getIdFromGraphQLId(id)} (${shortSha})`); + expect(runnerLink.attributes('href')).toBe(webUrl); }); it('requests the runners with group path and no other filters', () => { diff --git a/spec/graphql/mutations/dependency_proxy/group_settings/update_spec.rb b/spec/graphql/mutations/dependency_proxy/group_settings/update_spec.rb new file mode 100644 index 00000000000..35d3224d5ba --- /dev/null +++ b/spec/graphql/mutations/dependency_proxy/group_settings/update_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::DependencyProxy::GroupSettings::Update do + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:group) { create(:group) } + let_it_be_with_reload(:group_settings) { create(:dependency_proxy_group_setting, group: group) } + let_it_be(:user) { create(:user) } + + let(:params) { { group_path: group.full_path, enabled: false } } + + specify { expect(described_class).to require_graphql_authorizations(:admin_dependency_proxy) } + + describe '#resolve' do + subject { described_class.new(object: group, context: { current_user: user }, field: nil).resolve(**params) } + + shared_examples 'updating the dependency proxy group settings' do + it_behaves_like 'updating the dependency proxy group settings attributes', + from: { enabled: true }, + to: { enabled: false } + + it 'returns the dependency proxy settings no errors' do + expect(subject).to eq( + dependency_proxy_setting: group_settings, + errors: [] + ) + end + end + + shared_examples 'denying access to dependency proxy group settings' do + it 'raises Gitlab::Graphql::Errors::ResourceNotAvailable' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + where(:user_role, :shared_examples_name) do + :maintainer | 'updating the dependency proxy group settings' + :developer | 'updating the dependency proxy group settings' + :reporter | 'denying access to dependency proxy group settings' + :guest | 'denying access to dependency proxy group settings' + :anonymous | 'denying access to dependency proxy group settings' + end + + with_them do + before do + stub_config(dependency_proxy: { enabled: true }) + group.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + end +end diff --git a/spec/lib/atlassian/jira_connect/serializers/deployment_entity_spec.rb b/spec/lib/atlassian/jira_connect/serializers/deployment_entity_spec.rb index 82bcbdc4561..8ccc3253a46 100644 --- a/spec/lib/atlassian/jira_connect/serializers/deployment_entity_spec.rb +++ b/spec/lib/atlassian/jira_connect/serializers/deployment_entity_spec.rb @@ -46,12 +46,22 @@ RSpec.describe Atlassian::JiraConnect::Serializers::DeploymentEntity do using RSpec::Parameterized::TableSyntax where(:env_name, :env_type) do + 'PRODUCTION' | 'production' 'prod' | 'production' + 'prod-east-2' | 'production' + 'us-prod-east' | 'production' + 'fe-production' | 'production' 'test' | 'testing' + 'qa-env-2' | 'testing' 'staging' | 'staging' + 'pre-prod' | 'staging' + 'blue-kit-stage' | 'staging' + 'pre-prod' | 'staging' 'dev' | 'development' 'review/app' | 'development' 'something-else' | 'unmapped' + 'store-produce' | 'unmapped' + 'unproductive' | 'unmapped' end with_them do diff --git a/spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb b/spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb new file mode 100644 index 00000000000..f05bf23ad27 --- /dev/null +++ b/spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Updating the dependency proxy group settings' do + include GraphqlHelpers + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + + let(:params) do + { + group_path: group.full_path, + enabled: false + } + end + + let(:mutation) do + graphql_mutation(:update_dependency_proxy_settings, params) do + <<~QL + dependencyProxySetting { + enabled + } + errors + QL + end + end + + let(:mutation_response) { graphql_mutation_response(:update_dependency_proxy_settings) } + let(:group_settings) { mutation_response['dependencyProxySetting'] } + + before do + stub_config(dependency_proxy: { enabled: true }) + end + + describe 'post graphql mutation' do + subject { post_graphql_mutation(mutation, current_user: user) } + + let_it_be_with_reload(:group) { create(:group) } + let_it_be_with_reload(:group_settings) { create(:dependency_proxy_group_setting, group: group) } + + context 'without permission' do + it 'returns no response' do + subject + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response).to be_nil + end + end + + context 'with permission' do + before do + group.add_developer(user) + end + + it 'returns the updated dependency proxy settings', :aggregate_failures do + subject + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['errors']).to be_empty + expect(group_settings[:enabled]).to eq(false) + end + end + end +end diff --git a/spec/services/dependency_proxy/group_settings/update_service_spec.rb b/spec/services/dependency_proxy/group_settings/update_service_spec.rb new file mode 100644 index 00000000000..6f8c55daa8d --- /dev/null +++ b/spec/services/dependency_proxy/group_settings/update_service_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::DependencyProxy::GroupSettings::UpdateService do + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:group) { create(:group) } + let_it_be_with_reload(:group_settings) { create(:dependency_proxy_group_setting, group: group) } + let_it_be(:user) { create(:user) } + let_it_be(:params) { { enabled: false } } + + describe '#execute' do + subject { described_class.new(container: group, current_user: user, params: params).execute } + + shared_examples 'updating the dependency proxy group settings' do + it_behaves_like 'updating the dependency proxy group settings attributes', + from: { enabled: true }, + to: { enabled: false } + + it 'returns a success' do + result = subject + + expect(result.payload[:dependency_proxy_setting]).to be_present + expect(result).to be_success + end + end + + shared_examples 'denying access to dependency proxy group settings' do + context 'with existing dependency proxy group settings' do + it 'returns an error' do + result = subject + + expect(result).to have_attributes( + message: 'Access Denied', + status: :error, + http_status: 403 + ) + end + end + end + + where(:user_role, :shared_examples_name) do + :maintainer | 'updating the dependency proxy group settings' + :developer | 'updating the dependency proxy group settings' + :reporter | 'denying access to dependency proxy group settings' + :guest | 'denying access to dependency proxy group settings' + :anonymous | 'denying access to dependency proxy group settings' + end + + with_them do + before do + stub_config(dependency_proxy: { enabled: true }) + group.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + end +end diff --git a/spec/support/shared_examples/services/dependency_proxy_settings_shared_examples.rb b/spec/support/shared_examples/services/dependency_proxy_settings_shared_examples.rb new file mode 100644 index 00000000000..2c1dc2da560 --- /dev/null +++ b/spec/support/shared_examples/services/dependency_proxy_settings_shared_examples.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'updating the dependency proxy group settings attributes' do |from: {}, to: {}| + it 'updates the dependency proxy settings' do + expect { subject } + .to change { group_settings.reload.enabled }.from(from[:enabled]).to(to[:enabled]) + end +end diff --git a/yarn.lock b/yarn.lock index c39044537b6..f534d310f82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1310,10 +1310,10 @@ dom-accessibility-api "^0.5.1" pretty-format "^26.4.2" -"@tiptap/core@^2.0.0-beta.110": - version "2.0.0-beta.110" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.110.tgz#a03413056f484b875c85b26aa2eff8b3022e014f" - integrity sha512-QWfgDxommAzv1Ed9vA1KAAvBTkdWkkZmNiQIlqlyhe/5M1YffkMfy1+P7KOA+lxN9Ft5TERGa0+Fg9mK3VX2QQ== +"@tiptap/core@^2.0.0-beta.111": + version "2.0.0-beta.111" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.111.tgz#9f7237fad9ffd0da52808ec50915a84bb1452139" + integrity sha512-QwuVd5Ht+p/7x2/r8/Qzz/mvC6Ghd/CMEgj9vqkj8nLjPHisxfl7PqLy+vAgnX+7hUUTJJ0QZs6143K+dSorWQ== dependencies: "@types/prosemirror-commands" "^1.0.4" "@types/prosemirror-inputrules" "^1.0.4" @@ -1344,10 +1344,10 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.15.tgz#cf9ddb3fc316be9707753ad4e497bfb8a3ebb0c2" integrity sha512-jKyV6iiwhxwa0+7uuKD74jNDVNLNOS1GmU14MgaA95pY5e1fyaRBPPX8Gtt89niz2CLOY711AV17RPZTe/e60w== -"@tiptap/extension-bubble-menu@^2.0.0-beta.34": - version "2.0.0-beta.34" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.34.tgz#07598730c3ac755c86b2ac1e283b5be3591fbb0a" - integrity sha512-vw2RpwgqJUE7tpDayOey1AYs2qp1J0TVdlo4HYzlOWV1kJ+2Zevf0oA83j+IX6d0XmYqNTp+r+VgLZEd0K1Neg== +"@tiptap/extension-bubble-menu@^2.0.0-beta.35": + version "2.0.0-beta.35" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.35.tgz#11ea379b9e9044c96ddbcdc457155b6fd4ee81e7" + integrity sha512-9Cu7X2HvTCLMDOZNZLM0gL/fXHsQOUiWtVoDTfYGizo2sztFcgg7IV+FYvquerAzwfZvUX3EDdYBnRbhVXRobg== dependencies: prosemirror-state "^1.3.4" prosemirror-view "^1.20.1" @@ -1397,10 +1397,10 @@ "@types/prosemirror-dropcursor" "^1.0.3" prosemirror-dropcursor "^1.3.5" -"@tiptap/extension-floating-menu@^2.0.0-beta.28": - version "2.0.0-beta.28" - resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.28.tgz#be45bd0a558498e0fdfc593832726618e69ee8fc" - integrity sha512-GxrD4i75Px6RLlY6ZZRU5C5aCjnEiKODA07TpyITlrB6cwmdzZG4L7n+x4Z/VGiUu6+fkiLpnjLbX/Fbk9Yn2w== +"@tiptap/extension-floating-menu@^2.0.0-beta.29": + version "2.0.0-beta.29" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.29.tgz#a3d27a859be0f21334a37f9213bcd49e2f80241c" + integrity sha512-PwXdDqGwbm4sUlm+FTs69xwPl99r1HQ8iye9XLuQMkjMJv7W4fByyStqzSMiocYcdGF3rWA+txQRen5/qo9O7Q== dependencies: prosemirror-state "^1.3.4" prosemirror-view "^1.20.1" @@ -1530,13 +1530,13 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.13.tgz#da0af8d9a3f149d20076e15d88c6af21fb6d940f" integrity sha512-0EtAwuRldCAoFaL/iXgkRepEeOd55rPg5N4FQUN1xTwZT7PDofukP0DG/2jff/Uj17x4uTaJAa9qlFWuNnDvjw== -"@tiptap/vue-2@^2.0.0-beta.52": - version "2.0.0-beta.52" - resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.52.tgz#b61fd95f2368a86e64bee20b35a85a69a48fc930" - integrity sha512-81UfvJTK68hy+KZDdHSbtMVXbUhkZ8zW9u+dotga34YHgfTeZjO3UrF5eJ34dwzsPaaSU+buFu/TMH5vP3KcWQ== +"@tiptap/vue-2@^2.0.0-beta.53": + version "2.0.0-beta.53" + resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.53.tgz#6b1239fbba315cfc2c48598b32f0e08ebdbedeae" + integrity sha512-XviXJWtZG+z6LirjkbJ0/seatlXd3ecIPSzJKyNwCxM/PDzNlKjVotKmMdL1miJIvakWXQdmN2alh6/KESuvtQ== dependencies: - "@tiptap/extension-bubble-menu" "^2.0.0-beta.34" - "@tiptap/extension-floating-menu" "^2.0.0-beta.28" + "@tiptap/extension-bubble-menu" "^2.0.0-beta.35" + "@tiptap/extension-floating-menu" "^2.0.0-beta.29" prosemirror-view "^1.20.1" "@toast-ui/editor@^2.5.2": |