diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index 9713e79f525..cc13f279c4d 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -20,6 +20,10 @@ module CacheMarkdownField false end + def can_cache_field?(field) + true + end + # Returns the default Banzai render context for the cached markdown field. def banzai_render_context(field) raise ArgumentError.new("Unknown field: #{field.inspect}") unless @@ -38,17 +42,23 @@ module CacheMarkdownField context end - # Update every column in a row if any one is invalidated, as we only store + def rendered_field_content(markdown_field) + return unless can_cache_field?(markdown_field) + + options = { skip_project_check: skip_project_check? } + Banzai::Renderer.cacheless_render_field(self, markdown_field, options) + end + + # Update every applicable column in a row if any one is invalidated, as we only store # one version per row def refresh_markdown_cache - options = { skip_project_check: skip_project_check? } - updates = cached_markdown_fields.markdown_fields.map do |markdown_field| [ cached_markdown_fields.html_field(markdown_field), - Banzai::Renderer.cacheless_render_field(self, markdown_field, options) + rendered_field_content(markdown_field) ] end.to_h + updates['cached_markdown_version'] = latest_cached_markdown_version updates.each { |field, data| write_markdown_field(field, data) } diff --git a/app/models/group.rb b/app/models/group.rb index a5337f19b38..d6a4af5af15 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -406,11 +406,15 @@ class Group < Namespace end def ci_variables_for(ref, project) - list_of_ids = [self] + ancestors - variables = Ci::GroupVariable.where(group: list_of_ids) - variables = variables.unprotected unless project.protected_for?(ref) - variables = variables.group_by(&:group_id) - list_of_ids.reverse.flat_map { |group| variables[group.id] }.compact + cache_key = "ci_variables_for:group:#{self&.id}:project:#{project&.id}:ref:#{ref}" + + ::Gitlab::SafeRequestStore.fetch(cache_key) do + list_of_ids = [self] + ancestors + variables = Ci::GroupVariable.where(group: list_of_ids) + variables = variables.unprotected unless project.protected_for?(ref) + variables = variables.group_by(&:group_id) + list_of_ids.reverse.flat_map { |group| variables[group.id] }.compact + end end def group_member(user) diff --git a/app/models/project.rb b/app/models/project.rb index 5ec43de21fe..f72e777c004 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1963,6 +1963,14 @@ class Project < ApplicationRecord end def ci_variables_for(ref:, environment: nil) + cache_key = "ci_variables_for:project:#{self&.id}:ref:#{ref}:environment:#{environment}" + + ::Gitlab::SafeRequestStore.fetch(cache_key) do + uncached_ci_variables_for(ref: ref, environment: environment) + end + end + + def uncached_ci_variables_for(ref:, environment: nil) result = if protected_for?(ref) variables else diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 8bba79bd944..233834dbaf9 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -301,6 +301,10 @@ class Snippet < ApplicationRecord repository.update!(shard_name: repository_storage, disk_path: disk_path) end + def can_cache_field?(field) + field != :content || MarkupHelper.gitlab_markdown?(file_name) + end + class << self # Searches for snippets with a matching title or file name. # diff --git a/changelogs/unreleased/207976-stop-markdown-caching-of-non-markdown-snippet-content.yml b/changelogs/unreleased/207976-stop-markdown-caching-of-non-markdown-snippet-content.yml new file mode 100644 index 00000000000..dd249140092 --- /dev/null +++ b/changelogs/unreleased/207976-stop-markdown-caching-of-non-markdown-snippet-content.yml @@ -0,0 +1,5 @@ +--- +title: Fix Snippet content incorrectly caching +merge_request: 25985 +author: +type: fixed diff --git a/changelogs/unreleased/fix-dependency-proxy-link.yml b/changelogs/unreleased/fix-dependency-proxy-link.yml new file mode 100644 index 00000000000..547d0334d1e --- /dev/null +++ b/changelogs/unreleased/fix-dependency-proxy-link.yml @@ -0,0 +1,5 @@ +--- +title: Add link to dependency proxy docs on the dependency proxy page +merge_request: 26092 +author: +type: changed diff --git a/changelogs/unreleased/mk-hide-secondary-only-setting.yml b/changelogs/unreleased/mk-hide-secondary-only-setting.yml new file mode 100644 index 00000000000..49107c23b16 --- /dev/null +++ b/changelogs/unreleased/mk-hide-secondary-only-setting.yml @@ -0,0 +1,5 @@ +--- +title: 'Geo: Show secondary-only setting on only on secondaries' +merge_request: 26029 +author: +type: fixed diff --git a/changelogs/unreleased/sh-cache-ci-variables.yml b/changelogs/unreleased/sh-cache-ci-variables.yml new file mode 100644 index 00000000000..8534af37808 --- /dev/null +++ b/changelogs/unreleased/sh-cache-ci-variables.yml @@ -0,0 +1,5 @@ +--- +title: Memoize loading of CI variables +merge_request: 26147 +author: +type: performance diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md index 32eca7029da..b13cb36f9d0 100644 --- a/doc/user/clusters/applications.md +++ b/doc/user/clusters/applications.md @@ -9,10 +9,6 @@ and [deployments](../../ci/environments.md) when using [Auto DevOps](../../topic You can install them after you [create a cluster](../project/clusters/add_remove_clusters.md). -Interested in contributing a new GitLab managed app? Visit the -[development guidelines page](../../development/kubernetes.md#gitlab-managed-apps) -to get started. - ## Installing applications Applications managed by GitLab will be installed onto the `gitlab-managed-apps` namespace. @@ -466,6 +462,12 @@ The chart will deploy 5 Elasticsearch nodes: 2 masters, 2 data and 1 client node with resource requests totalling 0.125 CPU and 4.5GB RAM. Each data node requests 1.5GB of memory, which makes it incompatible with clusters of `f1-micro` and `g1-small` instance types. +### Future apps + +Interested in contributing a new GitLab managed app? Visit the +[development guidelines page](../../development/kubernetes.md#gitlab-managed-apps) +to get started. + ## Install using GitLab CI (alpha) > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20822) in GitLab 12.6. diff --git a/jest.config.js b/jest.config.js index fe05141dfd7..ff4fff68107 100644 --- a/jest.config.js +++ b/jest.config.js @@ -82,7 +82,7 @@ module.exports = { '^.+\\.js$': 'babel-jest', '^.+\\.vue$': 'vue-jest', }, - transformIgnorePatterns: ['node_modules/(?!(@gitlab/ui)/)'], + transformIgnorePatterns: ['node_modules/(?!(@gitlab/ui|bootstrap-vue)/)'], timers: 'fake', testEnvironment: '/spec/frontend/environment.js', testEnvironmentOptions: { diff --git a/package.json b/package.json index b8304fee6e1..852f9cfcc87 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,8 @@ "@babel/plugin-syntax-import-meta": "^7.8.3", "@babel/preset-env": "^7.8.4", "@gitlab/at.js": "^1.5.5", - "@gitlab/svgs": "^1.103.0", - "@gitlab/ui": "^9.17.0", + "@gitlab/svgs": "^1.104.0", + "@gitlab/ui": "^9.18.0", "@gitlab/visual-review-tools": "1.5.1", "@sentry/browser": "^5.10.2", "@sourcegraph/code-host-integration": "0.0.30", diff --git a/spec/javascripts/blob_edit/blob_bundle_spec.js b/spec/frontend/blob_edit/blob_bundle_spec.js similarity index 93% rename from spec/javascripts/blob_edit/blob_bundle_spec.js rename to spec/frontend/blob_edit/blob_bundle_spec.js index 06c6a603155..be438781850 100644 --- a/spec/javascripts/blob_edit/blob_bundle_spec.js +++ b/spec/frontend/blob_edit/blob_bundle_spec.js @@ -1,9 +1,10 @@ import $ from 'jquery'; import blobBundle from '~/blob_edit/blob_bundle'; +jest.mock('~/blob_edit/edit_blob'); + describe('BlobBundle', () => { beforeEach(() => { - spyOnDependency(blobBundle, 'EditBlob').and.stub(); setFixtures(`
diff --git a/spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js b/spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js new file mode 100644 index 00000000000..5f69d761fdf --- /dev/null +++ b/spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js @@ -0,0 +1,121 @@ +export const defaultProps = { + endpoint: '/foo/bar/issues/1/related_issues', + currentNamespacePath: 'foo', + currentProjectPath: 'bar', +}; + +export const issuable1 = { + id: 200, + epicIssueId: 1, + confidential: false, + reference: 'foo/bar#123', + displayReference: '#123', + title: 'some title', + path: '/foo/bar/issues/123', + relationPath: '/foo/bar/issues/123/relation', + state: 'opened', + linkType: 'relates_to', + dueDate: '2010-11-22', + weight: 5, +}; + +export const issuable2 = { + id: 201, + epicIssueId: 2, + confidential: false, + reference: 'foo/bar#124', + displayReference: '#124', + title: 'some other thing', + path: '/foo/bar/issues/124', + relationPath: '/foo/bar/issues/124/relation', + state: 'opened', + linkType: 'blocks', +}; + +export const issuable3 = { + id: 202, + epicIssueId: 3, + confidential: false, + reference: 'foo/bar#125', + displayReference: '#125', + title: 'some other other thing', + path: '/foo/bar/issues/125', + relationPath: '/foo/bar/issues/125/relation', + state: 'opened', + linkType: 'is_blocked_by', +}; + +export const issuable4 = { + id: 203, + epicIssueId: 4, + confidential: false, + reference: 'foo/bar#126', + displayReference: '#126', + title: 'some other other other thing', + path: '/foo/bar/issues/126', + relationPath: '/foo/bar/issues/126/relation', + state: 'opened', +}; + +export const issuable5 = { + id: 204, + epicIssueId: 5, + confidential: false, + reference: 'foo/bar#127', + displayReference: '#127', + title: 'some other other other thing', + path: '/foo/bar/issues/127', + relationPath: '/foo/bar/issues/127/relation', + state: 'opened', +}; + +export const defaultMilestone = { + id: 1, + state: 'active', + title: 'Milestone title', + start_date: '2018-01-01', + due_date: '2019-12-31', +}; + +export const defaultAssignees = [ + { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: `${gl.TEST_HOST}`, + web_url: `${gl.TEST_HOST}/root`, + status_tooltip_html: null, + path: '/root', + }, + { + id: 13, + name: 'Brooks Beatty', + username: 'brynn_champlin', + state: 'active', + avatar_url: `${gl.TEST_HOST}`, + web_url: `${gl.TEST_HOST}/brynn_champlin`, + status_tooltip_html: null, + path: '/brynn_champlin', + }, + { + id: 6, + name: 'Bryce Turcotte', + username: 'melynda', + state: 'active', + avatar_url: `${gl.TEST_HOST}`, + web_url: `${gl.TEST_HOST}/melynda`, + status_tooltip_html: null, + path: '/melynda', + }, + { + id: 20, + name: 'Conchita Eichmann', + username: 'juliana_gulgowski', + state: 'active', + avatar_url: `${gl.TEST_HOST}`, + web_url: `${gl.TEST_HOST}/juliana_gulgowski`, + status_tooltip_html: null, + path: '/juliana_gulgowski', + }, +]; diff --git a/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js b/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js index d3dc169ddab..3c42f0c2aa9 100644 --- a/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js +++ b/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js @@ -1,116 +1 @@ -export const defaultProps = { - endpoint: '/foo/bar/issues/1/related_issues', - currentNamespacePath: 'foo', - currentProjectPath: 'bar', -}; - -export const issuable1 = { - id: 200, - epicIssueId: 1, - confidential: false, - reference: 'foo/bar#123', - displayReference: '#123', - title: 'some title', - path: '/foo/bar/issues/123', - state: 'opened', - linkType: 'relates_to', - dueDate: '2010-11-22', - weight: 5, -}; - -export const issuable2 = { - id: 201, - epicIssueId: 2, - confidential: false, - reference: 'foo/bar#124', - displayReference: '#124', - title: 'some other thing', - path: '/foo/bar/issues/124', - state: 'opened', - linkType: 'blocks', -}; - -export const issuable3 = { - id: 202, - epicIssueId: 3, - confidential: false, - reference: 'foo/bar#125', - displayReference: '#125', - title: 'some other other thing', - path: '/foo/bar/issues/125', - state: 'opened', - linkType: 'is_blocked_by', -}; - -export const issuable4 = { - id: 203, - epicIssueId: 4, - confidential: false, - reference: 'foo/bar#126', - displayReference: '#126', - title: 'some other other other thing', - path: '/foo/bar/issues/126', - state: 'opened', -}; - -export const issuable5 = { - id: 204, - epicIssueId: 5, - confidential: false, - reference: 'foo/bar#127', - displayReference: '#127', - title: 'some other other other thing', - path: '/foo/bar/issues/127', - state: 'opened', -}; - -export const defaultMilestone = { - id: 1, - state: 'active', - title: 'Milestone title', - start_date: '2018-01-01', - due_date: '2019-12-31', -}; - -export const defaultAssignees = [ - { - id: 1, - name: 'Administrator', - username: 'root', - state: 'active', - avatar_url: `${gl.TEST_HOST}`, - web_url: `${gl.TEST_HOST}/root`, - status_tooltip_html: null, - path: '/root', - }, - { - id: 13, - name: 'Brooks Beatty', - username: 'brynn_champlin', - state: 'active', - avatar_url: `${gl.TEST_HOST}`, - web_url: `${gl.TEST_HOST}/brynn_champlin`, - status_tooltip_html: null, - path: '/brynn_champlin', - }, - { - id: 6, - name: 'Bryce Turcotte', - username: 'melynda', - state: 'active', - avatar_url: `${gl.TEST_HOST}`, - web_url: `${gl.TEST_HOST}/melynda`, - status_tooltip_html: null, - path: '/melynda', - }, - { - id: 20, - name: 'Conchita Eichmann', - username: 'juliana_gulgowski', - state: 'active', - avatar_url: `${gl.TEST_HOST}`, - web_url: `${gl.TEST_HOST}/juliana_gulgowski`, - status_tooltip_html: null, - path: '/juliana_gulgowski', - }, -]; +export * from '../../../../frontend/vue_shared/components/issue/related_issuable_mock_data'; diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 06d12c14793..697a9e98505 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -230,6 +230,26 @@ describe CacheMarkdownField, :clean_gitlab_redis_cache do end end end + + describe '#rendered_field_content' do + let(:thing) { klass.new(description: markdown, description_html: nil, cached_markdown_version: cache_version) } + + context 'when a field can be cached' do + it 'returns the html' do + thing.description = updated_markdown + + expect(thing.rendered_field_content(:description)).to eq updated_html + end + end + + context 'when a field cannot be cached' do + it 'returns nil' do + allow(thing).to receive(:can_cache_field?).with(:description).and_return false + + expect(thing.rendered_field_content(:description)).to eq nil + end + end + end end context 'for Active record classes' do diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 3531c695236..b5ed29189fd 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -911,6 +911,16 @@ describe Group do subject { group.ci_variables_for('ref', project) } + it 'memoizes the result by ref', :request_store do + expect(project).to receive(:protected_for?).with('ref').once.and_return(true) + expect(project).to receive(:protected_for?).with('other').once.and_return(false) + + 2.times do + expect(group.ci_variables_for('ref', project)).to contain_exactly(ci_variable, protected_variable) + expect(group.ci_variables_for('other', project)).to contain_exactly(ci_variable) + end + end + shared_examples 'ref is protected' do it 'contains all the variables' do is_expected.to contain_exactly(ci_variable, protected_variable) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e7deae38b46..2b4a832634f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2930,6 +2930,19 @@ describe Project do end end + it 'memoizes the result by ref and environment', :request_store do + scoped_variable = create(:ci_variable, value: 'secret', project: project, environment_scope: 'scoped') + + expect(project).to receive(:protected_for?).with('ref').once.and_return(true) + expect(project).to receive(:protected_for?).with('other').twice.and_return(false) + + 2.times do + expect(project.reload.ci_variables_for(ref: 'ref', environment: 'production')).to contain_exactly(ci_variable, protected_variable) + expect(project.reload.ci_variables_for(ref: 'other')).to contain_exactly(ci_variable) + expect(project.reload.ci_variables_for(ref: 'other', environment: 'scoped')).to contain_exactly(ci_variable, scoped_variable) + end + end + context 'when the ref is not protected' do before do allow(project).to receive(:protected_for?).with('ref').and_return(false) diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 1265b95736d..cb7b9961880 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -632,4 +632,26 @@ describe Snippet do end end end + + describe '#can_cache_field?' do + using RSpec::Parameterized::TableSyntax + + let(:snippet) { create(:snippet, file_name: file_name) } + + subject { snippet.can_cache_field?(field) } + + where(:field, :file_name, :result) do + :title | nil | true + :title | 'foo.bar' | true + :description | nil | true + :description | 'foo.bar' | true + :content | nil | false + :content | 'bar.foo' | false + :content | 'markdown.md' | true + end + + with_them do + it { is_expected.to eq result } + end + end end diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore old mode 100755 new mode 100644 diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore old mode 100755 new mode 100644 diff --git a/yarn.lock b/yarn.lock index 07e119260ea..48e42c00eba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -796,15 +796,15 @@ dependencies: vue-eslint-parser "^7.0.0" -"@gitlab/svgs@^1.103.0": - version "1.103.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.103.0.tgz#fb7136df9f5df3f53685740daf0ebf565b735608" - integrity sha512-+Bt+a8ln9KSz3QxB2P57ub71/eiEnKXJQSheTagYh2KlT6Glzh7XeLjyc3W6uGWBOONqsp96H/tYEa9dmDnLqQ== +"@gitlab/svgs@^1.104.0": + version "1.104.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.104.0.tgz#ebbf99788d74b7224f116f1c0040fa0c90034d99" + integrity sha512-lWg/EzxFdbx4YIdDWB2p5ag6Cna78AYGET8nXQYXYwd21/U3wKXKL7vsGR4kOxe1goA9ZAYG9eY+MK7cf+X2cA== -"@gitlab/ui@^9.17.0": - version "9.17.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.17.0.tgz#2c2e0412b8293889ab3c2e8f9d7ae1e4b9b508b7" - integrity sha512-6Ph3eE7ygUc6A72J6+mfce/MQSkyN4Rl2moSIZeMcAnHhIXBn6qoj4+Pq+jTWXYFqy3/ow6Iddle63Flj1dnkA== +"@gitlab/ui@^9.18.0": + version "9.18.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.18.0.tgz#3f4f09e8b7791d0bd01f90920ac6e4477e977ab9" + integrity sha512-GdQFuH4fPU+/wvX+UL5wSYN6VB2EuIXHBjjrdLHeUhEWuXrtrbBBmtoVEfmii0XcX5iUccgw55orz8Dmq3DlGg== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0" @@ -819,6 +819,7 @@ url-search-params-polyfill "^5.0.0" vue "^2.6.10" vue-loader "^15.4.2" + vue-runtime-helpers "^1.1.2" "@gitlab/visual-review-tools@1.5.1": version "1.5.1" @@ -11849,6 +11850,11 @@ vue-router@^3.0.2: resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be" integrity sha512-opKtsxjp9eOcFWdp6xLQPLmRGgfM932Tl56U9chYTnoWqKxQ8M20N7AkdEbM5beUh6wICoFGYugAX9vQjyJLFg== +vue-runtime-helpers@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vue-runtime-helpers/-/vue-runtime-helpers-1.1.2.tgz#446b7b820888ab0c5264d2c3a32468e72e4100f3" + integrity sha512-pZfGp+PW/IXEOyETE09xQHR1CKkR9HfHZdnMD/FVLUNI+HxYTa82evx5WrF6Kz4s82qtqHvMZ8MZpbk2zT2E1Q== + vue-style-loader@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.0.tgz#7588bd778e2c9f8d87bfc3c5a4a039638da7a863"