diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-19 03:08:49 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-19 03:08:49 +0300 |
commit | af5bf83315cddeb562d2429ae88b09a95b647472 (patch) | |
tree | 350b6526fe6abfcce2ecae22760caeee19b6921b | |
parent | af7558b036a53ebb0484e7978694a0b419c38c70 (diff) |
Add latest changes from gitlab-org/gitlab@master
45 files changed, 756 insertions, 206 deletions
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 7f62015510f..4f51b3f614a 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -3324,7 +3324,6 @@ Layout/LineLength: - 'spec/frontend/fixtures/merge_requests.rb' - 'spec/frontend/fixtures/merge_requests_diffs.rb' - 'spec/frontend/fixtures/pipeline_schedules.rb' - - 'spec/frontend/fixtures/projects.rb' - 'spec/frontend/fixtures/snippet.rb' - 'spec/graphql/gitlab_schema_spec.rb' - 'spec/graphql/mutations/boards/issues/issue_move_list_spec.rb' diff --git a/.rubocop_todo/layout/space_inside_parens.yml b/.rubocop_todo/layout/space_inside_parens.yml index d044e48d7aa..ac90f6b57e9 100644 --- a/.rubocop_todo/layout/space_inside_parens.yml +++ b/.rubocop_todo/layout/space_inside_parens.yml @@ -16,7 +16,6 @@ Layout/SpaceInsideParens: - 'ee/spec/finders/security/vulnerability_feedbacks_finder_spec.rb' - 'ee/spec/frontend/fixtures/analytics/devops_reports/devops_adoption/enabled_namespaces.rb' - 'ee/spec/frontend/fixtures/epic.rb' - - 'ee/spec/frontend/fixtures/namespace.rb' - 'ee/spec/lib/gitlab/search/index_curator_spec.rb' - 'ee/spec/lib/world_spec.rb' - 'ee/spec/mailers/notify_spec.rb' diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml index f35df67c4cc..78d1ffac5ac 100644 --- a/.rubocop_todo/rspec/context_wording.yml +++ b/.rubocop_todo/rspec/context_wording.yml @@ -1314,7 +1314,6 @@ RSpec/ContextWording: - 'spec/finders/user_group_notification_settings_finder_spec.rb' - 'spec/finders/user_recent_events_finder_spec.rb' - 'spec/frontend/fixtures/merge_requests.rb' - - 'spec/frontend/fixtures/projects.rb' - 'spec/frontend/fixtures/search.rb' - 'spec/frontend/fixtures/webauthn.rb' - 'spec/graphql/mutations/alert_management/create_alert_issue_spec.rb' diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml index 2b2054aed4c..7bcf93cb18a 100644 --- a/.rubocop_todo/rspec/feature_category.yml +++ b/.rubocop_todo/rspec/feature_category.yml @@ -93,7 +93,6 @@ RSpec/FeatureCategory: - 'ee/spec/frontend/fixtures/epic.rb' - 'ee/spec/frontend/fixtures/issues.rb' - 'ee/spec/frontend/fixtures/merge_requests.rb' - - 'ee/spec/frontend/fixtures/namespace.rb' - 'ee/spec/frontend/fixtures/on_demand_dast_scans.rb' - 'ee/spec/frontend/fixtures/oncall_schedule.rb' - 'ee/spec/frontend/fixtures/project_quality_summary.rb' @@ -1856,10 +1855,8 @@ RSpec/FeatureCategory: - 'spec/frontend/fixtures/listbox.rb' - 'spec/frontend/fixtures/merge_requests.rb' - 'spec/frontend/fixtures/merge_requests_diffs.rb' - - 'spec/frontend/fixtures/namespaces.rb' - 'spec/frontend/fixtures/pipeline_schedules.rb' - 'spec/frontend/fixtures/pipelines.rb' - - 'spec/frontend/fixtures/projects.rb' - 'spec/frontend/fixtures/projects_json.rb' - 'spec/frontend/fixtures/prometheus_integration.rb' - 'spec/frontend/fixtures/raw.rb' diff --git a/app/assets/javascripts/analytics/shared/components/date_ranges_dropdown.vue b/app/assets/javascripts/analytics/shared/components/date_ranges_dropdown.vue index 7ea7aba6f44..8b42d671149 100644 --- a/app/assets/javascripts/analytics/shared/components/date_ranges_dropdown.vue +++ b/app/assets/javascripts/analytics/shared/components/date_ranges_dropdown.vue @@ -55,6 +55,11 @@ export default { required: false, default: false, }, + disableSelectedDayCount: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -85,7 +90,9 @@ export default { return this.groupedDateRangeOptionsByValue[this.selectedValue]; }, showDaysSelectedCount() { - return !this.isCustomDateRangeSelected && this.daysSelectedCount; + return ( + !this.disableSelectedDayCount && !this.isCustomDateRangeSelected && this.daysSelectedCount + ); }, daysSelectedCount() { const { selectedDateRange } = this; diff --git a/app/assets/javascripts/observability/constants.js b/app/assets/javascripts/observability/constants.js index 34c43a10fc0..dbbec3678e8 100644 --- a/app/assets/javascripts/observability/constants.js +++ b/app/assets/javascripts/observability/constants.js @@ -1,7 +1,33 @@ +import { s__ } from '~/locale'; + export const SORTING_OPTIONS = { TIMESTAMP_DESC: 'timestamp_desc', TIMESTAMP_ASC: 'timestamp_asc', DURATION_DESC: 'duration_desc', DURATION_ASC: 'duration_asc', }; +Object.freeze(SORTING_OPTIONS); export const DEFAULT_SORTING_OPTION = SORTING_OPTIONS.TIMESTAMP_DESC; + +export const TIME_RANGE_OPTIONS = [ + { value: '5m', title: s__('Observability|Last 5 minutes') }, + { value: '15m', title: s__('Observability|Last 15 minutes') }, + { value: '30m', title: s__('Observability|Last 30 minutes') }, + { value: '1h', title: s__('Observability|Last 1 hour') }, + { value: '4h', title: s__('Observability|Last 4 hours') }, + { value: '12h', title: s__('Observability|Last 12 hours') }, + { value: '24h', title: s__('Observability|Last 24 hours') }, + { value: '7d', title: s__('Observability|Last 7 days') }, + { value: '14d', title: s__('Observability|Last 14 days') }, + { value: '30d', title: s__('Observability|Last 30 days') }, +]; +Object.freeze(TIME_RANGE_OPTIONS); + +const OPERERATOR_LIKE = '=~'; +const OPERERATOR_LIKE_TEXT = s__('ObservabilityMetrics|is like'); +const OPERERATOR_NOT_LIKE = '!~'; +const OPERERATOR_NOT_LIKE_TEXT = s__('ObservabilityMetrics|is not like'); + +const OPERATORS_LIKE = [{ value: OPERERATOR_LIKE, description: OPERERATOR_LIKE_TEXT }]; +const OPERATORS_NOT_LIKE = [{ value: OPERERATOR_NOT_LIKE, description: OPERERATOR_NOT_LIKE_TEXT }]; +export const OPERATORS_LIKE_NOT = [...OPERATORS_LIKE, ...OPERATORS_NOT_LIKE]; diff --git a/app/assets/javascripts/observability/utils.js b/app/assets/javascripts/observability/utils.js new file mode 100644 index 00000000000..bce82c58996 --- /dev/null +++ b/app/assets/javascripts/observability/utils.js @@ -0,0 +1,33 @@ +/** + * Return the data range for the given time period + * Accepted values are numbers followed by the unit 'm', 'h', 'd', e.g. '5m', '3h', '7d' + * + * e.g. timePerdio: '5m' + * returns: { min: Date(_now - 5min_), max: Date(_now_) } + * + * @param {String} timePeriod The 'period' string + * @returns {{max: Date, min: Date}|{}} where max, min are Date objects representing the period range + * It returns {} if the period filter does not represent any range (invalid range, etc) + */ +export const periodToDate = (timePeriod) => { + const maxMs = Date.now(); + let minMs; + const periodValue = parseInt(timePeriod.slice(0, -1), 10); + if (Number.isNaN(periodValue) || periodValue <= 0) return {}; + + const unit = timePeriod[timePeriod.length - 1]; + switch (unit) { + case 'm': + minMs = periodValue * 60 * 1000; + break; + case 'h': + minMs = periodValue * 60 * 1000 * 60; + break; + case 'd': + minMs = periodValue * 60 * 1000 * 60 * 24; + break; + default: + return {}; + } + return { min: new Date(maxMs - minMs), max: new Date(maxMs) }; +}; diff --git a/app/assets/javascripts/usage_quotas/components/search_and_sort_bar/search_and_sort_bar.stories.js b/app/assets/javascripts/usage_quotas/components/search_and_sort_bar/search_and_sort_bar.stories.js new file mode 100644 index 00000000000..3d0d2afbcf2 --- /dev/null +++ b/app/assets/javascripts/usage_quotas/components/search_and_sort_bar/search_and_sort_bar.stories.js @@ -0,0 +1,18 @@ +import SearchAndSortBar from './search_and_sort_bar.vue'; + +export default { + component: SearchAndSortBar, + title: 'usage_quotas/components/search_bar', +}; + +const Template = (_, { argTypes }) => ({ + components: { SearchAndSortBar }, + props: Object.keys(argTypes), + template: `<search-and-sort-bar v-bind="$props" />`, +}); +export const Default = Template.bind({}); + +Default.args = { + namespace: '42', + searchInputPlaceholder: 'search', +}; diff --git a/app/assets/javascripts/usage_quotas/components/search_and_sort_bar/search_and_sort_bar.vue b/app/assets/javascripts/usage_quotas/components/search_and_sort_bar/search_and_sort_bar.vue new file mode 100644 index 00000000000..db4c448dec4 --- /dev/null +++ b/app/assets/javascripts/usage_quotas/components/search_and_sort_bar/search_and_sort_bar.vue @@ -0,0 +1,74 @@ +<script> +import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants'; +import FilteredSortContainerRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; + +export default { + name: 'SearchAndSortBar', + components: { + FilteredSortContainerRoot, + }, + props: { + // Search + namespace: { + type: String, + required: true, + }, + searchInputPlaceholder: { + type: String, + required: true, + }, + recentSearchesStorageKey: { + type: String, + required: false, + default: '', + }, + initialFilterValue: { + type: Array, + required: false, + default: () => [], + }, + // Sort + initialSortBy: { + type: String, + required: false, + default: '', + validator: (value) => value === '' || /(_desc)|(_asc)/gi.test(value), + }, + sortOptions: { + type: Array, + default: () => [], + required: false, + }, + }, + emits: ['onFilter', 'onSort'], + methods: { + onFilter(searchTerms) { + const searchQuery = searchTerms.reduce((terms, searchTerm) => { + if (searchTerm.type !== FILTERED_SEARCH_TERM) { + return ''; + } + + return `${terms} ${searchTerm.value.data}`; + }, ''); + + this.$emit('onFilter', searchQuery.trim() || null); + }, + onSort(value) { + this.$emit('onSort', value); + }, + }, +}; +</script> + +<template> + <filtered-sort-container-root + :namespace="namespace" + :tokens="[] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */" + :search-input-placeholder="searchInputPlaceholder" + :sort-options="sortOptions" + :initial-sort-by="initialSortBy" + class="gl-flex-grow-1" + @onFilter="onFilter" + @onSort="onSort" + /> +</template> diff --git a/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue b/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue index efdb1b185d4..a812b90e378 100644 --- a/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue +++ b/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue @@ -1,6 +1,7 @@ <script> import { GlAlert } from '@gitlab/ui'; import StorageUsageStatistics from 'ee_else_ce/usage_quotas/storage/components/storage_usage_statistics.vue'; +import SearchAndSortBar from '~/usage_quotas/components/search_and_sort_bar/search_and_sort_bar.vue'; import DependencyProxyUsage from './dependency_proxy_usage.vue'; import ContainerRegistryUsage from './container_registry_usage.vue'; @@ -11,8 +12,9 @@ export default { StorageUsageStatistics, DependencyProxyUsage, ContainerRegistryUsage, + SearchAndSortBar, }, - inject: ['userNamespace'], + inject: ['userNamespace', 'namespaceId'], props: { namespaceLoadingError: { type: Boolean, @@ -89,6 +91,19 @@ export default { :loading="isNamespaceStorageStatisticsLoading" /> - <slot name="ee-storage-app"></slot> + <section class="gl-mt-5"> + <div class="gl-bg-gray-10 gl-p-5 gl-display-flex"> + <search-and-sort-bar + :namespace="namespaceId" + :search-input-placeholder="__('Search')" + @onFilter=" + (searchTerm) => { + $emit('search', searchTerm); + } + " + /> + </div> + <slot name="ee-storage-app"></slot> + </section> </div> </template> diff --git a/app/assets/javascripts/usage_quotas/storage/queries/namespace_storage.query.graphql b/app/assets/javascripts/usage_quotas/storage/queries/namespace_storage.query.graphql new file mode 100644 index 00000000000..d3b34f00db0 --- /dev/null +++ b/app/assets/javascripts/usage_quotas/storage/queries/namespace_storage.query.graphql @@ -0,0 +1,17 @@ +query getNamespaceStorageStatistics($fullPath: ID!) { + namespace(fullPath: $fullPath) { + id + rootStorageStatistics { + storageSize + repositorySize + lfsObjectsSize + containerRegistrySize + containerRegistrySizeIsEstimated + dependencyProxySize + buildArtifactsSize + packagesSize + wikiSize + snippetsSize + } + } +} diff --git a/app/assets/javascripts/usage_quotas/storage/queries/project_list_storage.query.graphql b/app/assets/javascripts/usage_quotas/storage/queries/project_list_storage.query.graphql new file mode 100644 index 00000000000..fd5b681ba81 --- /dev/null +++ b/app/assets/javascripts/usage_quotas/storage/queries/project_list_storage.query.graphql @@ -0,0 +1,47 @@ +#import "~/graphql_shared/fragments/page_info.fragment.graphql" + +query getProjectListStorageStatistics( + $fullPath: ID! + $searchTerm: String = "" + $first: Int + $last: Int + $after: String + $before: String + $sortKey: NamespaceProjectSort +) { + namespace(fullPath: $fullPath) { + id + projects( + includeSubgroups: true + notAimedForDeletion: true + search: $searchTerm + first: $first + last: $last + after: $after + before: $before + sort: $sortKey + ) { + nodes { + id + fullPath + nameWithNamespace + avatarUrl + webUrl + name + statistics { + storageSize + repositorySize + lfsObjectsSize + containerRegistrySize + buildArtifactsSize + packagesSize + wikiSize + snippetsSize + } + } + pageInfo { + ...PageInfo + } + } + } +} diff --git a/app/assets/stylesheets/page_bundles/login.scss b/app/assets/stylesheets/page_bundles/login.scss index 11582ff72f0..f4313518427 100644 --- a/app/assets/stylesheets/page_bundles/login.scss +++ b/app/assets/stylesheets/page_bundles/login.scss @@ -6,10 +6,6 @@ max-width: 960px; } - .navbar-gitlab .container { - max-width: none; - } - .flash-container { margin-bottom: $gl-padding; position: relative; @@ -95,15 +91,6 @@ } } -@include media-breakpoint-down(xs) { - .login-page { - .col-md-5.float-right { - float: none !important; - margin-bottom: 45px; - } - } -} - .html-devise-layout { margin: 0; padding: 0; diff --git a/app/views/layouts/_snowplow.html.haml b/app/views/layouts/_snowplow.html.haml index 503b38496f7..616fc59d2e8 100644 --- a/app/views/layouts/_snowplow.html.haml +++ b/app/views/layouts/_snowplow.html.haml @@ -2,7 +2,7 @@ - namespace = @group || @project&.namespace || @namespace = webpack_bundle_tag 'tracker' -- if Gitlab.com? && Feature.enabled?(:gl_analytics_tracking, Feature.current_request) +- if Gitlab.com? = webpack_bundle_tag 'analytics' = javascript_tag do :plain diff --git a/config/feature_flags/development/gl_analytics_tracking.yml b/config/feature_flags/development/gl_analytics_tracking.yml deleted file mode 100644 index 3a67aa05812..00000000000 --- a/config/feature_flags/development/gl_analytics_tracking.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: gl_analytics_tracking -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132534 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/426106 -milestone: '16.5' -type: development -group: group::analytics instrumentation -default_enabled: false diff --git a/doc/administration/geo/replication/security_review.md b/doc/administration/geo/replication/security_review.md index a267ac23779..8d563b7e609 100644 --- a/doc/administration/geo/replication/security_review.md +++ b/doc/administration/geo/replication/security_review.md @@ -226,7 +226,7 @@ from [owasp.org](https://owasp.org/). ### What data does the application store and how? -- Git repositories and files, tracking information related to the them, and the GitLab database contents. +- Git repositories and files, tracking information related to them, and the GitLab database contents. ### What data is or may need to be encrypted and what key management requirements have been defined? diff --git a/doc/administration/postgresql/replication_and_failover.md b/doc/administration/postgresql/replication_and_failover.md index 573110d7f57..007762c3c00 100644 --- a/doc/administration/postgresql/replication_and_failover.md +++ b/doc/administration/postgresql/replication_and_failover.md @@ -1242,7 +1242,7 @@ What happens here: - The application still uses the existing leader as its database backend. - The logical replication ensures that the new leader keeps in sync. - When other nodes are added to the new cluster, Patroni handles - the replication to the these nodes. + the replication to the nodes. It is a good idea to wait until the replica nodes of the new cluster are initialized and caught up on the replication lag. diff --git a/doc/api/index.md b/doc/api/index.md index aa10890214e..68ad4aa2e32 100644 --- a/doc/api/index.md +++ b/doc/api/index.md @@ -8,9 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w Automate with GitLab and integrate with external applications. -- [Integrations](../integration/index.md) -- [Webhooks](../user/project/integrations/webhooks.md) -- [REST API](rest/index.md) -- [GraphQL API](graphql/index.md) -- [OAuth 2.0 identity provider API](oauth2.md) -- [Editor and IDE extensions](../editor_extensions/index.md) +| | | | +|--|--|--| +| [**Integrate with GitLab**](../integration/index.md) **{chevron-right}**<br><br> Projects, issues, authentication, security providers. | [**Webhooks**](../user/project/integrations/webhooks.md) **{chevron-right}**<br><br> Custom HTTP callbacks, used to send events. | [**REST API**](rest/index.md) **{chevron-right}**<br><br> Programmatic interaction with GitLab. | +| [**GraphQL API**](graphql/index.md) **{chevron-right}**<br><br> Programmatic interaction with GitLab. | [**OAuth 2.0 identity provider API**](oauth2.md) **{chevron-right}**<br><br> Third-party authorization to GitLab. | [**Editor and IDE extensions**](../editor_extensions/index.md) **{chevron-right}**<br><br> Visual Studio Code, JetBrains, Neovim, GitLab CLI. | diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md index 7a3ad0ee804..ea1def6377e 100644 --- a/doc/ci/services/postgres.md +++ b/doc/ci/services/postgres.md @@ -12,40 +12,63 @@ do this with the Docker and Shell executors of GitLab Runner. ## Use PostgreSQL with the Docker executor -If you're using [GitLab Runner](../runners/index.md) with the Docker executor, -you basically have everything set up already. - NOTE: -Variables set in the GitLab UI are not passed down to the service containers. -For more information, see [GitLab CI/CD variables](../variables/index.md). -First, in your `.gitlab-ci.yml` add: +To pass variables set in the GitLab UI to service containers, you must [define the variables](../variables/index.md#define-a-cicd-variable-in-the-ui). +You must define your variables as either Group or Project, then call the variables in your job as shown in the following workaround. -```yaml -default: - services: - - postgres:12.2-alpine - -variables: - POSTGRES_DB: $POSTGRES_DB - POSTGRES_USER: $POSTGRES_USER - POSTGRES_PASSWORD: $POSTGRES_PASSWORD - POSTGRES_HOST_AUTH_METHOD: trust +In Postgres 15.4 and later, Postgres does not substitute a schema or owner name into an extension script if the name contains a quote, backslash, or dollar sign. +If the CI variables are not configured, the value uses the environment variable name as a string instead. For example, `POSTGRES_USER: $USER` results in the +`POSTGRES_USER` variable being set to '$USER', which causes Postgres to show the following error: + +```shell +Fatal: invalid character in extension ``` -And then configure your application to use the database, for example: +The workaround is to set your variables in [GitLab CI/CD variables](../variables/index.md) or set variables in string form: -```yaml -Host: postgres -User: $POSTGRES_USER -Password: $POSTGRES_PASSWORD -Database: $POSTGRES_DB -``` +1. [Set Postgres variables in GitLab](../variables/index.md#for-a-project). Variables set in the GitLab UI are not passed down to the service containers. + +1. In the `.gitlab-ci.yml` file, specify a Postgres image: + + ```yaml + default: + services: + - postgres + ``` + +1. In the `.gitlab-ci.yml` file, add your defined variables: + + ```yaml + variables: + POSTGRES_DB: $POSTGRES_DB + POSTGRES_USER: $POSTGRES_USER + POSTGRES_PASSWORD: $POSTGRES_PASSWORD + POSTGRES_HOST_AUTH_METHOD: trust + ``` + + For more information about using `postgres` for the `Host`, see [How services are linked to the job](../services/index.md#how-services-are-linked-to-the-job). + +1. Configure your application to use the database, for example: + + ```yaml + Host: postgres + User: $POSTGRES_USER + Password: $POSTGRES_PASSWORD + Database: $POSTGRES_DB + ``` + +Alternatively, you can set variables as a string in the `.gitlab-ci.yml` file: -If you're wondering why we used `postgres` for the `Host`, read more at -[How services are linked to the job](../services/index.md#how-services-are-linked-to-the-job). + ```yaml + variables: + POSTGRES_DB: DB_name + POSTGRES_USER: username + POSTGRES_PASSWORD: password + POSTGRES_HOST_AUTH_METHOD: trust + ``` -You can also use any other Docker image available on [Docker Hub](https://hub.docker.com/_/postgres). +You can use any other Docker image available on [Docker Hub](https://hub.docker.com/_/postgres). For example, to use PostgreSQL 14.3, the service becomes `postgres:14.3`. The `postgres` image can accept some environment variables. For more details, diff --git a/doc/development/github_importer.md b/doc/development/github_importer.md index 9e9216c4f66..676da95eb7e 100644 --- a/doc/development/github_importer.md +++ b/doc/development/github_importer.md @@ -95,7 +95,7 @@ For every collaborator, we schedule a job for the `Gitlab::GithubImport::ImportC NOTE: This stage is optional (controlled by `Gitlab::GithubImport::Settings`) and is selected by default. -### 6. Stage::ImportPullRequestsMergedByWorker +### 6. Stage::ImportPullRequestsMergedByWorker (deprecated) This worker imports the pull requests' _merged-by_ user information. The [_List pull requests_](https://docs.github.com/en/rest/pulls#list-pull-requests) @@ -104,20 +104,33 @@ individually to import this information. A `Gitlab::GithubImport::PullRequests::ImportMergedByWorker` job is scheduled for each fetched pull request. -### 7. Stage::ImportPullRequestsReviewRequestsWorker +NOTE: +This stage is skipped when `github_import_extended_events` feature flag is enabled as pull requests merged by information +is imported in the `10. Stage::ImportIssueEventsWorker` stage. This stage will be removed along with the feature flag. + +### 7. Stage::ImportPullRequestsReviewRequestsWorker (deprecated) This worker imports assigned reviewers of pull requests. For each pull request, this worker: - Fetches all assigned review requests. - Schedules a `Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker` job for each fetched review request. -### 8. Stage::ImportPullRequestsReviewsWorker +NOTE: +This stage is skipped when `github_import_extended_events` feature flag is enabled as pull requests review requests +information is imported in the `10. Stage::ImportIssueEventsWorker` stage. This stage will be removed along with the +feature flag. + +### 8. Stage::ImportPullRequestsReviewsWorker (deprecated) This worker imports reviews of pull requests. For each pull request, this worker: - Fetches all the pages of reviews. - Schedules a `Gitlab::GithubImport::PullRequests::ImportReviewWorker` job for each fetched review. +NOTE: +This stage is skipped when `github_import_extended_events` feature flag is enabled as pull requests reviews information +is imported in the`10. Stage::ImportIssueEventsWorker` stage. This stage will be removed along with the feature flag. + ### 9. Stage::ImportIssuesAndDiffNotesWorker This worker imports all issues and pull request comments. For every issue, we @@ -147,10 +160,21 @@ GitHub are stored in a single table. Therefore, they have globally-unique IDs an Therefore, both issues and pull requests have a common API for most related things. +When the feature flag `github_import_extended_events` is enabled, this stage is used to import +`pull request merged by`, `pull request reviews`, `pull request review requests` and `notes`. This is possible because +[timeline events endpoint](https://docs.github.com/en/rest/issues/timeline?apiVersion=2022-11-28#list-timeline-events-for-an-issue) +also contains such information. + +To facilitate the import of `pull request review requests` using the timeline events endpoint, +events must be processed sequentially. Given that import workers do not execute in a guaranteed order, +the `pull request review requests` events are initially placed in a Redis ordered list. Subsequently, they are consumed +in sequence by the `Gitlab::GithubImport::ReplayEventsWorker`. + NOTE: -This stage is optional and can consume significant extra import time (controlled by `Gitlab::GithubImport::Settings`). +This stage is mandatory when `github_import_extended_events` feature flag is enabled. Otherwise the stage is optional +and can executed using the [import options](../user/project/import/github.md#select-additional-items-to-import). -### 11. Stage::ImportNotesWorker +### 11. Stage::ImportNotesWorker (deprecated) This worker imports regular comments for both issues and pull requests. For every comment, we schedule a job for the @@ -161,6 +185,10 @@ returns comments for both issues and pull requests. This means we have to wait for all issues and pull requests to be imported before we can import regular comments. +NOTE: +This stage is skipped when `github_import_extended_events` feature flag is enabled as notes are imported in the +`10. Stage::ImportIssueEventsWorker` stage. This stage will be removed along with the feature flag. + ### 12. Stage::ImportAttachmentsWorker This worker imports note attachments that are linked inside Markdown. diff --git a/doc/development/internal_analytics/index.md b/doc/development/internal_analytics/index.md index 29d1a46c3cf..bd45f7fe207 100644 --- a/doc/development/internal_analytics/index.md +++ b/doc/development/internal_analytics/index.md @@ -62,7 +62,7 @@ such as the value of a setting or the count of rows in a database table. For GitLab there is an essential difference in analytics setup between SaaS and self-managed or GitLab Dedicated instances. On our SaaS instance both individual events and pre-computed metrics are available for analysis. Additionally for SaaS page views are automatically instrumented. -For self-managed only the metrics instrumenented on the version installed on the instance are available. +For self-managed only the metrics instrumented on the version installed on the instance are available. ## Data discovery @@ -87,7 +87,7 @@ For a list of other metrics tables refer to the [Data Models Cheat Sheet](https: ### Querying events -The following example query returns the number of daily event occurences for the `feature_used` event. +The following example query returns the number of daily event occurrences for the `feature_used` event. ```sql SELECT diff --git a/doc/install/index.md b/doc/install/index.md index d4d9a399eaf..782cd3bddd8 100644 --- a/doc/install/index.md +++ b/doc/install/index.md @@ -12,12 +12,10 @@ cloud providers, and in Kubernetes clusters. To get the best experience, you should balance performance, reliability, ease of administration (backups, upgrades, and troubleshooting) with the cost of hosting. -- [Requirements](requirements.md) -- [Installation methods](install_methods.md) -- [Cloud provider guides](cloud_providers.md) -- [Offline GitLab](../topics/offline/index.md) -- [Reference architectures](../administration/reference_architectures/index.md) -- [Steps after installing](next_steps.md) -- [Upgrade GitLab](../update/index.md) -- [Install GitLab Runner](https://docs.gitlab.com/runner/install/) -- [Configure GitLab Runner](https://docs.gitlab.com/runner/configuration/) +<!-- markdownlint-disable MD044 --> +| | | | +|--|--|--| +| [**Installation system requirements**](requirements.md) **{chevron-right}**<br><br> Prerequisites for installation. | [**Installation methods**](install_methods.md) **{chevron-right}**<br><br> Linux, Helm, Docker, Operator, source, or scripts. | [**Install GitLab on a cloud provider**](cloud_providers.md) **{chevron-right}**<br><br> AWS, Google Cloud Platform, Azure. | +| [**Offline GitLab**](../topics/offline/index.md) **{chevron-right}**<br><br> Isolated installation. | [**Reference architectures**](../administration/reference_architectures/index.md) **{chevron-right}**<br><br> Recommended deployments at scale. | [**Upgrade GitLab**](../update/index.md) **{chevron-right}**<br><br> Latest version instructions. | +| [**Install GitLab Runner**](https://docs.gitlab.com/runner/install/) **{chevron-right}**<br><br> Software for CI/CD jobs. | [**Configure GitLab Runner**](https://docs.gitlab.com/runner/configuration/) **{chevron-right}**<br><br> Config.toml, certificates, autoscaling, proxy setup. | | +<!-- markdownlint-enable MD044 --> diff --git a/doc/subscriptions/choosing_subscription.md b/doc/subscriptions/choosing_subscription.md index 04e05ef1be9..0bf476aeff4 100644 --- a/doc/subscriptions/choosing_subscription.md +++ b/doc/subscriptions/choosing_subscription.md @@ -5,9 +5,9 @@ description: Options for accessing GitLab. info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Choosing a GitLab subscription +# Choose a GitLab subscription -To choose the right GitLab subscription, choose an offering and a tier. +To choose the right GitLab subscription, select an offering and a tier. ## Choose a subscription diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md index 8d2ddb4f370..e995d2a9ffc 100644 --- a/doc/subscriptions/index.md +++ b/doc/subscriptions/index.md @@ -8,14 +8,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w Choose and manage the subscription that's right for you and your organization. -- [Choose a subscription](choosing_subscription.md) -- [Compare self-managed to SaaS](choosing_subscription.md) -- [GitLab SaaS](gitlab_com/index.md) -- [GitLab self-managed](self_managed/index.md) -- [GitLab Dedicated](gitlab_dedicated/index.md) -- [Community programs](community_programs.md) -- [Customers Portal](customers_portal.md) -- [Quarterly reconciliation](quarterly_reconciliation.md) -- [Storage usage quota](../user/usage_quotas.md) -- [Compute quota](../ci/pipelines/cicd_minutes.md) -- [Features available to Starter and Bronze subscribers](../subscriptions/bronze_starter.md) +| | | | +|--|--|--| +| [**Choose a GitLab subscription**](choosing_subscription.md) **{chevron-right}**<br><br> Options for accessing GitLab. | [**GitLab SaaS subscription**](gitlab_com/index.md) **{chevron-right}**<br><br> Seat usage, compute minutes, storage limits, renewal info. | [**GitLab self-managed subscription**](self_managed/index.md) **{chevron-right}**<br><br> Billable users, renewal and upgrade info. | +| [**GitLab Dedicated**](gitlab_dedicated/index.md) **{chevron-right}**<br><br> Available features and benefits. | [**Community programs**](community_programs.md) **{chevron-right}**<br><br> Education, Open Source, Startups. | [**The Customers Portal**](customers_portal.md) **{chevron-right}**<br><br> Payment and company details. | +| [**Quarterly reconciliation and annual true-ups**](quarterly_reconciliation.md) **{chevron-right}**<br><br> Billing examples.| [**Compute quota**](../ci/pipelines/cicd_minutes.md) **{chevron-right}**<br><br> Calculations, quotas, purchase information. | | diff --git a/doc/tutorials/index.md b/doc/tutorials/index.md index 8f02744b371..b9d004e1665 100644 --- a/doc/tutorials/index.md +++ b/doc/tutorials/index.md @@ -8,11 +8,8 @@ info: For assistance with this tutorials page, see https://handbook.gitlab.com/h These tutorials can help you learn how to use GitLab. -- [Find your way around GitLab](gitlab_navigation.md) -- [Learn Git](learn_git.md) -- [Plan and track your work](plan_and_track.md) -- [Build your application](build_application.md) -- [Secure your application and check compliance](secure_application.md) -- [Manage your infrastructure](infrastructure.md) -- [Develop with GitLab](develop.md) -- [Find more tutorials](more_tutorials.md) +| | | | +|--|--|--| +| [**Find your way around GitLab**](gitlab_navigation.md) **{chevron-right}**<br><br> Introduction to the product. | [**Learn Git**](learn_git.md) **{chevron-right}**<br><br> Git basics. | [**Plan and track your work**](plan_and_track.md) **{chevron-right}**<br><br> Planning, agile, issue boards. | +| [**Build your application**](build_application.md) **{chevron-right}**<br><br> CI/CD fundamentals and examples. | [**Secure your application and check compliance**](secure_application.md) **{chevron-right}**<br><br> Dependency and compliance scanning. | [**Manage your infrastructure**](infrastructure.md) **{chevron-right}**<br><br> GitOps, Kubernetes deployments. | +| [**Extend with GitLab**](develop.md) **{chevron-right}**<br><br> Integrations with third-party services. | | | diff --git a/doc/user/application_security/policies/scan-result-policies.md b/doc/user/application_security/policies/scan-result-policies.md index e2ec6b8ae56..c91b21cae69 100644 --- a/doc/user/application_security/policies/scan-result-policies.md +++ b/doc/user/application_security/policies/scan-result-policies.md @@ -143,11 +143,7 @@ This rule enforces the defined actions based on license findings. ## `any_merge_request` rule type > - The `branch_exceptions` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418741) in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. Enabled by default. [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133753) in GitLab 16.5. Feature flag removed. -> - The `any_merge_request` rule type was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418752) in GitLab 16.4. Enabled by default. [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136298) in GitLab 16.6. - -FLAG: -On self-managed GitLab, by default the `any_merge_request` field is available. To hide the feature, an administrator can [disable the feature flag](../../../administration/feature_flags.md) named `any_merge_request`. -On GitLab.com, this feature is available. +> - The `any_merge_request` rule type was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418752) in GitLab 16.4. Enabled by default. [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136298) in GitLab 16.6. Feature flag [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/432127). This rule enforces the defined actions for any merge request based on the commits signature. @@ -189,10 +185,10 @@ the defined policy. > - The `prevent_pushing_and_force_pushing` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/420629) in GitLab 16.4 [with flag](../../../administration/feature_flags.md) named `scan_result_policies_block_force_push`. Disabled by default. > - The above field was [enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/427260) in GitLab 16.6. > - The above field was [enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/427260) in GitLab 16.7. +> - Feature flag `scan_result_policies_block_force_push` [was removed](https://gitlab.com/gitlab-org/gitlab/-/issues/432123) in GitLab 16.8. FLAG: On self-managed GitLab, by default the `block_branch_modification` field is available. To hide the feature, an administrator can [disable the feature flag](../../../administration/feature_flags.md) named `scan_result_policies_block_unprotecting_branches`. On GitLab.com, this feature is available. -On self-managed GitLab, by default the `prevent_pushing_and_force_pushing` field is available. To hide the feature, an administrator can [disable the feature flag](../../../administration/feature_flags.md) named `scan_result_policies_block_force_push`. On GitLab.com, this feature is available. The settings set in the policy overwrite settings in the project. diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index b903e422a59..a6e49c47ce1 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -65,6 +65,27 @@ NOTE: [Making your email non-public](#set-your-public-email) does not prevent it from being used for commit matching, [project imports](../project/import/index.md), and [group migrations](../group/import/index.md). +## Delete emails from your user profile + +You can delete a secondary email address from your account. You cannot delete your +primary email address. + +If the deleted email address is used for any user emails, those user emails are +sent to the primary email address instead. + +NOTE: +Because of [issue 438600](https://gitlab.com/gitlab-org/gitlab/-/issues/438600), group notifications are still sent to +the deleted email address. + +To delete an email address from your account: + +1. On the left sidebar, select your avatar. +1. Select **Edit profile**. +1. On the left sidebar, select **Emails**. +1. Select **Delete** (**{remove}**) and confirm you want to **Remove**. + +You can also [use the API to delete a secondary email address](../../api/users.md#delete-email-for-current-user). + ## Make your user profile page private You can make your user profile visible to only you and GitLab administrators. diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index c4c0e48be3f..6420b2561dc 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -123,8 +123,7 @@ module Gitlab end def add_browsersdk_tracking - return unless Gitlab.com? && Feature.enabled?(:gl_analytics_tracking, -Feature.current_request) + return unless Gitlab.com? return if ENV['GITLAB_ANALYTICS_URL'].blank? || ENV['GITLAB_ANALYTICS_ID'].blank? diff --git a/lib/gitlab/pages/url_builder.rb b/lib/gitlab/pages/url_builder.rb index f01ec54b853..4d025af4ce5 100644 --- a/lib/gitlab/pages/url_builder.rb +++ b/lib/gitlab/pages/url_builder.rb @@ -14,18 +14,7 @@ module Gitlab end def pages_url(with_unique_domain: false) - return namespace_in_path_url(with_unique_domain && unique_domain_enabled?) if config.namespace_in_path - return unique_url if with_unique_domain && unique_domain_enabled? - - project_path_url = "#{config.protocol}://#{project_path}".downcase - - # If the project path is the same as host, we serve it as group page - # On development we ignore the URL port to make it work on GDK - return namespace_url if Rails.env.development? && portless(namespace_url) == project_path_url - # If the project path is the same as host, we serve it as group page - return namespace_url if namespace_url == project_path_url - - "#{namespace_url}/#{project_path}" + find_url(with_unique_domain).downcase end def unique_host @@ -63,6 +52,21 @@ module Gitlab attr_reader :project, :project_path + def find_url(with_unique_domain) + return namespace_in_path_url(with_unique_domain && unique_domain_enabled?) if config.namespace_in_path + return unique_url if with_unique_domain && unique_domain_enabled? + + project_path_url = "#{config.protocol}://#{project_path}" + + # If the project path is the same as host, we serve it as group page + # On development we ignore the URL port to make it work on GDK + return namespace_url if Rails.env.development? && portless(namespace_url) == project_path_url + # If the project path is the same as host, we serve it as group page + return namespace_url if namespace_url == project_path_url + + "#{namespace_url}/#{project_path}" + end + def namespace_url @namespace_url ||= url_for(project_namespace) end @@ -75,14 +79,13 @@ module Gitlab @pages_url ||= URI(config.url) .tap { |url| url.port = config.port } .to_s - .downcase end def namespace_in_path_url(with_unique_domain) if with_unique_domain - "#{pages_base_url}/#{project.project_setting.pages_unique_domain}".downcase + "#{pages_base_url}/#{project.project_setting.pages_unique_domain}" else - "#{pages_base_url}/#{project_namespace}/#{project_path}".downcase + "#{pages_base_url}/#{project_namespace}/#{project_path}" end end @@ -91,7 +94,6 @@ module Gitlab .tap { |url| url.port = config.port } .tap { |url| url.host.prepend("#{subdomain}.") } .to_s - .downcase end def portless(url) diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb index 4276091251a..e6baf62dfa2 100644 --- a/lib/gitlab/quick_actions/merge_request_actions.rb +++ b/lib/gitlab/quick_actions/merge_request_actions.rb @@ -311,7 +311,7 @@ module Gitlab types MergeRequest condition do quick_action_target.persisted? && - quick_action_target.reviewers.any? && + reviewers_to_remove?(@updates) && current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) end parse_params do |unassign_reviewer_param| @@ -360,6 +360,10 @@ module Gitlab end % { verb: verb, noun: noun } end + def reviewers_to_remove?(updates) + quick_action_target.reviewers.any? || updates&.dig(:reviewer_ids)&.any? + end + def merge_orchestration_service @merge_orchestration_service ||= ::MergeRequests::MergeOrchestrationService.new(project, current_user) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 7a824ff1000..230e745b80a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -33426,6 +33426,9 @@ msgstr "" msgid "ObservabilityMetrics|Failed to load metrics." msgstr "" +msgid "ObservabilityMetrics|Filter dimensions..." +msgstr "" + msgid "ObservabilityMetrics|Last ingested" msgstr "" @@ -33450,6 +33453,12 @@ msgstr "" msgid "ObservabilityMetrics|Value" msgstr "" +msgid "ObservabilityMetrics|is like" +msgstr "" + +msgid "ObservabilityMetrics|is not like" +msgstr "" + msgid "Observability|Enable" msgstr "" @@ -33462,6 +33471,36 @@ msgstr "" msgid "Observability|Get started with GitLab Observability" msgstr "" +msgid "Observability|Last 1 hour" +msgstr "" + +msgid "Observability|Last 12 hours" +msgstr "" + +msgid "Observability|Last 14 days" +msgstr "" + +msgid "Observability|Last 15 minutes" +msgstr "" + +msgid "Observability|Last 24 hours" +msgstr "" + +msgid "Observability|Last 30 days" +msgstr "" + +msgid "Observability|Last 30 minutes" +msgstr "" + +msgid "Observability|Last 4 hours" +msgstr "" + +msgid "Observability|Last 5 minutes" +msgstr "" + +msgid "Observability|Last 7 days" +msgstr "" + msgid "Observability|Monitor your applications with GitLab Observability." msgstr "" @@ -43038,6 +43077,9 @@ msgstr "" msgid "ScanExecutionPolicy|Select timezone" msgstr "" +msgid "ScanExecutionPolicy|The file at that project, ref, and path doesn't exist" +msgstr "" + msgid "ScanExecutionPolicy|The file path can't be empty" msgstr "" @@ -51727,36 +51769,6 @@ msgstr "" msgid "Tracing|Filter traces" msgstr "" -msgid "Tracing|Last 1 hour" -msgstr "" - -msgid "Tracing|Last 12 hours" -msgstr "" - -msgid "Tracing|Last 14 days" -msgstr "" - -msgid "Tracing|Last 15 minutes" -msgstr "" - -msgid "Tracing|Last 24 hours" -msgstr "" - -msgid "Tracing|Last 30 days" -msgstr "" - -msgid "Tracing|Last 30 minutes" -msgstr "" - -msgid "Tracing|Last 4 hours" -msgstr "" - -msgid "Tracing|Last 5 minutes" -msgstr "" - -msgid "Tracing|Last 7 days" -msgstr "" - msgid "Tracing|Metadata" msgstr "" diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb index a2fe79ae65d..fb9ffc5359e 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb @@ -5,8 +5,6 @@ module QA describe 'SSH keys support', :smoke, product_group: :source_code do let(:key_title) { "key for ssh tests #{Time.now.to_f}" } - key = nil - before do Flow::Login.sign_in end @@ -20,18 +18,19 @@ module QA expect(page).to have_content(key.sha256_fingerprint) end - # Note this context ensures that the example it contains is executed after the example above. Be aware of the order of execution if you add new examples in either context. - context 'after adding an ssh key' do - it 'can delete an ssh key', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347820' do - Page::Main::Menu.perform(&:click_edit_profile_link) - Page::Profile::Menu.perform(&:click_ssh_keys) - Page::Profile::SSHKeys.perform do |ssh_keys| - ssh_keys.remove_key(key.title) - end + it 'can delete an ssh key', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347820' do + key = Resource::SSHKey.fabricate_via_api! do |resource| + resource.title = key_title + end - expect(page).not_to have_content(key.title) - expect(page).not_to have_content(key.sha256_fingerprint) + Page::Main::Menu.perform(&:click_edit_profile_link) + Page::Profile::Menu.perform(&:click_ssh_keys) + Page::Profile::SSHKeys.perform do |ssh_keys| + ssh_keys.remove_key(key.title) end + + expect(page).not_to have_content(key.title) + expect(page).not_to have_content(key.sha256_fingerprint) end end end diff --git a/spec/frontend/analytics/shared/components/date_ranges_dropdown_spec.js b/spec/frontend/analytics/shared/components/date_ranges_dropdown_spec.js index 63407900be7..3c622983cad 100644 --- a/spec/frontend/analytics/shared/components/date_ranges_dropdown_spec.js +++ b/spec/frontend/analytics/shared/components/date_ranges_dropdown_spec.js @@ -135,6 +135,11 @@ describe('DateRangesDropdown', () => { expect(wrapper.findByText(`${expectedDaysCount} days selected`).exists()).toBe(true); }, ); + + it('should not rendered the indicator if disableSelectedDayCount is set', () => { + createComponent({ props: { disableSelectedDayCount: true, selected: lastWeekValue } }); + expect(findDaysSelectedCount().exists()).toBe(false); + }); }); describe('when the `tooltip` prop is set', () => { diff --git a/spec/frontend/fixtures/namespaces.rb b/spec/frontend/fixtures/namespaces.rb index 9858e3241cb..15d7d304035 100644 --- a/spec/frontend/fixtures/namespaces.rb +++ b/spec/frontend/fixtures/namespaces.rb @@ -2,11 +2,132 @@ require 'spec_helper' -RSpec.describe 'Jobs (JavaScript fixtures)' do +RSpec.describe 'Namespaces (JavaScript fixtures)', feature_category: :groups_and_projects do include ApiHelpers include JavaScriptFixturesHelpers include GraphqlHelpers + runners_token = 'runnerstoken:intabulasreferre' + + let_it_be(:namespace) { create(:namespace, :with_root_storage_statistics, name: 'frontend-fixtures') } + + let_it_be(:project_boilerplate) do + create( + :project, + name: 'Html5 Boilerplate', + path: 'html5-boilerplate', + namespace: namespace, + runners_token: runners_token + ) + end + + let_it_be(:project_twitter) do + create( + :project, + name: 'Twitter', + path: 'twitter', + namespace: namespace, + runners_token: runners_token + ) + end + + let_it_be(:user) { project_boilerplate.owner } + + describe 'Storage', feature_category: :consumables_cost_management do + describe GraphQL::Query, type: :request do + include GraphqlHelpers + base_input_path = 'usage_quotas/storage/queries/' + base_output_path = 'graphql/usage_quotas/storage/' + + context 'for namespace storage statistics query' do + before do + if Gitlab.ee? + namespace.update!( + additional_purchased_storage_size: 10_240 + ) + end + + namespace.root_storage_statistics.update!( + storage_size: 4.gigabytes, + container_registry_size: 1200.megabytes, + registry_size_estimated: false, + dependency_proxy_size: 1300.megabytes, + repository_size: 100.megabytes, + lfs_objects_size: 100.megabytes, + wiki_size: 100.megabytes, + build_artifacts_size: 100.megabytes, + packages_size: 100.megabytes, + snippets_size: 100.megabytes, + pipeline_artifacts_size: 100.megabytes, + uploads_size: 100.megabytes, + notification_level: "warning" + ) + end + + query_name = 'namespace_storage.query.graphql' + + it "#{base_output_path}#{query_name}.json" do + query = get_graphql_query_as_string("#{base_input_path}#{query_name}", ee: Gitlab.ee?) + + post_graphql( + query, + current_user: user, + variables: { + fullPath: namespace.full_path + } + ) + + expect_graphql_errors_to_be_empty + end + end + + context 'for project storage statistics query' do + before do + project_twitter.update!( + repository_size_limit: 100_000 + ) + project_twitter.statistics.update!( + repository_size: 209_710, + lfs_objects_size: 209_720, + build_artifacts_size: 1_272_375, + pipeline_artifacts_size: 0, + wiki_size: 0, + packages_size: 0 + ) + + project_boilerplate.update!( + repository_size_limit: 100_000 + ) + project_boilerplate.statistics.update!( + repository_size: 0, + lfs_objects_size: 0, + build_artifacts_size: 1_272_375, + pipeline_artifacts_size: 0, + wiki_size: 0, + packages_size: 0 + ) + end + + query_name = 'project_list_storage.query.graphql' + + it "#{base_output_path}#{query_name}.json" do + query = get_graphql_query_as_string("#{base_input_path}#{query_name}", ee: Gitlab.ee?) + + post_graphql( + query, + current_user: user, + variables: { + fullPath: namespace.full_path, + first: 10 + } + ) + + expect_graphql_errors_to_be_empty + end + end + end + end + describe API::Projects, type: :request do let_it_be(:user) { create(:user) } diff --git a/spec/frontend/fixtures/projects.rb b/spec/frontend/fixtures/projects.rb index 8cd651c5b36..440902ffa81 100644 --- a/spec/frontend/fixtures/projects.rb +++ b/spec/frontend/fixtures/projects.rb @@ -2,16 +2,41 @@ require 'spec_helper' -RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do +RSpec.describe 'Projects (JavaScript fixtures)', type: :controller, feature_category: :groups_and_projects do include ApiHelpers include JavaScriptFixturesHelpers runners_token = 'runnerstoken:intabulasreferre' let(:namespace) { create(:namespace, name: 'frontend-fixtures') } - let(:project) { create(:project, namespace: namespace, path: 'builds-project', runners_token: runners_token, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) } - let(:project_with_repo) { create(:project, :repository, description: 'Code and stuff', avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) } - let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2', runners_token: runners_token) } + let(:project) do + create( + :project, + namespace: namespace, + path: 'builds-project', + runners_token: runners_token, + avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png') + ) + end + + let(:project_with_repo) do + create( + :project, + :repository, + description: 'Code and stuff', + avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png') + ) + end + + let(:project_variable_populated) do + create( + :project, + namespace: namespace, + path: 'builds-project2', + runners_token: runners_token + ) + end + let(:user) { project.first_owner } render_views @@ -48,7 +73,7 @@ RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do describe GraphQL::Query, type: :request do include GraphqlHelpers - context 'access token projects query' do + context 'for access token projects query' do before do project_variable_populated.add_maintainer(user) end @@ -70,7 +95,7 @@ RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do describe 'Storage', feature_category: :consumables_cost_management do describe GraphQL::Query, type: :request do include GraphqlHelpers - context 'project storage statistics query' do + context 'for project storage statistics query' do before do project.statistics.update!( repository_size: 3_900_000, @@ -89,7 +114,7 @@ RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do query_name = 'project_storage.query.graphql' it "#{base_output_path}#{query_name}.json" do - query = get_graphql_query_as_string("#{base_input_path}#{query_name}") + query = get_graphql_query_as_string("#{base_input_path}#{query_name}", ee: Gitlab.ee?) post_graphql(query, current_user: user, variables: { fullPath: project.full_path }) diff --git a/spec/frontend/observability/utils_spec.js b/spec/frontend/observability/utils_spec.js new file mode 100644 index 00000000000..38c14ed9ca1 --- /dev/null +++ b/spec/frontend/observability/utils_spec.js @@ -0,0 +1,35 @@ +import { periodToDate } from '~/observability/utils'; + +describe('periodToDate', () => { + const realDateNow = Date.now; + + const MOCK_NOW_DATE = new Date('2023-10-09 15:30:00'); + + beforeEach(() => { + global.Date.now = jest.fn().mockReturnValue(MOCK_NOW_DATE); + }); + + afterEach(() => { + global.Date.now = realDateNow; + }); + it.each` + periodLabel | period | expectedMinDate + ${'minutes (m)'} | ${'30m'} | ${new Date('2023-10-09 15:00:00')} + ${'hours (h)'} | ${'2h'} | ${new Date('2023-10-09 13:30:00')} + ${'days (d)'} | ${'7d'} | ${new Date('2023-10-02 15:30:00')} + `('should return the correct date range for $periodLabel', ({ period, expectedMinDate }) => { + const result = periodToDate(period); + expect(result.min).toEqual(expectedMinDate); + expect(result.max).toEqual(MOCK_NOW_DATE); + }); + + it('should return an empty object if period value is not a positive integer', () => { + expect(periodToDate('')).toEqual({}); + expect(periodToDate('-5d')).toEqual({}); + expect(periodToDate('invalid')).toEqual({}); + }); + + it('should return an empty object if unit is not "m", "h", or "d"', () => { + expect(periodToDate('2w')).toEqual({}); + }); +}); diff --git a/spec/frontend/usage_quotas/components/search_and_sort_bar/search_and_sort_bar_spec.js b/spec/frontend/usage_quotas/components/search_and_sort_bar/search_and_sort_bar_spec.js new file mode 100644 index 00000000000..2b3e04b329a --- /dev/null +++ b/spec/frontend/usage_quotas/components/search_and_sort_bar/search_and_sort_bar_spec.js @@ -0,0 +1,83 @@ +import { shallowMount } from '@vue/test-utils'; +import SearchAndSortBar from '~/usage_quotas/components/search_and_sort_bar/search_and_sort_bar.vue'; +import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants'; +import FilteredSortContainerRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; + +describe('SearchAndSortBar', () => { + let wrapper; + + const defaultProps = { + namespace: '42', + searchInputPlaceholder: 'Search term', + }; + + const findFilteredSortContainerRoot = () => wrapper.findComponent(FilteredSortContainerRoot); + + const createComponent = (config) => { + const { props = {}, listeners = {} } = config; + + wrapper = shallowMount(SearchAndSortBar, { + propsData: { ...defaultProps, ...props }, + listeners, + }); + }; + + describe('onFilter', () => { + const onFilter = jest.fn(); + + beforeEach(() => { + createComponent({ + listeners: { onFilter }, + }); + }); + + it('parses and propagates emitted search event', () => { + const filteredSortContainerRoot = findFilteredSortContainerRoot(); + filteredSortContainerRoot.vm.$emit('onFilter', [ + { + id: 'token-1', + type: FILTERED_SEARCH_TERM, + value: { + data: 'abc', + }, + }, + { + id: 'token-2', + type: FILTERED_SEARCH_TERM, + value: { + data: 'def', + }, + }, + { + id: 'token-3', + type: FILTERED_SEARCH_TERM, + value: { + data: '123', + }, + }, + ]); + + expect(onFilter).toHaveBeenCalledTimes(1); + expect(onFilter).toHaveBeenCalledWith('abc def 123'); + }); + }); + + describe('onSort', () => { + const onSort = jest.fn(); + + beforeEach(() => { + createComponent({ + listeners: { onSort }, + }); + }); + + it('propagates emitted sorting value', () => { + const SORTING_VALUE = 'name_desc'; + const filteredSortContainerRoot = findFilteredSortContainerRoot(); + filteredSortContainerRoot.vm.$emit('onSort', SORTING_VALUE); + + expect(onSort).toHaveBeenCalledTimes(1); + expect(onSort).toHaveBeenCalledWith(SORTING_VALUE); + }); + }); +}); diff --git a/spec/frontend/usage_quotas/storage/components/namespace_storage_app_spec.js b/spec/frontend/usage_quotas/storage/components/namespace_storage_app_spec.js index 544666ddc5d..f9bc3d4da85 100644 --- a/spec/frontend/usage_quotas/storage/components/namespace_storage_app_spec.js +++ b/spec/frontend/usage_quotas/storage/components/namespace_storage_app_spec.js @@ -4,20 +4,16 @@ import NamespaceStorageApp from '~/usage_quotas/storage/components/namespace_sto import StorageUsageStatistics from '~/usage_quotas/storage/components/storage_usage_statistics.vue'; import DependencyProxyUsage from '~/usage_quotas/storage/components/dependency_proxy_usage.vue'; import ContainerRegistryUsage from '~/usage_quotas/storage/components/container_registry_usage.vue'; -import { defaultNamespaceProvideValues } from '../mock_data'; +import { + defaultNamespaceProvideValues, + mockGetNamespaceStorageGraphQLResponse, +} from '../mock_data'; const defaultProps = { namespaceLoadingError: false, projectsLoadingError: false, isNamespaceStorageStatisticsLoading: false, - // hardcoding object until we move test_fixtures from ee/ to here - namespace: { - rootStorageStatistics: { - storageSize: 1234, - containerRegistrySize: 111, - containerRegistrySizeIsEstimated: false, - }, - }, + namespace: mockGetNamespaceStorageGraphQLResponse.data.namespace, }; describe('NamespaceStorageApp', () => { diff --git a/spec/frontend/usage_quotas/storage/components/storage_usage_overview_card_spec.js b/spec/frontend/usage_quotas/storage/components/storage_usage_overview_card_spec.js index c79b6b94ac1..2dde568b1d2 100644 --- a/spec/frontend/usage_quotas/storage/components/storage_usage_overview_card_spec.js +++ b/spec/frontend/usage_quotas/storage/components/storage_usage_overview_card_spec.js @@ -3,14 +3,14 @@ import { numberToHumanSize } from '~/lib/utils/number_utils'; import StorageUsageOverviewCard from '~/usage_quotas/storage/components/storage_usage_overview_card.vue'; import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { mockGetNamespaceStorageGraphQLResponse } from '../mock_data'; describe('StorageUsageOverviewCard', () => { /** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */ let wrapper; const defaultProps = { - purchasedStorage: 0, - // hardcoding value until we move test_fixtures from ee/ to here - usedStorage: 1234, + usedStorage: + mockGetNamespaceStorageGraphQLResponse.data.namespace.rootStorageStatistics.storageSize, loading: false, }; diff --git a/spec/frontend/usage_quotas/storage/components/storage_usage_statistics_spec.js b/spec/frontend/usage_quotas/storage/components/storage_usage_statistics_spec.js index 73d02dc273f..bb96a12aaf2 100644 --- a/spec/frontend/usage_quotas/storage/components/storage_usage_statistics_spec.js +++ b/spec/frontend/usage_quotas/storage/components/storage_usage_statistics_spec.js @@ -1,10 +1,11 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import StorageUsageStatistics from '~/usage_quotas/storage/components/storage_usage_statistics.vue'; import StorageUsageOverviewCard from '~/usage_quotas/storage/components/storage_usage_overview_card.vue'; +import { mockGetNamespaceStorageGraphQLResponse } from '../mock_data'; const defaultProps = { - // hardcoding value until we move test_fixtures from ee/ to here - usedStorage: 1234, + usedStorage: + mockGetNamespaceStorageGraphQLResponse.data.namespace.rootStorageStatistics.storageSize, loading: false, }; diff --git a/spec/frontend/usage_quotas/storage/mock_data.js b/spec/frontend/usage_quotas/storage/mock_data.js index 7847ddbe0a4..e7886614575 100644 --- a/spec/frontend/usage_quotas/storage/mock_data.js +++ b/spec/frontend/usage_quotas/storage/mock_data.js @@ -1,6 +1,11 @@ import mockGetProjectStorageStatisticsGraphQLResponse from 'test_fixtures/graphql/usage_quotas/storage/project_storage.query.graphql.json'; +import mockGetNamespaceStorageGraphQLResponse from 'test_fixtures/graphql/usage_quotas/storage/namespace_storage.query.graphql.json'; +import mockGetProjectListStorageGraphQLResponse from 'test_fixtures/graphql/usage_quotas/storage/project_list_storage.query.graphql.json'; export { mockGetProjectStorageStatisticsGraphQLResponse }; +export { mockGetNamespaceStorageGraphQLResponse }; +export { mockGetProjectListStorageGraphQLResponse }; + export const mockEmptyResponse = { data: { project: null } }; export const defaultProjectProvideValues = { @@ -9,4 +14,5 @@ export const defaultProjectProvideValues = { export const defaultNamespaceProvideValues = { userNamespace: false, + namespaceId: '42', }; diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb index d9dcae3cdc7..de81e54a206 100644 --- a/spec/lib/gitlab/gon_helper_spec.rb +++ b/spec/lib/gitlab/gon_helper_spec.rb @@ -202,19 +202,6 @@ RSpec.describe Gitlab::GonHelper do helper.add_browsersdk_tracking end end - - context 'when feature flag is false' do - before do - stub_feature_flags(gl_analytics_tracking: false) - end - - it "doesn't set the analytics_url and analytics_id" do - expect(gon).not_to receive(:analytics_url=) - expect(gon).not_to receive(:analytics_id=) - - helper.add_browsersdk_tracking - end - end end context 'when environment variables are not set' do diff --git a/spec/lib/gitlab/pages/url_builder_spec.rb b/spec/lib/gitlab/pages/url_builder_spec.rb index 1a97ca01c3e..863c4481c9e 100644 --- a/spec/lib/gitlab/pages/url_builder_spec.rb +++ b/spec/lib/gitlab/pages/url_builder_spec.rb @@ -63,6 +63,12 @@ RSpec.describe Gitlab::Pages::UrlBuilder, feature_category: :pages do it { is_expected.to eq('http://group.example.com/project') } end + context 'when project is upper cased' do + let(:full_path) { 'group/Project' } + + it { is_expected.to eq('http://group.example.com/project') } + end + context 'when project is in a nested group page' do let(:full_path) { 'group/subgroup/project' } @@ -127,6 +133,12 @@ RSpec.describe Gitlab::Pages::UrlBuilder, feature_category: :pages do it { is_expected.to eq('http://example.com/group/project') } end + context 'when project is upper cased' do + let(:full_path) { 'group/Project' } + + it { is_expected.to eq('http://example.com/group/project') } + end + context 'when project is in a nested group page' do let(:full_path) { 'group/subgroup/project' } diff --git a/spec/support/helpers/user_with_namespace_shim.yml b/spec/support/helpers/user_with_namespace_shim.yml index 7f683e3f220..ba302bbeac6 100644 --- a/spec/support/helpers/user_with_namespace_shim.yml +++ b/spec/support/helpers/user_with_namespace_shim.yml @@ -121,7 +121,6 @@ - ee/spec/finders/ee/fork_targets_finder_spec.rb - ee/spec/finders/issues_finder_spec.rb - ee/spec/finders/security/approval_groups_finder_spec.rb -- ee/spec/frontend/fixtures/namespace.rb - ee/spec/frontend/fixtures/search.rb - ee/spec/graphql/ee/resolvers/board_lists_resolver_spec.rb - ee/spec/graphql/mutations/namespaces/increase_storage_temporarily_spec.rb diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index d4f7cbd8e0c..66730340f84 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -4233,7 +4233,6 @@ - './spec/frontend/fixtures/pipeline_schedules.rb' - './spec/frontend/fixtures/pipelines.rb' - './spec/frontend/fixtures/projects_json.rb' -- './spec/frontend/fixtures/projects.rb' - './spec/frontend/fixtures/prometheus_integration.rb' - './spec/frontend/fixtures/raw.rb' - './spec/frontend/fixtures/releases.rb' |