diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-06 15:10:20 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-06 15:10:20 +0300 |
commit | 9ad806210531e19c851a69cbcdaf9ed8bd6cb478 (patch) | |
tree | 348438fb6eb4219841571786b0826736d1d3e016 | |
parent | 4903f95b04db58edc1931ec917d2313c916a06b2 (diff) |
Add latest changes from gitlab-org/gitlab@master
29 files changed, 246 insertions, 142 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index c9439b8889d..3970647ecc2 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -27c00fa0b260e976c5d6c0382ea850949cb51d63 +f99ae76f9fecec6c162dbd990845ba9c7c5727d6 diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js index bbd081843ca..281e48d9aa7 100644 --- a/app/assets/javascripts/issues/list/utils.js +++ b/app/assets/javascripts/issues/list/utils.js @@ -289,9 +289,9 @@ const formatData = (token) => { }; export const convertToApiParams = (filterTokens) => { - const params = {}; - const not = {}; - const or = {}; + const params = new Map(); + const not = new Map(); + const or = new Map(); filterTokens .filter((token) => token.type !== FILTERED_SEARCH_TERM) @@ -307,32 +307,34 @@ export const convertToApiParams = (filterTokens) => { obj = params; } const data = formatData(token); - Object.assign(obj, { - [apiField]: obj[apiField] ? [obj[apiField], data].flat() : data, - }); + obj.set(apiField, obj.has(apiField) ? [obj.get(apiField), data].flat() : data); }); - if (Object.keys(not).length) { - Object.assign(params, { not }); + if (not.size) { + params.set('not', Object.fromEntries(not)); } - if (Object.keys(or).length) { - Object.assign(params, { or }); + if (or.size) { + params.set('or', Object.fromEntries(or)); } - return params; + return Object.fromEntries(params); }; -export const convertToUrlParams = (filterTokens) => - filterTokens +export const convertToUrlParams = (filterTokens) => { + const urlParamsMap = filterTokens .filter((token) => token.type !== FILTERED_SEARCH_TERM) .reduce((acc, token) => { const filterType = getFilterType(token); const urlParam = filters[token.type][URL_PARAM][token.value.operator]?.[filterType]; - return Object.assign(acc, { - [urlParam]: acc[urlParam] ? [acc[urlParam], token.value.data].flat() : token.value.data, - }); - }, {}); + return acc.set( + urlParam, + acc.has(urlParam) ? [acc.get(urlParam), token.value.data].flat() : token.value.data, + ); + }, new Map()); + + return Object.fromEntries(urlParamsMap); +}; export const convertToSearchQuery = (filterTokens) => filterTokens diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue index 4af8cf1ded3..b289229af69 100644 --- a/app/assets/javascripts/notes/components/note_header.vue +++ b/app/assets/javascripts/notes/components/note_header.vue @@ -85,7 +85,7 @@ export default { return this.expanded ? 'chevron-up' : 'chevron-down'; }, noteTimestampLink() { - return this.noteId ? `#note_${this.noteId}` : undefined; + return this.noteId ? `#note_${getIdFromGraphQLId(this.noteId)}` : undefined; }, hasAuthor() { return this.author && Object.keys(this.author).length; diff --git a/app/assets/javascripts/repository/components/blob_viewers/notebook_viewer.vue b/app/assets/javascripts/repository/components/blob_viewers/notebook_viewer.vue index 1114a0942ec..53dad19028d 100644 --- a/app/assets/javascripts/repository/components/blob_viewers/notebook_viewer.vue +++ b/app/assets/javascripts/repository/components/blob_viewers/notebook_viewer.vue @@ -1,11 +1,15 @@ <script> -import { GlLoadingIcon } from '@gitlab/ui'; -import notebookLoader from '~/blob/notebook'; import { stripPathTail } from '~/lib/utils/url_utility'; +import NotebookViewer from '~/blob/notebook/notebook_viewer.vue'; export default { - components: { - GlLoadingIcon, + components: { NotebookViewer }, + provide() { + // `relativeRawPath` is injected in app/assets/javascripts/notebook/cells/markdown.vue + // It is needed for images in Markdown cells that reference local files to work. + // See the following MR for more context: + // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69075 + return { relativeRawPath: stripPathTail(this.url) }; }, props: { blob: { @@ -18,14 +22,9 @@ export default { url: this.blob.rawPath, }; }, - mounted() { - notebookLoader({ el: this.$refs.viewer, relativeRawPath: stripPathTail(this.url) }); - }, }; </script> <template> - <div ref="viewer" :data-endpoint="url" data-testid="notebook"> - <gl-loading-icon class="gl-my-4" size="lg" /> - </div> + <notebook-viewer :endpoint="url" /> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue b/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue index 917ed259dd0..f2ec8f589ce 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue @@ -142,7 +142,7 @@ export default { :title="setTooltip(btn)" :href="btn.href" :target="btn.target" - :class="[{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }, btn.class]" + :class="[{ 'gl-mr-1': index !== tertiaryButtons.length - 1 }, btn.class]" :data-clipboard-text="btn.dataClipboardText" :data-qa-selector="actionButtonQaSelector(btn)" :data-method="btn.dataMethod" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue index c762922d890..b192ccfa379 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue @@ -31,7 +31,7 @@ export default { }; </script> <template> - <h4 class="js-mr-widget-author"> + <h4 class="js-mr-widget-author gl-flex-grow-1"> {{ actionText }} <mr-widget-author :author="author" /> <span class="sr-only">{{ dateReadable }} ({{ dateTitle }})</span> diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 827080d9866..13009651550 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -294,6 +294,9 @@ export default class MergeRequestStore { // Security reports this.sastComparisonPath = data.sast_comparison_path; this.secretDetectionComparisonPath = data.secret_detection_comparison_path; + + this.sastComparisonPathV2 = data.new_sast_comparison_path; + this.secretDetectionComparisonPathV2 = data.new_secret_detection_comparison_path; } get isNothingToMergeState() { diff --git a/app/views/notify/unknown_sign_in_email.html.haml b/app/views/notify/unknown_sign_in_email.html.haml index f8a0ae1352c..e252af78060 100644 --- a/app/views/notify/unknown_sign_in_email.html.haml +++ b/app/views/notify/unknown_sign_in_email.html.haml @@ -24,6 +24,11 @@ = Gitlab.config.gitlab.host %tr %td{ style: "#{default_style}border-top:1px solid #ededed;" } + = _('User') + %td{ style: "#{default_style}color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + #{sanitize_name(@user.name)} (#{@user.username}) + %tr + %td{ style: "#{default_style}border-top:1px solid #ededed;" } = _('IP Address') %td{ style: "#{default_style}color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } %span.muted{ style: "color:#333333;text-decoration:none;" } diff --git a/app/views/notify/unknown_sign_in_email.text.haml b/app/views/notify/unknown_sign_in_email.text.haml index f3e318f0d15..fbe35c502da 100644 --- a/app/views/notify/unknown_sign_in_email.text.haml +++ b/app/views/notify/unknown_sign_in_email.text.haml @@ -1,4 +1,4 @@ -= _('Hi %{username}!') % { username: sanitize_name(@user.name) } += _('Hi %{user_name} (%{user_username})!') % { user_name: sanitize_name(@user.name), user_username: @user.username } = _('A sign-in to your account has been made from the following IP address: %{ip}') % { ip: @ip } diff --git a/app/workers/gitlab_service_ping_worker.rb b/app/workers/gitlab_service_ping_worker.rb index b02e7318585..53a4361fb48 100644 --- a/app/workers/gitlab_service_ping_worker.rb +++ b/app/workers/gitlab_service_ping_worker.rb @@ -52,3 +52,5 @@ class GitlabServicePingWorker # rubocop:disable Scalability/IdempotentWorker nil end end + +GitlabServicePingWorker.prepend_mod diff --git a/db/migrate/20230303144424_unique_index_on_catalog_resources_project.rb b/db/migrate/20230303144424_unique_index_on_catalog_resources_project.rb new file mode 100644 index 00000000000..6ac2e72616a --- /dev/null +++ b/db/migrate/20230303144424_unique_index_on_catalog_resources_project.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class UniqueIndexOnCatalogResourcesProject < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + INDEX_NAME = 'index_catalog_resources_on_project_id' + + def up + remove_concurrent_index :catalog_resources, :project_id, name: INDEX_NAME + add_concurrent_index :catalog_resources, :project_id, unique: true, name: INDEX_NAME + end + + def down + remove_concurrent_index :catalog_resources, :project_id, name: INDEX_NAME + add_concurrent_index :catalog_resources, :project_id, name: INDEX_NAME + end +end diff --git a/db/schema_migrations/20230303144424 b/db/schema_migrations/20230303144424 new file mode 100644 index 00000000000..f0684710508 --- /dev/null +++ b/db/schema_migrations/20230303144424 @@ -0,0 +1 @@ +c8ea35db8903fb627b3ed68b9d9a1e78c34ab40a5aed6fe19e329d13e371f652
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index ceddf2a82a5..64beb9476f9 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -29397,7 +29397,7 @@ CREATE INDEX index_bulk_import_failures_on_correlation_id_value ON bulk_import_f CREATE INDEX index_bulk_imports_on_user_id ON bulk_imports USING btree (user_id); -CREATE INDEX index_catalog_resources_on_project_id ON catalog_resources USING btree (project_id); +CREATE UNIQUE INDEX index_catalog_resources_on_project_id ON catalog_resources USING btree (project_id); CREATE INDEX index_chat_names_on_team_id_and_chat_id ON chat_names USING btree (team_id, chat_id); diff --git a/doc/security/user_file_uploads.md b/doc/security/user_file_uploads.md index db2948a8bd5..80f4b1a8a2a 100644 --- a/doc/security/user_file_uploads.md +++ b/doc/security/user_file_uploads.md @@ -45,6 +45,30 @@ To configure authentication settings for all media files: 1. Scroll to **Project visibility** and select **Require authentication to view media files**. You cannot select this option for projects with **Public** visibility. +## Delete uploaded files + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92791) in GitLab 15.3. + +You should delete an uploaded file when that file contains sensitive or confidential information. When you have deleted that file, users cannot access the file and the direct URL returns a 404 error. + +Project Owners and Maintainers can use the [interactive GraphiQL explorer](../api/graphql/index.md#graphiql) to access a [GraphQL endpoint](../api/graphql/reference/index.md#mutationuploaddelete) and delete an uploaded file. + +For example: + +```graphql +mutation{ + uploadDelete(input: { projectPath: "<path/to/project>", secret: "<32-character-id>" , filename: "<filename>" }) { + upload { + id + size + path + } + errors + } +} +``` + +Project members that do not have the Owner or Maintainer role cannot access this GraphQL endpoint. <!-- ## Troubleshooting Include any troubleshooting steps that you can foresee. If you know beforehand what issues diff --git a/doc/user/clusters/agent/gitops/flux_tutorial.md b/doc/user/clusters/agent/gitops/flux_tutorial.md index b3d71e66dea..3c30e466110 100644 --- a/doc/user/clusters/agent/gitops/flux_tutorial.md +++ b/doc/user/clusters/agent/gitops/flux_tutorial.md @@ -112,7 +112,7 @@ spec: Congratulations! You now have a manifest repository, a deploy token, and a secret generated directly on your cluster. -### Configure Flux to sync your manifests +## Configure Flux to sync your manifests Next, tell `flux-config` to sync with the `web-app-manifests` repository. diff --git a/lib/gitlab/analytics/cycle_analytics/request_params.rb b/lib/gitlab/analytics/cycle_analytics/request_params.rb index 6452739afd3..3e70d64fea6 100644 --- a/lib/gitlab/analytics/cycle_analytics/request_params.rb +++ b/lib/gitlab/analytics/cycle_analytics/request_params.rb @@ -92,7 +92,7 @@ module Gitlab attrs[:direction] = direction if direction.present? attrs[:stage] = stage_data_attributes.to_json if stage_id.present? attrs[:namespace] = namespace_attributes - attrs[:enable_tasks_by_type_chart] = false + attrs[:enable_tasks_by_type_chart] = 'false' attrs[:default_stages] = Gitlab::Analytics::CycleAnalytics::DefaultStages.all.map do |stage_params| ::Analytics::CycleAnalytics::StagePresenter.new(stage_params) end.to_json @@ -141,13 +141,11 @@ module Gitlab end def namespace_attributes - container = project || namespace - return {} unless container + return {} unless project { - name: container.name, - full_path: container.full_path, - avatar_url: container.avatar_url + name: project.name, + full_path: project.full_path } end diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake index 5a67e6cfa8c..375005c678d 100644 --- a/lib/tasks/gitlab/db.rake +++ b/lib/tasks/gitlab/db.rake @@ -476,6 +476,7 @@ namespace :gitlab do .reject(&:abstract_class) .reject { |c| c.name =~ /^(?:EE::)?Gitlab::(?:BackgroundMigration|DatabaseImporters)::/ } .reject { |c| c.name =~ /^HABTM_/ } + .reject { |c| c < Gitlab::Database::Migration[1.0]::MigrationRecord } .each { |c| classes[c.table_name] << c.name if classes.has_key?(c.table_name) } sources.each do |source_name| diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b25f604a78f..bd2521d60c4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -20923,6 +20923,9 @@ msgstr "" msgid "Helps reduce request volume for protected paths." msgstr "" +msgid "Hi %{user_name} (%{user_username})!" +msgstr "" + msgid "Hi %{username}!" msgstr "" @@ -32873,6 +32876,9 @@ msgstr "" msgid "ProductAnalytics|Viewport" msgstr "" +msgid "ProductAnalytics|Visualization Designer" +msgstr "" + msgid "ProductAnalytics|Visualization Type" msgstr "" diff --git a/qa/Gemfile b/qa/Gemfile index 3201d6d1b17..28eb54c8160 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -gem 'gitlab-qa', '~> 9', '>= 9.1.0', require: 'gitlab/qa' +gem 'gitlab-qa', '~> 9', '>= 9.1.1', require: 'gitlab/qa' gem 'activesupport', '~> 6.1.7.2' # This should stay in sync with the root's Gemfile gem 'allure-rspec', '~> 2.20.0' gem 'capybara', '~> 3.38.0' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 52ac2d75708..a5d8b1b0f35 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -102,11 +102,12 @@ GEM gitlab (4.18.0) httparty (~> 0.18) terminal-table (>= 1.5.1) - gitlab-qa (9.1.0) + gitlab-qa (9.1.1) activesupport (~> 6.1) gitlab (~> 4.18.0) http (~> 5.0) nokogiri (~> 1.10) + parallel (>= 1, < 2) rainbow (>= 3, < 4) table_print (= 1.5.7) zeitwerk (>= 2, < 3) @@ -317,7 +318,7 @@ DEPENDENCIES faraday-retry (~> 2.1) fog-core (= 2.1.0) fog-google (~> 1.19) - gitlab-qa (~> 9, >= 9.1.0) + gitlab-qa (~> 9, >= 9.1.1) influxdb-client (~> 2.9) knapsack (~> 4.0) nokogiri (~> 1.14, >= 1.14.2) diff --git a/spec/frontend/contributors/component/contributors_spec.js b/spec/frontend/contributors/component/contributors_spec.js index 03b1e977548..9f5c7e11644 100644 --- a/spec/frontend/contributors/component/contributors_spec.js +++ b/spec/frontend/contributors/component/contributors_spec.js @@ -1,5 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; -import Vue, { nextTick } from 'vue'; +import { nextTick } from 'vue'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import ContributorsCharts from '~/contributors/components/contributors.vue'; import { createStore } from '~/contributors/stores'; @@ -16,7 +16,6 @@ jest.mock('~/lib/utils/url_utility', () => ({ let wrapper; let mock; let store; -const Component = Vue.extend(ContributorsCharts); const endpoint = 'contributors/-/graphs'; const branch = 'main'; const chartData = [ @@ -32,7 +31,7 @@ function factory() { mock.onGet().reply(HTTP_STATUS_OK, chartData); store = createStore(); - wrapper = mountExtended(Component, { + wrapper = mountExtended(ContributorsCharts, { propsData: { endpoint, branch, diff --git a/spec/frontend/issues/new/components/title_suggestions_spec.js b/spec/frontend/issues/new/components/title_suggestions_spec.js index 1cd6576967a..343bdbba301 100644 --- a/spec/frontend/issues/new/components/title_suggestions_spec.js +++ b/spec/frontend/issues/new/components/title_suggestions_spec.js @@ -1,106 +1,95 @@ import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import TitleSuggestions from '~/issues/new/components/title_suggestions.vue'; import TitleSuggestionsItem from '~/issues/new/components/title_suggestions_item.vue'; +import getIssueSuggestionsQuery from '~/issues/new/queries/issues.query.graphql'; +import { mockIssueSuggestionResponse } from '../mock_data'; + +Vue.use(VueApollo); + +const MOCK_PROJECT_PATH = 'project'; +const MOCK_ISSUES_COUNT = mockIssueSuggestionResponse.data.project.issues.edges.length; describe('Issue title suggestions component', () => { let wrapper; + let mockApollo; + + function createComponent({ + search = 'search', + queryResponse = jest.fn().mockResolvedValue(mockIssueSuggestionResponse), + } = {}) { + mockApollo = createMockApollo([[getIssueSuggestionsQuery, queryResponse]]); - function createComponent(search = 'search') { wrapper = shallowMount(TitleSuggestions, { propsData: { search, - projectPath: 'project', + projectPath: MOCK_PROJECT_PATH, }, + apolloProvider: mockApollo, }); } - beforeEach(() => { - createComponent(); - }); + const waitForDebounce = () => { + jest.runOnlyPendingTimers(); + return waitForPromises(); + }; afterEach(() => { - wrapper.destroy(); + mockApollo = null; }); it('does not render with empty search', async () => { - wrapper.setProps({ search: '' }); + createComponent({ search: '' }); + await waitForDebounce(); - await nextTick(); expect(wrapper.isVisible()).toBe(false); }); - describe('with data', () => { - let data; - - beforeEach(() => { - data = { issues: [{ id: 1 }, { id: 2 }] }; - }); - - it('renders component', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData(data); - - await nextTick(); - expect(wrapper.findAll('li').length).toBe(data.issues.length); - }); + it('does not render when loading', () => { + createComponent(); + expect(wrapper.isVisible()).toBe(false); + }); - it('does not render with empty search', async () => { - wrapper.setProps({ search: '' }); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData(data); + it('does not render with empty issues data', async () => { + const emptyIssuesResponse = { + data: { + project: { + id: 'gid://gitlab/Project/1', + issues: { + edges: [], + }, + }, + }, + }; - await nextTick(); - expect(wrapper.isVisible()).toBe(false); - }); + createComponent({ queryResponse: jest.fn().mockResolvedValue(emptyIssuesResponse) }); + await waitForDebounce(); - it('does not render when loading', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - ...data, - loading: 1, - }); + expect(wrapper.isVisible()).toBe(false); + }); - await nextTick(); - expect(wrapper.isVisible()).toBe(false); + describe('with data', () => { + beforeEach(async () => { + createComponent(); + await waitForDebounce(); }); - it('does not render with empty issues data', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ issues: [] }); - - await nextTick(); - expect(wrapper.isVisible()).toBe(false); + it('renders component', () => { + expect(wrapper.findAll('li').length).toBe(MOCK_ISSUES_COUNT); }); - it('renders list of issues', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData(data); - - await nextTick(); - expect(wrapper.findAllComponents(TitleSuggestionsItem).length).toBe(2); + it('renders list of issues', () => { + expect(wrapper.findAllComponents(TitleSuggestionsItem).length).toBe(MOCK_ISSUES_COUNT); }); - it('adds margin class to first item', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData(data); - - await nextTick(); + it('adds margin class to first item', () => { expect(wrapper.findAll('li').at(0).classes()).toContain('gl-mb-3'); }); - it('does not add margin class to last item', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData(data); - - await nextTick(); + it('does not add margin class to last item', () => { expect(wrapper.findAll('li').at(1).classes()).not.toContain('gl-mb-3'); }); }); diff --git a/spec/frontend/issues/new/mock_data.js b/spec/frontend/issues/new/mock_data.js index 74b569d9833..0d2a388cd86 100644 --- a/spec/frontend/issues/new/mock_data.js +++ b/spec/frontend/issues/new/mock_data.js @@ -26,3 +26,67 @@ export default () => ({ webUrl: `${TEST_HOST}/author`, }, }); + +export const mockIssueSuggestionResponse = { + data: { + project: { + id: 'gid://gitlab/Project/278964', + issues: { + edges: [ + { + node: { + id: 'gid://gitlab/Issue/123725957', + iid: '696', + title: 'Remove unused MR widget extension expand success, failed, warning events', + confidential: false, + userNotesCount: 16, + upvotes: 0, + webUrl: 'https://gitlab.com/gitlab-org/gitlab/-/issues/696', + state: 'opened', + closedAt: null, + createdAt: '2023-02-15T12:29:59Z', + updatedAt: '2023-03-01T19:38:22Z', + author: { + id: 'gid://gitlab/User/325', + name: 'User Name', + username: 'user-name', + avatarUrl: '/uploads/-/system/user/avatar/325/avatar.png', + webUrl: 'https://gitlab.com/user-name', + __typename: 'UserCore', + }, + __typename: 'Issue', + }, + __typename: 'IssueEdge', + }, + { + node: { + id: 'gid://gitlab/Issue/123', + iid: '391', + title: 'Remove unused MR widget extension expand success, failed, warning events', + confidential: false, + userNotesCount: 16, + upvotes: 0, + webUrl: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391', + state: 'opened', + closedAt: null, + createdAt: '2023-02-15T12:29:59Z', + updatedAt: '2023-03-01T19:38:22Z', + author: { + id: 'gid://gitlab/User/2080', + name: 'User Name', + username: 'user-name', + avatarUrl: '/uploads/-/system/user/avatar/2080/avatar.png', + webUrl: 'https://gitlab.com/user-name', + __typename: 'UserCore', + }, + __typename: 'Issue', + }, + __typename: 'IssueEdge', + }, + ], + __typename: 'IssueConnection', + }, + __typename: 'Project', + }, + }, +}; diff --git a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js index 012e2e9c3e2..96b228fd3b2 100644 --- a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js +++ b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js @@ -1,6 +1,5 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; import DropdownField from '~/monitoring/components/variables/dropdown_field.vue'; describe('Custom variable component', () => { @@ -56,11 +55,8 @@ describe('Custom variable component', () => { it('changing dropdown items triggers update', async () => { createShallowWrapper(); - jest.spyOn(wrapper.vm, '$emit'); - findDropdownItems().at(1).vm.$emit('click'); - await nextTick(); - expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary'); + expect(wrapper.emitted('input')).toEqual([['canary']]); }); }); diff --git a/spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js index 51f3d31ec72..5d37692bf90 100644 --- a/spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js +++ b/spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js @@ -1,7 +1,6 @@ -import { GlLoadingIcon } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import NotebookViewer from '~/repository/components/blob_viewers/notebook_viewer.vue'; -import notebookLoader from '~/blob/notebook'; +import Notebook from '~/blob/notebook/notebook_viewer.vue'; jest.mock('~/blob/notebook'); @@ -17,24 +16,11 @@ describe('Notebook Viewer', () => { }); }; - const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findNotebookWrapper = () => wrapper.findByTestId('notebook'); + const findNotebook = () => wrapper.findComponent(Notebook); beforeEach(() => createComponent()); - it('calls the notebook loader', () => { - expect(notebookLoader).toHaveBeenCalledWith({ - el: wrapper.vm.$refs.viewer, - relativeRawPath: ROOT_RELATIVE_PATH, - }); - }); - - it('renders a loading icon component', () => { - expect(findLoadingIcon().props('size')).toBe('lg'); - }); - - it('renders the notebook wrapper', () => { - expect(findNotebookWrapper().exists()).toBe(true); - expect(findNotebookWrapper().attributes('data-endpoint')).toBe(DEFAULT_BLOB_DATA.rawPath); + it('renders a Notebook component', () => { + expect(findNotebook().props('endpoint')).toBe(DEFAULT_BLOB_DATA.rawPath); }); }); diff --git a/spec/lib/gitlab/analytics/cycle_analytics/request_params_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/request_params_spec.rb index 6f2f1cd9124..3c171d684d6 100644 --- a/spec/lib/gitlab/analytics/cycle_analytics/request_params_spec.rb +++ b/spec/lib/gitlab/analytics/cycle_analytics/request_params_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RequestParams, feature_categor it_behaves_like 'unlicensed cycle analytics request params' do let_it_be(:user) { create(:user) } let_it_be(:root_group) { create(:group) } - let_it_be(:project) { create(:project, group: root_group) } + let_it_be_with_refind(:project) { create(:project, group: root_group) } let(:namespace) { project.project_namespace } @@ -17,8 +17,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RequestParams, feature_categor expect(attributes).to match(hash_including({ namespace: { name: project.name, - full_path: project.full_path, - avatar_url: project.avatar_url + full_path: project.full_path } })) end diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb index 32df477fdfc..c796801fdf9 100644 --- a/spec/mailers/emails/profile_spec.rb +++ b/spec/mailers/emails/profile_spec.rb @@ -429,11 +429,16 @@ RSpec.describe Emails::Profile do is_expected.to have_subject "#{Gitlab.config.gitlab.host} sign-in from new location" end + it 'mentions the username' do + is_expected.to have_body_text user.name + is_expected.to have_body_text user.username + end + it 'mentions the new sign-in IP' do is_expected.to have_body_text ip end - it 'mentioned the time' do + it 'mentions the time' do is_expected.to have_body_text current_time.strftime('%Y-%m-%d %H:%M:%S %Z') end diff --git a/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb b/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb index 9f6031af444..f1ffddf6507 100644 --- a/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb +++ b/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb @@ -122,6 +122,6 @@ RSpec.shared_examples 'unlicensed cycle analytics request params' do describe 'enable_tasks_by_type_chart data attribute' do subject(:value) { described_class.new(params).to_data_attributes[:enable_tasks_by_type_chart] } - it { is_expected.to eq(false) } + it { is_expected.to eq('false') } end end diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb index f43106959b0..0f13840ae01 100644 --- a/spec/tasks/gitlab/db_rake_spec.rb +++ b/spec/tasks/gitlab/db_rake_spec.rb @@ -402,6 +402,12 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor end end + let(:migration_table_class) do + Class.new(Gitlab::Database::Migration[1.0]::MigrationRecord) do + self.table_name = 'table1' + end + end + let(:view_class) do Class.new(ApplicationRecord) do self.table_name = 'view1' @@ -427,12 +433,13 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor before do stub_const('TableClass1', table_class) + stub_const('MIgrationTableClass1', migration_table_class) stub_const('ViewClass1', view_class) File.write(table_file_path, table_metadata.to_yaml) File.write(view_file_path, view_metadata.to_yaml) - allow(model).to receive(:descendants).and_return([table_class, view_class]) + allow(model).to receive(:descendants).and_return([table_class, migration_table_class, view_class]) end it 'appends new classes to the dictionary' do |