diff options
46 files changed, 418 insertions, 129 deletions
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 9e376a52702..fb6f5dc73b8 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -18,8 +18,6 @@ import Icon from '~/vue_shared/components/icon.vue'; import { getParameterValues, mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; import invalidUrl from '~/lib/utils/invalid_url'; import DateTimePicker from './date_time_picker/date_time_picker.vue'; -import MonitorTimeSeriesChart from './charts/time_series.vue'; -import MonitorSingleStatChart from './charts/single_stat.vue'; import GraphGroup from './graph_group.vue'; import EmptyState from './empty_state.vue'; import TrackEventDirective from '~/vue_shared/directives/track_event'; @@ -28,8 +26,6 @@ import { getTimeDiff, isValidDate, getAddMetricTrackingOptions } from '../utils' export default { components: { VueDraggable, - MonitorTimeSeriesChart, - MonitorSingleStatChart, PanelType, GraphGroup, EmptyState, diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue index 581b2093d44..a5c933a0071 100644 --- a/app/assets/javascripts/monitoring/components/embed.vue +++ b/app/assets/javascripts/monitoring/components/embed.vue @@ -1,8 +1,8 @@ <script> import { mapActions, mapState } from 'vuex'; import { getParameterValues, removeParams } from '~/lib/utils/url_utility'; +import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; import GraphGroup from './graph_group.vue'; -import MonitorTimeSeriesChart from './charts/time_series.vue'; import { sidebarAnimationDuration } from '../constants'; import { getTimeDiff } from '../utils'; @@ -11,7 +11,7 @@ let sidebarMutationObserver; export default { components: { GraphGroup, - MonitorTimeSeriesChart, + PanelType, }, props: { dashboardUrl: { @@ -92,16 +92,13 @@ export default { <template> <div class="metrics-embed" :class="{ 'd-inline-flex col-lg-6 p-0': isSingleChart }"> <div v-if="charts.length" class="row w-100 m-n2 pb-4"> - <monitor-time-series-chart - v-for="graphData in charts" - :key="graphData.title" + <panel-type + v-for="(graphData, graphIndex) in charts" + :key="`panel-type-${graphIndex}`" class="w-100" + clipboard-text="" :graph-data="graphData" - :container-width="elWidth" :group-id="dashboardUrl" - :project-path="null" - :show-border="true" - :single-embed="isSingleChart" /> </div> </div> diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue index fab1dd0f981..080fe6f2b4b 100644 --- a/app/assets/javascripts/monitoring/components/panel_type.vue +++ b/app/assets/javascripts/monitoring/components/panel_type.vue @@ -47,6 +47,11 @@ export default { required: false, default: '', }, + groupId: { + type: String, + required: false, + default: 'panel-type-chart', + }, }, computed: { ...mapState('monitoringDashboard', ['deploymentData', 'projectPath']), @@ -117,7 +122,7 @@ export default { :deployment-data="deploymentData" :project-path="projectPath" :thresholds="getGraphAlertValues(graphData.metrics)" - group-id="panel-type-chart" + :group-id="groupId" > <div class="d-flex align-items-center"> <alert-widget diff --git a/app/assets/javascripts/pages/snippets/show/index.js b/app/assets/javascripts/pages/snippets/show/index.js index 5efcc901fde..26936110402 100644 --- a/app/assets/javascripts/pages/snippets/show/index.js +++ b/app/assets/javascripts/pages/snippets/show/index.js @@ -5,11 +5,9 @@ import initNotes from '~/init_notes'; import snippetEmbed from '~/snippet/snippet_embed'; document.addEventListener('DOMContentLoaded', () => { - if (!gon.features.snippetsVue) { - new LineHighlighter(); // eslint-disable-line no-new - new BlobViewer(); // eslint-disable-line no-new - initNotes(); - new ZenMode(); // eslint-disable-line no-new - snippetEmbed(); - } + new LineHighlighter(); // eslint-disable-line no-new + new BlobViewer(); // eslint-disable-line no-new + initNotes(); + new ZenMode(); // eslint-disable-line no-new + snippetEmbed(); }); diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index 42f9c0522a3..5d458a229f2 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -28,6 +28,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic positions = @merge_request.note_positions_for_paths(diffs.diff_file_paths, current_user) diffs.unfold_diff_files(positions.unfoldable) + diffs.write_cache options = { merge_request: @merge_request, diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb index 2ea1aea1f51..c368e6c8ac7 100644 --- a/app/mailers/emails/profile.rb +++ b/app/mailers/emails/profile.rb @@ -34,3 +34,5 @@ module Emails # rubocop: enable CodeReuse/ActiveRecord end end + +Emails::Profile.prepend_if_ee('EE::Emails::Profile') diff --git a/app/models/ci/legacy_stage.rb b/app/models/ci/legacy_stage.rb index 2fd369c9aff..0a67a652e22 100644 --- a/app/models/ci/legacy_stage.rb +++ b/app/models/ci/legacy_stage.rb @@ -5,6 +5,7 @@ module Ci # We should migrate this object to actual database record in the future class LegacyStage include StaticModel + include Presentable attr_reader :pipeline, :name diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 780bbe75485..f4dfd9128fa 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -27,8 +27,6 @@ class MergeRequest < ApplicationRecord SORTING_PREFERENCE_FIELD = :merge_requests_sort - prepend_if_ee('::EE::MergeRequest') # rubocop: disable Cop/InjectEnterpriseEditionModule - belongs_to :target_project, class_name: "Project" belongs_to :source_project, class_name: "Project" belongs_to :merge_user, class_name: "User" @@ -1531,3 +1529,5 @@ class MergeRequest < ApplicationRecord Gitlab::EtagCaching::Store.new.touch(key) end end + +MergeRequest.prepend_if_ee('::EE::MergeRequest') diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index 7ae431eaad7..770fc2b5c8f 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -70,3 +70,5 @@ class PersonalAccessToken < ApplicationRecord "gitlab:personal_access_token:#{user_id}" end end + +PersonalAccessToken.prepend_if_ee('EE::PersonalAccessToken') diff --git a/app/presenters/ci/legacy_stage_presenter.rb b/app/presenters/ci/legacy_stage_presenter.rb new file mode 100644 index 00000000000..2a60b1280da --- /dev/null +++ b/app/presenters/ci/legacy_stage_presenter.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Ci + class LegacyStagePresenter < Gitlab::View::Presenter::Delegated + presents :legacy_stage + + def latest_ordered_statuses + preload_statuses(legacy_stage.statuses.latest_ordered) + end + + def retried_ordered_statuses + preload_statuses(legacy_stage.statuses.retried_ordered) + end + + private + + def preload_statuses(statuses) + statuses.tap do |statuses| + # rubocop: disable CodeReuse/ActiveRecord + ActiveRecord::Associations::Preloader.new.preload(preloadable_statuses(statuses), :tags) + # rubocop: enable CodeReuse/ActiveRecord + end + end + + def preloadable_statuses(statuses) + statuses.reject do |status| + status.instance_of?(::GenericCommitStatus) || status.instance_of?(::Ci::Bridge) + end + end + end +end diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml index 4358365504a..6b95c0f40c5 100644 --- a/app/views/admin/application_settings/_account_and_limit.html.haml +++ b/app/views/admin/application_settings/_account_and_limit.html.haml @@ -23,6 +23,9 @@ = f.label :session_expire_delay, _('Session duration (minutes)'), class: 'label-light' = f.number_field :session_expire_delay, class: 'form-control' %span.form-text.text-muted#session_expire_delay_help_block= _('GitLab restart is required to apply changes') + + = render_if_exists 'admin/application_settings/personal_access_token_expiration_policy', form: f + .form-group = f.label :user_oauth_applications, _('User OAuth applications'), class: 'label-bold' .form-check diff --git a/app/views/projects/stage/_stage.html.haml b/app/views/projects/stage/_stage.html.haml index f93994bebe3..387c8fb3234 100644 --- a/app/views/projects/stage/_stage.html.haml +++ b/app/views/projects/stage/_stage.html.haml @@ -1,3 +1,5 @@ +- stage = stage.present(current_user: current_user) + %tr %th{ colspan: 10 } %strong @@ -6,8 +8,8 @@ = ci_icon_for_status(stage.status) = stage.name.titleize -= render stage.statuses.latest_ordered, stage: false, ref: false, pipeline_link: false, allow_retry: true -= render stage.statuses.retried_ordered, stage: false, ref: false, pipeline_link: false, retried: true += render stage.latest_ordered_statuses, stage: false, ref: false, pipeline_link: false, allow_retry: true += render stage.retried_ordered_statuses, stage: false, ref: false, pipeline_link: false, retried: true %tr %td{ colspan: 10 } diff --git a/app/views/shared/_personal_access_tokens_form.html.haml b/app/views/shared/_personal_access_tokens_form.html.haml index ca0b473addf..16f8a692635 100644 --- a/app/views/shared/_personal_access_tokens_form.html.haml +++ b/app/views/shared/_personal_access_tokens_form.html.haml @@ -18,6 +18,9 @@ .form-group.col-md-6 = f.label :expires_at, _('Expires at'), class: 'label-bold' .input-icon-wrapper + + = render_if_exists 'personal_access_tokens/callout_max_personal_access_token_lifetime' + = f.text_field :expires_at, class: "datepicker form-control", placeholder: 'YYYY-MM-DD' .form-group diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index c77b05e3ea8..36b4e00e8d5 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -4,16 +4,13 @@ - breadcrumb_title @snippet.to_reference - page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets") -- if Feature.enabled?(:snippets_vue) - #js-snippet-view{ 'data-qa-selector': 'snippet_view' } -- else - = render 'shared/snippets/header' += render 'shared/snippets/header' - .personal-snippets - %article.file-holder.snippet-file-content - = render 'shared/snippets/blob' +.personal-snippets + %article.file-holder.snippet-file-content + = render 'shared/snippets/blob' - .row-content-block.top-block.content-component-block - = render 'award_emoji/awards_block', awardable: @snippet, inline: true + .row-content-block.top-block.content-component-block + = render 'award_emoji/awards_block', awardable: @snippet, inline: true - #notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => false + #notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => false diff --git a/changelogs/unreleased/preserve-merge-train-rows-after-merge.yml b/changelogs/unreleased/preserve-merge-train-rows-after-merge.yml new file mode 100644 index 00000000000..3441ebbbf1b --- /dev/null +++ b/changelogs/unreleased/preserve-merge-train-rows-after-merge.yml @@ -0,0 +1,5 @@ +--- +title: Preserve merge train history +merge_request: 19864 +author: +type: changed diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index b4be61d8a3d..063a3cc2b5b 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -123,3 +123,5 @@ - [refresh_license_compliance_checks, 2] - [design_management_new_version, 1] - [epics, 2] + - [personal_access_tokens, 1] + diff --git a/db/migrate/20190920122420_add_max_personal_access_token_lifetime_to_application_settings.rb b/db/migrate/20190920122420_add_max_personal_access_token_lifetime_to_application_settings.rb new file mode 100644 index 00000000000..5a6e810dede --- /dev/null +++ b/db/migrate/20190920122420_add_max_personal_access_token_lifetime_to_application_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddMaxPersonalAccessTokenLifetimeToApplicationSettings < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + add_column :application_settings, :max_personal_access_token_lifetime, :integer + end +end diff --git a/db/migrate/20191106144901_add_state_to_merge_trains.rb b/db/migrate/20191106144901_add_state_to_merge_trains.rb new file mode 100644 index 00000000000..e2256705f53 --- /dev/null +++ b/db/migrate/20191106144901_add_state_to_merge_trains.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddStateToMergeTrains < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + MERGE_TRAIN_STATUS_CREATED = 0 # Equivalent to MergeTrain.statuses[:created] + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default :merge_trains, :status, :integer, limit: 2, default: MERGE_TRAIN_STATUS_CREATED + end + + def down + remove_column :merge_trains, :status + end +end diff --git a/db/migrate/20191112105448_add_index_on_personal_access_tokens_user_id_and_expires_at.rb b/db/migrate/20191112105448_add_index_on_personal_access_tokens_user_id_and_expires_at.rb new file mode 100644 index 00000000000..1c1dc31ff23 --- /dev/null +++ b/db/migrate/20191112105448_add_index_on_personal_access_tokens_user_id_and_expires_at.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddIndexOnPersonalAccessTokensUserIdAndExpiresAt < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + INDEX_NAME = 'index_pat_on_user_id_and_expires_at' + + disable_ddl_transaction! + + def up + add_concurrent_index :personal_access_tokens, [:user_id, :expires_at], name: INDEX_NAME, using: :btree + end + + def down + remove_concurrent_index_by_name :personal_access_tokens, INDEX_NAME + end +end diff --git a/db/migrate/20191118155702_add_index_on_status_to_merge_trains.rb b/db/migrate/20191118155702_add_index_on_status_to_merge_trains.rb new file mode 100644 index 00000000000..9b5238045f8 --- /dev/null +++ b/db/migrate/20191118155702_add_index_on_status_to_merge_trains.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AddIndexOnStatusToMergeTrains < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + INDEX_NAME = 'index_for_status_per_branch_per_project' + + disable_ddl_transaction! + + def up + add_concurrent_index :merge_trains, [:target_project_id, :target_branch, :status], name: INDEX_NAME + remove_concurrent_index :merge_trains, :target_project_id + end + + def down + add_concurrent_index :merge_trains, :target_project_id + remove_concurrent_index :merge_trains, [:target_project_id, :target_branch, :status], name: INDEX_NAME + end +end diff --git a/db/schema.rb b/db/schema.rb index 621ae7f380e..d33060f0e37 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -325,6 +325,7 @@ ActiveRecord::Schema.define(version: 2019_12_02_031812) do t.string "encrypted_asset_proxy_secret_key_iv" t.string "static_objects_external_storage_url", limit: 255 t.string "static_objects_external_storage_auth_token", limit: 255 + t.integer "max_personal_access_token_lifetime" t.boolean "throttle_protected_paths_enabled", default: false, null: false t.integer "throttle_protected_paths_requests_per_period", default: 10, null: false t.integer "throttle_protected_paths_period_in_seconds", default: 60, null: false @@ -2524,9 +2525,10 @@ ActiveRecord::Schema.define(version: 2019_12_02_031812) do t.datetime_with_timezone "updated_at", null: false t.integer "target_project_id", null: false t.text "target_branch", null: false + t.integer "status", limit: 2, default: 0, null: false t.index ["merge_request_id"], name: "index_merge_trains_on_merge_request_id", unique: true t.index ["pipeline_id"], name: "index_merge_trains_on_pipeline_id" - t.index ["target_project_id"], name: "index_merge_trains_on_target_project_id" + t.index ["target_project_id", "target_branch", "status"], name: "index_for_status_per_branch_per_project" t.index ["user_id"], name: "index_merge_trains_on_user_id" end @@ -2925,6 +2927,7 @@ ActiveRecord::Schema.define(version: 2019_12_02_031812) do t.boolean "impersonation", default: false, null: false t.string "token_digest" t.index ["token_digest"], name: "index_personal_access_tokens_on_token_digest", unique: true + t.index ["user_id", "expires_at"], name: "index_pat_on_user_id_and_expires_at" t.index ["user_id"], name: "index_personal_access_tokens_on_user_id" end diff --git a/doc/api/settings.md b/doc/api/settings.md index ad9ffcbf872..185cce6353e 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -269,6 +269,7 @@ are listed in the descriptions of the relevant settings. | `max_artifacts_size` | integer | no | Maximum artifacts size in MB | | `max_attachment_size` | integer | no | Limit attachment size in MB | | `max_pages_size` | integer | no | Maximum size of pages repositories in MB | +| `max_personal_access_token_lifetime` | integer | no | **(ULTIMATE ONLY)** Maximum allowable lifetime for personal access tokens in days | | `metrics_enabled` | boolean | no | (**If enabled, requires:** `metrics_host`, `metrics_method_call_threshold`, `metrics_packet_size`, `metrics_pool_size`, `metrics_port`, `metrics_sample_interval` and `metrics_timeout`) Enable influxDB metrics. | | `metrics_host` | string | required by: `metrics_enabled` | InfluxDB host. | | `metrics_method_call_threshold` | integer | required by: `metrics_enabled` | A method call is only tracked when it takes longer than the given amount of milliseconds. | diff --git a/doc/ci/environments.md b/doc/ci/environments.md index bd989157486..6666b8d6145 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -303,6 +303,41 @@ You are not required to use the same prefix or only slashes (`/`) in the dynamic names. However, using this format will enable the [grouping similar environments](#grouping-similar-environments) feature. +### Configuring Kubernetes deployments + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/27630) in GitLab 12.6. + +If you are deploying to a [Kubernetes cluster](../user/project/clusters/index.md) +associated with your project, you can configure these deployments from your +`gitlab-ci.yml` file. + +The following configuration options are supported: + +- [`namespace`](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) + +In the following example, the job will deploy your application to the +`production` Kubernetes namespace. + +```yaml +deploy: + stage: deploy + script: + - echo "Deploy to production server" + environment: + name: production + url: https://example.com + kubernetes: + namespace: production + only: + - master +``` + +NOTE: **Note:** +Kubernetes configuration is not supported for Kubernetes clusters +that are [managed by GitLab](../user/project/clusters/index.md#gitlab-managed-clusters). +To follow progress on support for Gitlab-managed clusters, see the +[relevant issue](https://gitlab.com/gitlab-org/gitlab/issues/38054). + ### Complete example The configuration in this section provides a full development workflow where your app is: diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 714de3bac36..8562dc646f1 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1421,6 +1421,38 @@ The `stop_review_app` job is **required** to have the following keywords defined - `stage` should be the same as the `review_app` in order for the environment to stop automatically when the branch is deleted +#### `environment:kubernetes` + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/27630) in GitLab 12.6. + +The `kubernetes` block is used to configure deployments to a +[Kubernetes cluster](../../user/project/clusters/index.md) that is associated with your project. + +For example: + +```yaml +deploy: + stage: deploy + script: make deploy-app + environment: + name: production + kubernetes: + namespace: production +``` + +This will set up the `deploy` job to deploy to the `production` +environment, using the `production` +[Kubernetes namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/). + +For more information, see +[Available settings for `kubernetes`](../environments.md#configuring-kubernetes-deployments). + +NOTE: **Note:** +Kubernetes configuration is not supported for Kubernetes clusters +that are [managed by GitLab](../../user/project/clusters/index.md#gitlab-managed-clusters). +To follow progress on support for Gitlab-managed clusters, see the +[relevant issue](https://gitlab.com/gitlab-org/gitlab/issues/38054). + #### Dynamic environments > - [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6. diff --git a/doc/user/admin_area/settings/account_and_limit_settings.md b/doc/user/admin_area/settings/account_and_limit_settings.md index e443127a8a0..9d82b3b4292 100644 --- a/doc/user/admin_area/settings/account_and_limit_settings.md +++ b/doc/user/admin_area/settings/account_and_limit_settings.md @@ -84,3 +84,35 @@ add the line below to `/etc/gitlab/gitlab.rb` before increasing the max attachme ``` nginx['client_max_body_size'] = "200m" ``` + +## Limiting lifetime of personal access tokens **(ULTIMATE ONLY)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/3649) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.6. + +Users can optionally specify an expiration date for +[personal access tokens](../../profile/personal_access_tokens.md). +This expiration date is not a requirement, and can be set to any arbitrary date. + +Since personal access tokens are the only token needed for programmatic access to GitLab, +organizations with security requirements may want to enforce more protection to require +regular rotation of these tokens. + +### Setting a limit + +Only a GitLab administrator can set a limit. Leaving it empty means +there are no restrictions. + +To set a limit on how long personal access tokens are valid: + +1. Navigate to **Admin Area > Settings > General**. +1. Expand the **Account and limit** section. +1. Fill in the **Maximun allowable lifetime for personal access tokens (days)** field. +1. Click **Save changes**. + +Once a lifetime for personal access tokens is set, GitLab will: + +- Apply the lifetime for new personal access tokens, and require users to set an expiration date + and a date no later than the allowed lifetime. +- After three hours, revoke old tokens with no expiration date or with a lifetime longer than the + allowed lifetime. Three hours is given to allow administrators to change the allowed lifetime, + or remove it, before revocation takes place. diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb index 1d99349304b..fe7df1062c0 100644 --- a/lib/gitlab/diff/file_collection/merge_request_diff.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb @@ -4,45 +4,6 @@ module Gitlab module Diff module FileCollection class MergeRequestDiff < MergeRequestDiffBase - include Gitlab::Utils::StrongMemoize - - def diff_files - strong_memoize(:diff_files) do - diff_files = super - - diff_files.each { |diff_file| cache.decorate(diff_file) } - - diff_files - end - end - - override :write_cache - def write_cache - cache.write_if_empty - end - - override :clear_cache - def clear_cache - cache.clear - end - - def cache_key - cache.key - end - - def real_size - @merge_request_diff.real_size - end - - private - - def cache - @cache ||= if Feature.enabled?(:hset_redis_diff_caching, project) - Gitlab::Diff::HighlightCache.new(self) - else - Gitlab::Diff::DeprecatedHighlightCache.new(self) - end - end end end end diff --git a/lib/gitlab/diff/file_collection/merge_request_diff_base.rb b/lib/gitlab/diff/file_collection/merge_request_diff_base.rb index a747a6ed475..06cf3d4d168 100644 --- a/lib/gitlab/diff/file_collection/merge_request_diff_base.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff_base.rb @@ -15,6 +15,44 @@ module Gitlab diff_refs: merge_request_diff.diff_refs, fallback_diff_refs: merge_request_diff.fallback_diff_refs) end + + def diff_files + strong_memoize(:diff_files) do + diff_files = super + + diff_files.each { |diff_file| cache.decorate(diff_file) } + + diff_files + end + end + + override :write_cache + def write_cache + cache.write_if_empty + end + + override :clear_cache + def clear_cache + cache.clear + end + + def cache_key + cache.key + end + + def real_size + @merge_request_diff.real_size + end + + private + + def cache + @cache ||= if Feature.enabled?(:hset_redis_diff_caching, project) + Gitlab::Diff::HighlightCache.new(self) + else + Gitlab::Diff::DeprecatedHighlightCache.new(self) + end + end end end end diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 487dcd58d01..2616a19fdaa 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -42,7 +42,6 @@ module Gitlab # Initialize gon.features with any flags that should be # made globally available to the frontend push_frontend_feature_flag(:suppress_ajax_navigation_errors, default_enabled: true) - push_frontend_feature_flag(:snippets_vue, default_enabled: false) end # Exposes the state of a feature flag to the frontend code. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5a404b8976f..6dd524981a4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9128,6 +9128,9 @@ msgstr "" msgid "Helps reduce request volume for protected paths" msgstr "" +msgid "Hi %{username}!" +msgstr "" + msgid "Hide archived projects" msgstr "" @@ -10274,6 +10277,9 @@ msgstr "" msgid "Leave Admin Mode" msgstr "" +msgid "Leave blank for no limit. Once set, existing personal access tokens may be revoked" +msgstr "" + msgid "Leave edit mode? All unsaved changes will be lost." msgstr "" @@ -10764,6 +10770,9 @@ msgstr "" msgid "Max seats used" msgstr "" +msgid "Maximum allowable lifetime for personal access token (days)" +msgstr "" + msgid "Maximum artifacts size (MB)" msgstr "" @@ -10782,6 +10791,9 @@ msgstr "" msgid "Maximum job timeout has a value which could not be accepted" msgstr "" +msgid "Maximum lifetime allowable for Personal Access Tokens is active, your expire date must be set before %{maximum_allowable_date}." +msgstr "" + msgid "Maximum number of comments exceeded" msgstr "" @@ -11985,6 +11997,9 @@ msgstr[1] "" msgid "One or more groups that you don't have access to." msgstr "" +msgid "One or more of you personal access tokens were revoked" +msgstr "" + msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git." msgstr "" @@ -17439,6 +17454,11 @@ msgstr "" msgid "The following items will be exported:" msgstr "" +msgid "The following personal access token: %{token_names} was revoked, because a new policy to expire personal access tokens were set." +msgid_plural "The following personal access tokens: %{token_names} were revoked, because a new policy to expire personal access tokens were set." +msgstr[0] "" +msgstr[1] "" + msgid "The fork relationship has been removed." msgstr "" @@ -20114,6 +20134,12 @@ msgstr "" msgid "You can create files directly in GitLab using one of the following options." msgstr "" +msgid "You can create new ones at your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings" +msgstr "" + +msgid "You can create new ones at your Personal Access Tokens settings %{pat_link}" +msgstr "" + msgid "You can easily contribute to them by requesting to join these groups." msgstr "" diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb index 06d9af33189..8f4f85b55bb 100644 --- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -25,6 +25,16 @@ describe Projects::MergeRequests::DiffsController do end end + shared_examples 'cached diff collection' do + it 'ensures diff highlighting cache writing' do + expect_next_instance_of(Gitlab::Diff::HighlightCache) do |cache| + expect(cache).to receive(:write_if_empty).once + end + + go + end + end + shared_examples 'persisted preferred diff view cookie' do context 'with view param' do before do @@ -112,6 +122,7 @@ describe Projects::MergeRequests::DiffsController do end it_behaves_like 'persisted preferred diff view cookie' + it_behaves_like 'cached diff collection' end describe 'GET diffs_metadata' do @@ -404,6 +415,7 @@ describe Projects::MergeRequests::DiffsController do it_behaves_like 'forked project with submodules' it_behaves_like 'persisted preferred diff view cookie' + it_behaves_like 'cached diff collection' context 'diff unfolding' do let!(:unfoldable_diff_note) do diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb index fd7ef71db15..4ef3b0e5e7a 100644 --- a/spec/features/snippets/internal_snippet_spec.rb +++ b/spec/features/snippets/internal_snippet_spec.rb @@ -5,10 +5,6 @@ require 'spec_helper' describe 'Internal Snippets', :js do let(:internal_snippet) { create(:personal_snippet, :internal) } - before do - stub_feature_flags(snippets_vue: false) - end - describe 'normal user' do before do sign_in(create(:user)) diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index 57264f97ddc..2bd01be25e9 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -16,7 +16,6 @@ describe 'Comments on personal snippets', :js do let!(:other_note) { create(:note_on_personal_snippet) } before do - stub_feature_flags(snippets_vue: false) sign_in user visit snippet_path(snippet) diff --git a/spec/features/snippets/private_snippets_spec.rb b/spec/features/snippets/private_snippets_spec.rb index 37f45f22a27..9df4cd01103 100644 --- a/spec/features/snippets/private_snippets_spec.rb +++ b/spec/features/snippets/private_snippets_spec.rb @@ -6,7 +6,6 @@ describe 'Private Snippets', :js do let(:user) { create(:user) } before do - stub_feature_flags(snippets_vue: false) sign_in(user) end diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb index 045afcf1c12..82edda509c2 100644 --- a/spec/features/snippets/public_snippets_spec.rb +++ b/spec/features/snippets/public_snippets_spec.rb @@ -3,10 +3,6 @@ require 'spec_helper' describe 'Public Snippets', :js do - before do - stub_feature_flags(snippets_vue: false) - end - it 'Unauthenticated user should see public snippets' do public_snippet = create(:personal_snippet, :public) diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb index 9c686be012b..450e520e293 100644 --- a/spec/features/snippets/show_spec.rb +++ b/spec/features/snippets/show_spec.rb @@ -6,10 +6,6 @@ describe 'Snippet', :js do let(:project) { create(:project, :repository) } let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content) } - before do - stub_feature_flags(snippets_vue: false) - end - context 'Ruby file' do let(:file_name) { 'popen.rb' } let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data } diff --git a/spec/features/snippets/spam_snippets_spec.rb b/spec/features/snippets/spam_snippets_spec.rb index 0c3ca6f17c8..3e71a4e7879 100644 --- a/spec/features/snippets/spam_snippets_spec.rb +++ b/spec/features/snippets/spam_snippets_spec.rb @@ -7,7 +7,6 @@ describe 'User creates snippet', :js do before do stub_feature_flags(allow_possible_spam: false) - stub_feature_flags(snippets_vue: false) stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') Gitlab::CurrentSettings.update!( diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index b373264bbe4..9a141dd463a 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -8,7 +8,6 @@ describe 'User creates snippet', :js do let(:user) { create(:user) } before do - stub_feature_flags(snippets_vue: false) sign_in(user) visit new_snippet_path end diff --git a/spec/features/snippets/user_deletes_snippet_spec.rb b/spec/features/snippets/user_deletes_snippet_spec.rb index 35619b92561..217419a220a 100644 --- a/spec/features/snippets/user_deletes_snippet_spec.rb +++ b/spec/features/snippets/user_deletes_snippet_spec.rb @@ -10,8 +10,6 @@ describe 'User deletes snippet' do before do sign_in(user) - stub_feature_flags(snippets_vue: false) - visit snippet_path(snippet) end diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb index 1d26660a4f6..51d9baf44bc 100644 --- a/spec/features/snippets/user_edits_snippet_spec.rb +++ b/spec/features/snippets/user_edits_snippet_spec.rb @@ -12,7 +12,6 @@ describe 'User edits snippet', :js do let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, author: user) } before do - stub_feature_flags(snippets_vue: false) sign_in(user) visit edit_snippet_path(snippet) diff --git a/spec/features/snippets_spec.rb b/spec/features/snippets_spec.rb index bc7fa161e87..9df6fe7d16b 100644 --- a/spec/features/snippets_spec.rb +++ b/spec/features/snippets_spec.rb @@ -6,38 +6,11 @@ describe 'Snippets' do context 'when the project has snippets' do let(:project) { create(:project, :public) } let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) } - before do allow(Snippet).to receive(:default_per_page).and_return(1) - - visit project_snippets_path(project) + visit snippets_path(username: project.owner.username) end it_behaves_like 'paginated snippets' end - - describe 'rendering engine' do - let_it_be(:snippet) { create(:personal_snippet, :public) } - let(:snippets_vue_feature_flag_enabled) { true } - - before do - stub_feature_flags(snippets_vue: snippets_vue_feature_flag_enabled) - - visit snippet_path(snippet) - end - - it 'renders Vue application' do - expect(page).to have_selector('#js-snippet-view') - expect(page).not_to have_selector('.personal-snippets') - end - - context 'when feature flag is disabled' do - let(:snippets_vue_feature_flag_enabled) { false } - - it 'renders HAML application and not Vue' do - expect(page).not_to have_selector('#js-snippet-view') - expect(page).to have_selector('.personal-snippets') - end - end - end end diff --git a/spec/frontend/monitoring/embed/embed_spec.js b/spec/frontend/monitoring/embed/embed_spec.js index c5219f6130e..6d40719fa32 100644 --- a/spec/frontend/monitoring/embed/embed_spec.js +++ b/spec/frontend/monitoring/embed/embed_spec.js @@ -1,7 +1,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; import Embed from '~/monitoring/components/embed.vue'; -import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue'; +import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; import { TEST_HOST } from 'helpers/test_constants'; import { groups, initialState, metricsData, metricsWithData } from './mock_data'; @@ -55,7 +55,7 @@ describe('Embed', () => { it('shows an empty state when no metrics are present', () => { expect(wrapper.find('.metrics-embed').exists()).toBe(true); - expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(false); + expect(wrapper.find(PanelType).exists()).toBe(false); }); }); @@ -71,12 +71,12 @@ describe('Embed', () => { it('shows a chart when metrics are present', () => { wrapper.setProps({}); expect(wrapper.find('.metrics-embed').exists()).toBe(true); - expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(true); - expect(wrapper.findAll(MonitorTimeSeriesChart).length).toBe(2); + expect(wrapper.find(PanelType).exists()).toBe(true); + expect(wrapper.findAll(PanelType).length).toBe(2); }); it('includes groupId with dashboardUrl', () => { - expect(wrapper.find(MonitorTimeSeriesChart).props('groupId')).toBe(TEST_HOST); + expect(wrapper.find(PanelType).props('groupId')).toBe(TEST_HOST); }); }); }); diff --git a/spec/frontend/monitoring/panel_type_spec.js b/spec/frontend/monitoring/panel_type_spec.js index b30ad747a12..7adecc56d18 100644 --- a/spec/frontend/monitoring/panel_type_spec.js +++ b/spec/frontend/monitoring/panel_type_spec.js @@ -102,6 +102,10 @@ describe('Panel Type component', () => { expect(clipboardText()).toBe(exampleText); }); + + it('includes a default group id', () => { + expect(panelType.vm.groupId).toBe('panel-type-chart'); + }); }); describe('Anomaly Chart panel type', () => { diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb index 265c6260ca9..7e945d1d140 100644 --- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb @@ -123,4 +123,8 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do collection_default_args) end end + + it_behaves_like 'cacheable diff collection' do + let(:cacheable_files_count) { batch_size } + end end diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb index 7f207d5d2ee..d8f1fd26aeb 100644 --- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb @@ -4,7 +4,8 @@ require 'spec_helper' describe Gitlab::Diff::FileCollection::MergeRequestDiff do let(:merge_request) { create(:merge_request) } - let(:subject) { described_class.new(merge_request.merge_request_diff, diff_options: nil) } + let(:diffable) { merge_request.merge_request_diff } + let(:subject) { described_class.new(diffable, diff_options: nil) } let(:diff_files) { subject.diff_files } describe '#diff_files' do @@ -52,6 +53,10 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do let(:stub_path) { '.gitignore' } end + it_behaves_like 'cacheable diff collection' do + let(:cacheable_files_count) { diffable.size.to_i } + end + it 'returns a valid instance of a DiffCollection' do expect(diff_files).to be_a(Gitlab::Git::DiffCollection) end diff --git a/spec/presenters/ci/legacy_stage_presenter_spec.rb b/spec/presenters/ci/legacy_stage_presenter_spec.rb new file mode 100644 index 00000000000..932cfacafb1 --- /dev/null +++ b/spec/presenters/ci/legacy_stage_presenter_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::LegacyStagePresenter do + let(:legacy_stage) { create(:ci_stage) } + let(:presenter) { described_class.new(legacy_stage) } + + let!(:build) { create(:ci_build, :tags, pipeline: legacy_stage.pipeline, stage: legacy_stage.name) } + let!(:retried_build) { create(:ci_build, :tags, :retried, pipeline: legacy_stage.pipeline, stage: legacy_stage.name) } + + before do + create(:generic_commit_status, pipeline: legacy_stage.pipeline, stage: legacy_stage.name) + end + + describe '#latest_ordered_statuses' do + subject(:latest_ordered_statuses) { presenter.latest_ordered_statuses } + + it 'preloads build tags' do + expect(latest_ordered_statuses.second.association(:tags)).to be_loaded + end + end + + describe '#retried_ordered_statuses' do + subject(:retried_ordered_statuses) { presenter.retried_ordered_statuses } + + it 'preloads build tags' do + expect(retried_ordered_statuses.first.association(:tags)).to be_loaded + end + end +end diff --git a/spec/support/shared_examples/diff_file_collections.rb b/spec/support/shared_examples/diff_file_collections.rb index 4c64abd2a97..c8bd137bf84 100644 --- a/spec/support/shared_examples/diff_file_collections.rb +++ b/spec/support/shared_examples/diff_file_collections.rb @@ -57,3 +57,45 @@ shared_examples 'unfoldable diff' do subject.unfold_diff_files([position]) end end + +shared_examples 'cacheable diff collection' do + let(:cache) { instance_double(Gitlab::Diff::HighlightCache) } + + before do + expect(Gitlab::Diff::HighlightCache).to receive(:new).with(subject) { cache } + end + + describe '#write_cache' do + it 'calls Gitlab::Diff::HighlightCache#write_if_empty' do + expect(cache).to receive(:write_if_empty).once + + subject.write_cache + end + end + + describe '#clear_cache' do + it 'calls Gitlab::Diff::HighlightCache#clear' do + expect(cache).to receive(:clear).once + + subject.clear_cache + end + end + + describe '#cache_key' do + it 'calls Gitlab::Diff::HighlightCache#key' do + expect(cache).to receive(:key).once + + subject.cache_key + end + end + + describe '#diff_files' do + it 'calls Gitlab::Diff::HighlightCache#decorate' do + expect(cache).to receive(:decorate) + .with(instance_of(Gitlab::Diff::File)) + .exactly(cacheable_files_count).times + + subject.diff_files + end + end +end |