diff options
29 files changed, 218 insertions, 124 deletions
diff --git a/.markdownlint.yml b/.markdownlint.yml index ccf2ef02eee..35e8ef119be 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -1,3 +1,4 @@ +--- # Base Markdownlint configuration # Extended Markdownlint configuration in doc/.markdownlint/ "default": true diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 32b7211cb61..7d47e599800 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -1.40.0 +1.41.0 diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js index b671d038ce8..f45af5fe08e 100644 --- a/app/assets/javascripts/activities.js +++ b/app/assets/javascripts/activities.js @@ -42,7 +42,7 @@ export default class Activities { } updateTooltips() { - localTimeAgo($('.js-timeago', '.content_list')); + localTimeAgo(document.querySelectorAll('.content_list .js-timeago')); } reloadActivities() { diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index da7fc88d8ac..39dc4a4e9e5 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -93,7 +93,7 @@ export default class CommitsList { .text(n__('%d commit', '%d commits', commitsCount)); } - localTimeAgo($processedData.find('.js-timeago')); + localTimeAgo($processedData.find('.js-timeago').get()); return processedData; } diff --git a/app/assets/javascripts/lib/utils/datetime/timeago_utility.js b/app/assets/javascripts/lib/utils/datetime/timeago_utility.js index 0199cf20ace..5c6c5ccd5c1 100644 --- a/app/assets/javascripts/lib/utils/datetime/timeago_utility.js +++ b/app/assets/javascripts/lib/utils/datetime/timeago_utility.js @@ -1,4 +1,3 @@ -import $ from 'jquery'; import * as timeago from 'timeago.js'; import { languageCode, s__, createDateTimeFormat } from '../../../locale'; import { formatDate } from './date_format_utility'; @@ -97,21 +96,21 @@ export const getTimeago = () => /** * For the given elements, sets a tooltip with a formatted date. - * @param {JQuery} $timeagoEls - * @param {Boolean} setTimeago + * @param {Array<Node>|NodeList} elements + * @param {Boolean} updateTooltip */ -export const localTimeAgo = ($timeagoEls, setTimeago = true) => { +export const localTimeAgo = (elements, updateTooltip = true) => { const { format } = getTimeago(); - $timeagoEls.each((i, el) => { - $(el).text(format($(el).attr('datetime'), timeagoLanguageCode)); + elements.forEach((el) => { + el.innerText = format(el.dateTime, timeagoLanguageCode); }); - if (!setTimeago) { + if (!updateTooltip) { return; } function addTimeAgoTooltip() { - $timeagoEls.each((i, el) => { + elements.forEach((el) => { // Recreate with custom template el.setAttribute('title', formatDate(el.dateTime)); }); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 63c25136f74..88ddd12594e 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -183,7 +183,7 @@ document.addEventListener('DOMContentLoaded', () => { return true; }); - localTimeAgo($('abbr.timeago, .js-timeago'), true); + localTimeAgo(document.querySelectorAll('abbr.timeago, .js-timeago'), true); /** * This disables form buttons while a form is submitting diff --git a/app/assets/javascripts/members/components/action_buttons/user_action_buttons.vue b/app/assets/javascripts/members/components/action_buttons/user_action_buttons.vue index 1e9f79927ea..0c20f935d50 100644 --- a/app/assets/javascripts/members/components/action_buttons/user_action_buttons.vue +++ b/app/assets/javascripts/members/components/action_buttons/user_action_buttons.vue @@ -38,6 +38,7 @@ export default { usersName: user.name, source: source.fullName, }, + false, ); } diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index c3c3aacae35..1d1c0a23fab 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -333,8 +333,9 @@ export default class MergeRequestTabs { axios .get(`${source}.json`) .then(({ data }) => { - document.querySelector('div#commits').innerHTML = data.html; - localTimeAgo($('.js-timeago', 'div#commits')); + const commitsDiv = document.querySelector('div#commits'); + commitsDiv.innerHTML = data.html; + localTimeAgo(commitsDiv.querySelectorAll('.js-timeago')); this.commitsLoaded = true; this.scrollToContainerElement('#commits'); @@ -407,7 +408,7 @@ export default class MergeRequestTabs { initChangesDropdown(this.stickyTop); - localTimeAgo($('.js-timeago', 'div#diffs')); + localTimeAgo(document.querySelectorAll('#diffs .js-timeago')); syntaxHighlight($('#diffs .js-syntax-highlight')); if (this.isDiffAction(this.currentAction)) { diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index cd44e8b4987..ef51587734d 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -358,7 +358,7 @@ export default class Notes { setupNewNote($note) { // Update datetime format on the recent note - localTimeAgo($note.find('.js-timeago'), false); + localTimeAgo($note.find('.js-timeago').get(), false); this.collapseLongCommitList(); this.taskList.init(); @@ -511,7 +511,7 @@ export default class Notes { Notes.animateAppendNote(noteEntity.html, discussionContainer); } - localTimeAgo($('.js-timeago'), false); + localTimeAgo(document.querySelectorAll('.js-timeago'), false); Notes.checkMergeRequestStatus(); return this.updateNotesCount(1); } diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue index a3b78da6ef5..413163c8536 100644 --- a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue +++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue @@ -58,7 +58,7 @@ export default { }, computed: { modalTitle() { - return sprintf(this.title, { username: this.username }); + return sprintf(this.title, { username: this.username }, false); }, secondaryButtonLabel() { return s__('AdminUsers|Block user'); @@ -112,7 +112,7 @@ export default { </gl-sprintf> </p> - <oncall-schedules-list v-if="schedules.length" :schedules="schedules" /> + <oncall-schedules-list v-if="schedules.length" :schedules="schedules" :user-name="username" /> <p> <gl-sprintf :message="s__('AdminUsers|To confirm, type %{username}')"> diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare.js index 8d152ec4ba6..d61209f904d 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare.js +++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare.js @@ -15,7 +15,7 @@ const updateCommitList = (url, $loadingIndicator, $commitList, params) => { .then(({ data }) => { $loadingIndicator.hide(); $commitList.html(data); - localTimeAgo($('.js-timeago', $commitList)); + localTimeAgo($commitList.get(0).querySelectorAll('.js-timeago')); }); }; diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index f9d70845560..90eafa85886 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -166,7 +166,7 @@ export default class UserTabs { const tabSelector = `div#${action}`; this.$parentEl.find(tabSelector).html(data.html); this.loaded[action] = true; - localTimeAgo($('.js-timeago', tabSelector)); + localTimeAgo(document.querySelectorAll(`${tabSelector} .js-timeago`)); this.toggleLoading(false); }) @@ -209,7 +209,7 @@ export default class UserTabs { container, url: $(`${container} .overview-content-list`).data('href'), ...options, - postRenderCallback: () => localTimeAgo($('.js-timeago', container)), + postRenderCallback: () => localTimeAgo(document.querySelectorAll(`${container} .js-timeago`)), }); } diff --git a/app/assets/javascripts/vue_shared/components/oncall_schedules_list.vue b/app/assets/javascripts/vue_shared/components/oncall_schedules_list.vue index ff2847624c5..e37a663ace3 100644 --- a/app/assets/javascripts/vue_shared/components/oncall_schedules_list.vue +++ b/app/assets/javascripts/vue_shared/components/oncall_schedules_list.vue @@ -27,9 +27,13 @@ export default { title() { return this.isCurrentUser ? s__('OnCallSchedules|You are currently a part of:') - : sprintf(s__('OnCallSchedules|User %{name} is currently part of:'), { - name: this.userName, - }); + : sprintf( + s__('OnCallSchedules|User %{name} is currently part of:'), + { + name: this.userName, + }, + false, + ); }, footer() { return this.isCurrentUser diff --git a/danger/datateam/Dangerfile b/danger/datateam/Dangerfile index 0a33cb6777b..e054cddce06 100644 --- a/danger/datateam/Dangerfile +++ b/danger/datateam/Dangerfile @@ -1,14 +1,24 @@ # frozen_string_literal: true # rubocop:disable Style/SignalException +DATA_WAREHOUSE_LABELS = [ + "Data Warehouse::Impact Check", + "Data Warehouse::Impacted", + "Data Warehouse::Not Impacted" +].freeze + CHANGED_SCHEMA_MESSAGE = <<~MSG -Mentioning @gitlab-data/engineers to notify the team about changes to the db/structure.sql file. +Notification to the Data Team about changes to the db/structure.sql file, add label `Data Warehouse::Impact Check`. + +/label ~"Data Warehouse::Impact Check" MSG db_schema_updated = !git.modified_files.grep(%r{\Adb/structure\.sql}).empty? -if db_schema_updated +no_data_warehouse_labels = (gitlab.mr_labels & DATA_WAREHOUSE_LABELS).empty? + +if db_schema_updated && no_data_warehouse_labels markdown(CHANGED_SCHEMA_MESSAGE) diff --git a/doc/.markdownlint/markdownlint-no-trailing-spaces.yml b/doc/.markdownlint/markdownlint-no-trailing-spaces.yml index 3d107a3e667..71903ae423d 100644 --- a/doc/.markdownlint/markdownlint-no-trailing-spaces.yml +++ b/doc/.markdownlint/markdownlint-no-trailing-spaces.yml @@ -1,3 +1,4 @@ +--- # Extended Markdown configuration to enforce no-trailing-spaces rule "extends": "../../.markdownlint.yml" "no-trailing-spaces": true diff --git a/doc/.vale/gitlab/Acronyms.yml b/doc/.vale/gitlab/Acronyms.yml index 2077cdf9994..bc8f38fe5e2 100644 --- a/doc/.vale/gitlab/Acronyms.yml +++ b/doc/.vale/gitlab/Acronyms.yml @@ -120,6 +120,7 @@ exceptions: - PUT - RAID - RAM + - RBAC - RDP - REST - RFC diff --git a/doc/.vale/gitlab/HeaderGerunds.yml b/doc/.vale/gitlab/HeaderGerunds.yml index c73ed78da34..9e5fa19f867 100644 --- a/doc/.vale/gitlab/HeaderGerunds.yml +++ b/doc/.vale/gitlab/HeaderGerunds.yml @@ -1,5 +1,5 @@ --- -# Error: gitlab.HeaderGerunds +# Suggestion: gitlab.HeaderGerunds # # Checks for headers that start with gerunds (ing words). # Related to: https://docs.gitlab.com/ee/development/documentation/structure.html diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md index 2eab4555c85..835293ff500 100644 --- a/doc/administration/auth/authentiq.md +++ b/doc/administration/auth/authentiq.md @@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w To enable the Authentiq OmniAuth provider for passwordless authentication you must register an application with Authentiq. -Authentiq will generate a Client ID and the accompanying Client Secret for you to use. +Authentiq generates a Client ID and the accompanying Client Secret for you to use. 1. Get your Client credentials (Client ID and Client Secret) at [Authentiq](https://www.authentiq.com/developers). @@ -67,15 +67,17 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t 1. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect if you installed GitLab via Omnibus or from source respectively. -On the sign in page there should now be an Authentiq icon below the regular sign in form. Click the icon to begin the authentication process. +On the sign in page there should now be an Authentiq icon below the regular sign in form. Click the +icon to begin the authentication process. If the user: -- If the user has the Authentiq ID app installed in their iOS or Android device, they can: +- Has the Authentiq ID app installed in their iOS or Android device, they can: 1. Scan the QR code. 1. Decide what personal details to share. 1. Sign in to your GitLab installation. -- If not they will be prompted to download the app and then follow the procedure above. +- Does not have the app installed, they are prompted to download the app and then follow the + procedure above. -If everything goes right, the user will be returned to GitLab and will be signed in. +If everything works, the user is returned to GitLab and is signed in. <!-- ## Troubleshooting diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md index 63b19b1d82f..0af248e0573 100644 --- a/doc/administration/gitaly/index.md +++ b/doc/administration/gitaly/index.md @@ -186,7 +186,8 @@ Gitaly Cluster supports: - [Strong consistency](praefect.md#strong-consistency) of the secondary replicas. - [Automatic failover](praefect.md#automatic-failover-and-primary-election-strategies) from the primary to the secondary. - Reporting of possible data loss if replication queue is non-empty. -- Marking repositories as [read-only](praefect.md#read-only-mode) if data loss is detected to prevent data inconsistencies. +- From GitLab 13.0 to GitLab 14.0, marking repositories as [read-only](praefect.md#read-only-mode) + if data loss is detected to prevent data inconsistencies. Follow the [Gitaly Cluster epic](https://gitlab.com/groups/gitlab-org/-/epics/1489) for improvements including diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md index 1d070d601b9..b0ff67c0fdd 100644 --- a/doc/administration/gitaly/praefect.md +++ b/doc/administration/gitaly/praefect.md @@ -1239,24 +1239,30 @@ The `per_repository` election strategy solves this problem by electing a primary repository. Combined with [configurable replication factors](#configure-replication-factor), you can horizontally scale storage capacity and distribute write load across Gitaly nodes. -Primary elections are run when: +Primary elections are run: -- Praefect starts up. -- The cluster's consensus of a Gitaly node's health changes. +- In GitLab 14.1 and later, lazily. This means that Praefect doesn't immediately elect + a new primary node if the current one is unhealthy. A new primary is elected if it is + necessary to serve a request while the current primary is unavailable. +- In GitLab 13.12 to GitLab 14.0 when: + - Praefect starts up. + - The cluster's consensus of a Gitaly node's health changes. -A Gitaly node is considered: +A valid primary node candidate is a Gitaly node that: -- Healthy if `>=50%` Praefect nodes have successfully health checked the Gitaly node in the - previous ten seconds. -- Unhealthy otherwise. +- Is healthy. A Gitaly node is considered healthy if `>=50%` Praefect nodes have + successfully health checked the Gitaly node in the previous ten seconds. +- Has a fully up to date copy of the repository. -During an election run, Praefect elects a new primary Gitaly node for each repository that has -an unhealthy primary Gitaly node. The election is made: +If there are multiple primary node candidates, Praefect: -- Randomly from healthy secondary Gitaly nodes that are the most up to date. -- Only from Gitaly nodes assigned to the host repository. +- Picks one of them randomly. +- Prioritizes promoting a Gitaly node that is assigned to host the repository. If + there are no assigned Gitaly nodes to elect as the primary, Praefect may temporarily + elect an unassigned one. The unassigned primary is demoted in favor of an assigned + one when one becomes available. -If there are no healthy secondary nodes for a repository: +If there are no valid primary candidates for a repository: - The unhealthy primary node is demoted and the repository is left without a primary node. - Operations that require a primary node fail until a primary is successfully elected. @@ -1351,23 +1357,37 @@ Migrate to [repository-specific primary nodes](#repository-specific-primary-node Gitaly Cluster recovers from a failing primary Gitaly node by promoting a healthy secondary as the new primary. -To minimize data loss, Gitaly Cluster: +In GitLab 14.1 and later, Gitaly Cluster: + +- Elects a healthy secondary with a fully up to date copy of the repository as the new primary. +- Repository becomes unavailable if there are no fully up to date copies of it on healthy secondaries. + +To minimize data loss in GitLab 13.0 to 14.0, Gitaly Cluster: - Switches repositories that are outdated on the new primary to [read-only mode](#read-only-mode). -- Elects the secondary with the least unreplicated writes from the primary to be the new primary. - Because there can still be some unreplicated writes, [data loss can occur](#check-for-data-loss). +- Elects the secondary with the least unreplicated writes from the primary to be the new + primary. Because there can still be some unreplicated writes, + [data loss can occur](#check-for-data-loss). ### Read-only mode > - Introduced in GitLab 13.0 as [generally available](https://about.gitlab.com/handbook/product/gitlab-the-product/#generally-available-ga). > - Between GitLab 13.0 and GitLab 13.2, read-only mode applied to the whole virtual storage and occurred whenever failover occurred. > - [In GitLab 13.3 and later](https://gitlab.com/gitlab-org/gitaly/-/issues/2862), read-only mode applies on a per-repository basis and only occurs if a new primary is out of date. +new primary. If the failed primary contained unreplicated writes, [data loss can occur](#check-for-data-loss). +> - Removed in GitLab 14.1. Instead, repositories [become unavailable](#unavailable-repositories). -When Gitaly Cluster switches to a new primary, repositories enter read-only mode if they are out of -date. This can happen after failing over to an outdated secondary. Read-only mode eases data -recovery efforts by preventing writes that may conflict with the unreplicated writes on other nodes. +In GitLab 13.0 to 14.0, when Gitaly Cluster switches to a new primary, repositories enter +read-only mode if they are out of date. This can happen after failing over to an outdated +secondary. Read-only mode eases data recovery efforts by preventing writes that may conflict +with the unreplicated writes on other nodes. -To enable writes again, an administrator can: +When Gitaly Cluster switches to a new primary In GitLab 13.0 to 14.0, repositories enter +read-only mode if they are out of date. This can happen after failing over to an outdated +secondary. Read-only mode eases data recovery efforts by preventing writes that may conflict +with the unreplicated writes on other nodes. + +To enable writes again in GitLab 13.0 to 14.0, an administrator can: 1. [Check](#check-for-data-loss) for data loss. 1. Attempt to [recover](#data-recovery) missing data. @@ -1375,21 +1395,38 @@ To enable writes again, an administrator can: [accept data loss](#enable-writes-or-accept-data-loss) if necessary, depending on the version of GitLab. +## Unavailable repositories + +> - From GitLab 13.0 through 14.0, repositories became read-only if they were outdated on the primary but fully up to date on a healthy secondary. `dataloss` sub-command displays read-only repositories by default through these versions. +> - Since GitLab 14.1, Praefect contains more responsive failover logic which immediately fails over to one of the fully up to date secondaries rather than placing the repository in read-only mode. Since GitLab 14.1, the `dataloss` sub-command displays repositories which are unavailable due to having no fully up to date copies on healthy Gitaly nodes. + +A repository is unavailable if all of its up to date replicas are unavailable. Unavailable repositories are +not accessible through Praefect to prevent serving stale data that may break automated tooling. + ### Check for data loss -The Praefect `dataloss` sub-command identifies replicas that are likely to be outdated. This can help -identify potential data loss after a failover. The following parameters are -available: +The Praefect `dataloss` subcommand identifies: + +- Copies of repositories in GitLab 13.0 to GitLab 14.0 that at are likely to be outdated. + This can help identify potential data loss after a failover. +- Repositories in GitLab 14.1 and later that are unavailable. This helps identify potential + data loss and repositories which are no longer accessible because all of their up-to-date + replicas copies are unavailable. + +The following parameters are available: -- `-virtual-storage` that specifies which virtual storage to check. The default behavior is to - display outdated replicas of read-only repositories as they might require administrator action. -- In GitLab 13.3 and later, `-partially-replicated` that specifies whether to display a list of - [outdated replicas of writable repositories](#outdated-replicas-of-writable-repositories). +- `-virtual-storage` that specifies which virtual storage to check. Because they might require + an administrator to intervene, the default behavior is to display: + - In GitLab 13.0 to 14.0, copies of read-only repositories. + - In GitLab 14.1 and later, unavailable repositories. +- In GitLab 14.1 and later, [`-partially-unavailable`](#unavailable-replicas-of-available-repositories) + that specifies whether to include in the output repositories that are available but have + some assigned copies that are not available. NOTE: `dataloss` is still in beta and the output format is subject to change. -To check for repositories with outdated primaries, run: +To check for repositories with outdated primaries or for unavailable repositories, run: ```shell sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss [-virtual-storage <virtual-storage>] @@ -1401,13 +1438,20 @@ Every configured virtual storage is checked if none is specified: sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss ``` -Repositories which have assigned storage nodes that contain an outdated copy of the repository are listed -in the output. This information is printed for each repository: +Repositories are listed in the output that have either: + +- An outdated copy of the repository on the primary, in GitLab 13.0 to GitLab 14.0. +- No healthy and fully up-to-date copies available, in GitLab 14.1 and later. + +The following information is printed for each repository: - A repository's relative path to the storage directory identifies each repository and groups the related information. -- The repository's current status is printed in parentheses next to the disk path. If the repository's primary - is outdated, the repository is in `read-only` mode and can't accept writes. Otherwise, the mode is `writable`. +- The repository's current status is printed in parentheses next to the disk path: + - In GitLab 13.0 to 14.0, either `(read-only)` if the repository's primary node is outdated + and can't accept writes. Otherwise, `(writable)`. + - In GitLab 14.1 and later, `(unavailable)` is printed next to the disk path if the + repository is unavailable. - The primary field lists the repository's current primary. If the repository has no primary, the field shows `No Primary`. - The In-Sync Storages lists replicas which have replicated the latest successful write and all writes @@ -1417,44 +1461,51 @@ in the output. This information is printed for each repository: is listed next to replica. It's important to notice that the outdated replicas may be fully up to date or contain later changes but Praefect can't guarantee it. -Whether a replica is assigned to host the repository is listed with each replica's status. `assigned host` is printed -next to replicas which are assigned to store the repository. The text is omitted if the replica contains a copy of -the repository but is not assigned to store the repository. Such replicas aren't kept in-sync by Praefect, but may -act as replication sources to bring assigned replicas up to date. +Additional information includes: + +- Whether a node is assigned to host the repository is listed with each node's status. + `assigned host` is printed next to nodes that are assigned to store the repository. The + text is omitted if the node contains a copy of the repository but is not assigned to store + the repository. Such copies aren't kept in sync by Praefect, but may act as replication + sources to bring assigned copies up to date. +- In GitLab 14.1 and later, `unhealthy` is printed next to the copies that are located + on unhealthy Gitaly nodes. Example output: ```shell Virtual storage: default Outdated repositories: - @hashed/3f/db/3fdba35f04dc8c462986c992bcf875546257113072a909c162f7e470e581e278.git (read-only): + @hashed/3f/db/3fdba35f04dc8c462986c992bcf875546257113072a909c162f7e470e581e278.git (unavailable): Primary: gitaly-1 In-Sync Storages: - gitaly-2, assigned host + gitaly-2, assigned host, unhealthy Outdated Storages: gitaly-1 is behind by 3 changes or less, assigned host gitaly-3 is behind by 3 changes or less ``` -A confirmation is printed out when every repository is writable. For example: +A confirmation is printed out when every repository is available. For example: ```shell Virtual storage: default - All repositories are writable! + All repositories are available! ``` -#### Outdated replicas of writable repositories +#### Unavailable replicas of available repositories -> [Introduced](https://gitlab.com/gitlab-org/gitaly/-/issues/3019) in GitLab 13.3. +NOTE: +In GitLab 14.0 and earlier, the flag is `-partially-replicated` and the output shows any repositories with assigned nodes with outdated +copies. -To also list information of repositories whose primary is up to date but one or more assigned -replicas are outdated, use the `-partially-replicated` flag. +To also list information of repositories which are available but are unavailable from some of the assigned nodes, +use the `-partially-unavailable` flag. -A repository is writable if the primary has the latest changes. Secondaries might be temporarily -outdated while they are waiting to replicate the latest changes. +A repository is available if there is a healthy, up to date replica available. Some of the assigned secondary +replicas may be temporarily unavailable for access while they are waiting to replicate the latest changes. ```shell -sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss [-virtual-storage <virtual-storage>] [-partially-replicated] +sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss [-virtual-storage <virtual-storage>] [-partially-unavailable] ``` Example output: @@ -1462,7 +1513,7 @@ Example output: ```shell Virtual storage: default Outdated repositories: - @hashed/3f/db/3fdba35f04dc8c462986c992bcf875546257113072a909c162f7e470e581e278.git (writable): + @hashed/3f/db/3fdba35f04dc8c462986c992bcf875546257113072a909c162f7e470e581e278.git: Primary: gitaly-1 In-Sync Storages: gitaly-1, assigned host @@ -1471,14 +1522,14 @@ Virtual storage: default gitaly-3 is behind by 3 changes or less ``` -With the `-partially-replicated` flag set, a confirmation is printed out if every assigned replica is fully up to -date. +With the `-partially-unavailable` flag set, a confirmation is printed out if every assigned replica is fully up to +date and healthy. For example: ```shell Virtual storage: default - All repositories are up to date! + All repositories are fully available on all assigned storages! ``` ### Check repository checksums @@ -1486,30 +1537,50 @@ Virtual storage: default To check a project's repository checksums across on all Gitaly nodes, run the [replicas Rake task](../raketasks/praefect.md#replica-checksums) on the main GitLab node. +### Accept data loss + +WARNING: +`accept-dataloss` causes permanent data loss by overwriting other versions of the repository. Data +[recovery efforts](#data-recovery) must be performed before using it. + +If it is not possible to bring one of the up to date replicas back online, you may have to accept data +loss. When accepting data loss, Praefect marks the chosen replica of the repository as the latest version +and replicates it to the other assigned Gitaly nodes. This process overwrites any other version of the +repository so care must be taken. + +```shell +sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml accept-dataloss +-virtual-storage <virtual-storage> -repository <relative-path> -authoritative-storage <storage-name> +``` + ### Enable writes or accept data loss -Praefect provides the following sub-commands to re-enable writes: +WARNING: +`accept-dataloss` causes permanent data loss by overwriting other versions of the repository. +Data [recovery efforts](#data-recovery) must be performed before using it. -- In GitLab 13.2 and earlier, `enable-writes` to re-enable virtual storage for writes after data - recovery attempts. +Praefect provides the following subcommands to re-enable writes or accept data loss: - ```shell - sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml enable-writes -virtual-storage <virtual-storage> - ``` +- In GitLab 13.2 and earlier, `enable-writes` to re-enable virtual storage for writes after + data recovery attempts: -- [In GitLab 13.3](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2415) and later, - `accept-dataloss` to accept data loss and re-enable writes for repositories after data recovery - attempts have failed. Accepting data loss causes current version of the repository on the - authoritative storage to be considered latest. Other storages are brought up to date with the - authoritative storage by scheduling replication jobs. + ```shell + sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml enable-writes -virtual-storage <virtual-storage> + ``` + +- In GitLab 13.3 and later, if it is not possible to bring one of the up to date nodes back + online, you may have to accept data loss: ```shell sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml accept-dataloss -virtual-storage <virtual-storage> -repository <relative-path> -authoritative-storage <storage-name> ``` -WARNING: -`accept-dataloss` causes permanent data loss by overwriting other versions of the repository. Data -[recovery efforts](#data-recovery) must be performed before using it. + When accepting data loss, Praefect: + + 1. Marks the chosen copy of the repository as the latest version. + 1. Replicates the copy to the other assigned Gitaly nodes. + + This process overwrites any other copy of the repository so care must be taken. ## Data recovery diff --git a/generator_templates/usage_metric_definition/metric_definition.yml b/generator_templates/usage_metric_definition/metric_definition.yml index 53ff84fc4ab..209ff628eb5 100644 --- a/generator_templates/usage_metric_definition/metric_definition.yml +++ b/generator_templates/usage_metric_definition/metric_definition.yml @@ -11,7 +11,7 @@ milestone: "<%= milestone %>" introduced_by_url: time_frame: <%= time_frame %> data_source: -data_category: Operational +data_category: Optional distribution: <%= distribution %> tier: diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index b7cda94f289..64645ed16cd 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -48,7 +48,10 @@ module QA Resource::Issue.fabricate_via_api!.visit! end - it 'comments on an issue with an attachment', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1742' do + # The following example is excluded from running in `review-qa-smoke` job + # as it proved to be flaky when running against Review App + # See https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/11568#note_621999351 + it 'comments on an issue with an attachment', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1742', exclude: { job: 'review-qa-smoke' } do Page::Project::Issue::Show.perform do |show| show.comment('See attached image for scale', attachment: file_to_attach) diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb index 4bd00bd0a80..4d59e1ded3d 100644 --- a/spec/features/dashboard/issues_filter_spec.rb +++ b/spec/features/dashboard/issues_filter_spec.rb @@ -81,14 +81,14 @@ RSpec.describe 'Dashboard Issues filtering', :js do sort_by('Created date') visit_issues(assignee_username: user.username) - expect(find('.issues-filters')).to have_content('Created date') + expect(page).to have_button('Created date') end it 'keeps sorting issues after visiting Projects Issues page' do sort_by('Created date') visit project_issues_path(project) - expect(find('.issues-filters')).to have_content('Created date') + expect(page).to have_button('Created date') end end diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb index c161e1deb83..48297e9049e 100644 --- a/spec/features/issues/user_sorts_issues_spec.rb +++ b/spec/features/issues/user_sorts_issues_spec.rb @@ -22,11 +22,11 @@ RSpec.describe "User sorts issues" do create(:award_emoji, :upvote, awardable: issue2) sign_in(user) - - visit(project_issues_path(project)) end it 'keeps the sort option' do + visit(project_issues_path(project)) + find('.filter-dropdown-container .dropdown').click page.within('ul.dropdown-menu.dropdown-menu-right li') do @@ -47,11 +47,10 @@ RSpec.describe "User sorts issues" do end it 'sorts by popularity', :js do - find('.filter-dropdown-container .dropdown').click + visit(project_issues_path(project)) - page.within('ul.dropdown-menu.dropdown-menu-right li') do - click_link("Popularity") - end + click_button 'Created date' + click_on 'Popularity' page.within(".issues-list") do page.within("li.issue:nth-child(1)") do @@ -129,7 +128,7 @@ RSpec.describe "User sorts issues" do it 'filters by none' do visit project_issues_path(project, due_date: Issue::NoDueDate.name) - page.within '.issues-holder' do + page.within '.issues-list' do expect(page).not_to have_content('foo') expect(page).not_to have_content('bar') expect(page).to have_content('baz') @@ -139,7 +138,7 @@ RSpec.describe "User sorts issues" do it 'filters by any' do visit project_issues_path(project, due_date: Issue::AnyDueDate.name) - page.within '.issues-holder' do + page.within '.issues-list' do expect(page).to have_content('foo') expect(page).to have_content('bar') expect(page).to have_content('baz') @@ -153,7 +152,7 @@ RSpec.describe "User sorts issues" do visit project_issues_path(project, due_date: Issue::DueThisWeek.name) - page.within '.issues-holder' do + page.within '.issues-list' do expect(page).to have_content('foo') expect(page).to have_content('bar') expect(page).not_to have_content('baz') @@ -167,7 +166,7 @@ RSpec.describe "User sorts issues" do visit project_issues_path(project, due_date: Issue::DueThisMonth.name) - page.within '.issues-holder' do + page.within '.issues-list' do expect(page).to have_content('foo') expect(page).to have_content('bar') expect(page).not_to have_content('baz') @@ -181,7 +180,7 @@ RSpec.describe "User sorts issues" do visit project_issues_path(project, due_date: Issue::Overdue.name) - page.within '.issues-holder' do + page.within '.issues-list' do expect(page).not_to have_content('foo') expect(page).not_to have_content('bar') expect(page).to have_content('baz') @@ -195,7 +194,7 @@ RSpec.describe "User sorts issues" do visit project_issues_path(project, due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name) - page.within '.issues-holder' do + page.within '.issues-list' do expect(page).not_to have_content('foo') expect(page).not_to have_content('bar') expect(page).to have_content('baz') diff --git a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb index 54c9fbef218..99473f3b1ea 100644 --- a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb @@ -60,7 +60,7 @@ RSpec.describe 'User sorts merge requests' do visit(project_issues_path(project)) - expect(find('.issues-filters a.is-active')).not_to have_content('Milestone') + expect(page).not_to have_button('Milestone') end context 'when merge requests have awards' do diff --git a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js index 5cffe3ef2b0..2314ec678d3 100644 --- a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js +++ b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js @@ -1,4 +1,3 @@ -import $ from 'jquery'; import { getTimeago, localTimeAgo, timeFor } from '~/lib/utils/datetime/timeago_utility'; import { s__ } from '~/locale'; import '~/commons/bootstrap'; @@ -81,16 +80,16 @@ describe('TimeAgo utils', () => { `With User Setting timeDisplayRelative: $timeDisplayRelative`, ({ timeDisplayRelative, text }) => { it.each` - timeagoArg | title - ${false} | ${'some time'} - ${true} | ${'Feb 18, 2020 10:22pm UTC'} + updateTooltip | title + ${false} | ${'some time'} + ${true} | ${'Feb 18, 2020 10:22pm UTC'} `( - `has content: '${text}' and tooltip: '$title' with timeagoArg = $timeagoArg`, - ({ timeagoArg, title }) => { + `has content: '${text}' and tooltip: '$title' with updateTooltip = $updateTooltip`, + ({ updateTooltip, title }) => { window.gon = { time_display_relative: timeDisplayRelative }; const element = document.querySelector('time'); - localTimeAgo($(element), timeagoArg); + localTimeAgo([element], updateTooltip); jest.runAllTimers(); diff --git a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap index 4c644a0d05f..5e367891337 100644 --- a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap +++ b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap @@ -10,6 +10,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`] <oncall-schedules-list-stub schedules="schedule1,schedule2" + username="username" /> <p> diff --git a/spec/frontend/vue_shared/oncall_schedules_list_spec.js b/spec/frontend/vue_shared/oncall_schedules_list_spec.js index 5c30809c09b..f83a5187b8b 100644 --- a/spec/frontend/vue_shared/oncall_schedules_list_spec.js +++ b/spec/frontend/vue_shared/oncall_schedules_list_spec.js @@ -18,7 +18,7 @@ const mockSchedules = [ }, ]; -const userName = 'User 1'; +const userName = "O'User"; describe('On-call schedules list', () => { let wrapper; diff --git a/spec/support/matchers/have_issuable_counts.rb b/spec/support/matchers/have_issuable_counts.rb index 049cfc022fb..586ba0651dc 100644 --- a/spec/support/matchers/have_issuable_counts.rb +++ b/spec/support/matchers/have_issuable_counts.rb @@ -6,7 +6,7 @@ RSpec::Matchers.define :have_issuable_counts do |opts| end match do |actual| - actual.within '.issues-state-filters' do + actual.within '.top-area' do expected_counts.each do |expected_count| expect(actual).to have_content(expected_count) end |