Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
cd4d8b60a0
commit
b52c116c99
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
# Base Markdownlint configuration
|
# Base Markdownlint configuration
|
||||||
# Extended Markdownlint configuration in doc/.markdownlint/
|
# Extended Markdownlint configuration in doc/.markdownlint/
|
||||||
"default": true
|
"default": true
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
1.40.0
|
1.41.0
|
||||||
|
|
|
@ -42,7 +42,7 @@ export default class Activities {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTooltips() {
|
updateTooltips() {
|
||||||
localTimeAgo($('.js-timeago', '.content_list'));
|
localTimeAgo(document.querySelectorAll('.content_list .js-timeago'));
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadActivities() {
|
reloadActivities() {
|
||||||
|
|
|
@ -93,7 +93,7 @@ export default class CommitsList {
|
||||||
.text(n__('%d commit', '%d commits', commitsCount));
|
.text(n__('%d commit', '%d commits', commitsCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
localTimeAgo($processedData.find('.js-timeago'));
|
localTimeAgo($processedData.find('.js-timeago').get());
|
||||||
|
|
||||||
return processedData;
|
return processedData;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import $ from 'jquery';
|
|
||||||
import * as timeago from 'timeago.js';
|
import * as timeago from 'timeago.js';
|
||||||
import { languageCode, s__, createDateTimeFormat } from '../../../locale';
|
import { languageCode, s__, createDateTimeFormat } from '../../../locale';
|
||||||
import { formatDate } from './date_format_utility';
|
import { formatDate } from './date_format_utility';
|
||||||
|
@ -97,21 +96,21 @@ export const getTimeago = () =>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For the given elements, sets a tooltip with a formatted date.
|
* For the given elements, sets a tooltip with a formatted date.
|
||||||
* @param {JQuery} $timeagoEls
|
* @param {Array<Node>|NodeList} elements
|
||||||
* @param {Boolean} setTimeago
|
* @param {Boolean} updateTooltip
|
||||||
*/
|
*/
|
||||||
export const localTimeAgo = ($timeagoEls, setTimeago = true) => {
|
export const localTimeAgo = (elements, updateTooltip = true) => {
|
||||||
const { format } = getTimeago();
|
const { format } = getTimeago();
|
||||||
$timeagoEls.each((i, el) => {
|
elements.forEach((el) => {
|
||||||
$(el).text(format($(el).attr('datetime'), timeagoLanguageCode));
|
el.innerText = format(el.dateTime, timeagoLanguageCode);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!setTimeago) {
|
if (!updateTooltip) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTimeAgoTooltip() {
|
function addTimeAgoTooltip() {
|
||||||
$timeagoEls.each((i, el) => {
|
elements.forEach((el) => {
|
||||||
// Recreate with custom template
|
// Recreate with custom template
|
||||||
el.setAttribute('title', formatDate(el.dateTime));
|
el.setAttribute('title', formatDate(el.dateTime));
|
||||||
});
|
});
|
||||||
|
|
|
@ -183,7 +183,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
return true;
|
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
|
* This disables form buttons while a form is submitting
|
||||||
|
|
|
@ -38,6 +38,7 @@ export default {
|
||||||
usersName: user.name,
|
usersName: user.name,
|
||||||
source: source.fullName,
|
source: source.fullName,
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -333,8 +333,9 @@ export default class MergeRequestTabs {
|
||||||
axios
|
axios
|
||||||
.get(`${source}.json`)
|
.get(`${source}.json`)
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
document.querySelector('div#commits').innerHTML = data.html;
|
const commitsDiv = document.querySelector('div#commits');
|
||||||
localTimeAgo($('.js-timeago', 'div#commits'));
|
commitsDiv.innerHTML = data.html;
|
||||||
|
localTimeAgo(commitsDiv.querySelectorAll('.js-timeago'));
|
||||||
this.commitsLoaded = true;
|
this.commitsLoaded = true;
|
||||||
this.scrollToContainerElement('#commits');
|
this.scrollToContainerElement('#commits');
|
||||||
|
|
||||||
|
@ -407,7 +408,7 @@ export default class MergeRequestTabs {
|
||||||
|
|
||||||
initChangesDropdown(this.stickyTop);
|
initChangesDropdown(this.stickyTop);
|
||||||
|
|
||||||
localTimeAgo($('.js-timeago', 'div#diffs'));
|
localTimeAgo(document.querySelectorAll('#diffs .js-timeago'));
|
||||||
syntaxHighlight($('#diffs .js-syntax-highlight'));
|
syntaxHighlight($('#diffs .js-syntax-highlight'));
|
||||||
|
|
||||||
if (this.isDiffAction(this.currentAction)) {
|
if (this.isDiffAction(this.currentAction)) {
|
||||||
|
|
|
@ -358,7 +358,7 @@ export default class Notes {
|
||||||
|
|
||||||
setupNewNote($note) {
|
setupNewNote($note) {
|
||||||
// Update datetime format on the recent note
|
// Update datetime format on the recent note
|
||||||
localTimeAgo($note.find('.js-timeago'), false);
|
localTimeAgo($note.find('.js-timeago').get(), false);
|
||||||
|
|
||||||
this.collapseLongCommitList();
|
this.collapseLongCommitList();
|
||||||
this.taskList.init();
|
this.taskList.init();
|
||||||
|
@ -511,7 +511,7 @@ export default class Notes {
|
||||||
Notes.animateAppendNote(noteEntity.html, discussionContainer);
|
Notes.animateAppendNote(noteEntity.html, discussionContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
localTimeAgo($('.js-timeago'), false);
|
localTimeAgo(document.querySelectorAll('.js-timeago'), false);
|
||||||
Notes.checkMergeRequestStatus();
|
Notes.checkMergeRequestStatus();
|
||||||
return this.updateNotesCount(1);
|
return this.updateNotesCount(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
modalTitle() {
|
modalTitle() {
|
||||||
return sprintf(this.title, { username: this.username });
|
return sprintf(this.title, { username: this.username }, false);
|
||||||
},
|
},
|
||||||
secondaryButtonLabel() {
|
secondaryButtonLabel() {
|
||||||
return s__('AdminUsers|Block user');
|
return s__('AdminUsers|Block user');
|
||||||
|
@ -112,7 +112,7 @@ export default {
|
||||||
</gl-sprintf>
|
</gl-sprintf>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<oncall-schedules-list v-if="schedules.length" :schedules="schedules" />
|
<oncall-schedules-list v-if="schedules.length" :schedules="schedules" :user-name="username" />
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<gl-sprintf :message="s__('AdminUsers|To confirm, type %{username}')">
|
<gl-sprintf :message="s__('AdminUsers|To confirm, type %{username}')">
|
||||||
|
|
|
@ -15,7 +15,7 @@ const updateCommitList = (url, $loadingIndicator, $commitList, params) => {
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
$loadingIndicator.hide();
|
$loadingIndicator.hide();
|
||||||
$commitList.html(data);
|
$commitList.html(data);
|
||||||
localTimeAgo($('.js-timeago', $commitList));
|
localTimeAgo($commitList.get(0).querySelectorAll('.js-timeago'));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,7 @@ export default class UserTabs {
|
||||||
const tabSelector = `div#${action}`;
|
const tabSelector = `div#${action}`;
|
||||||
this.$parentEl.find(tabSelector).html(data.html);
|
this.$parentEl.find(tabSelector).html(data.html);
|
||||||
this.loaded[action] = true;
|
this.loaded[action] = true;
|
||||||
localTimeAgo($('.js-timeago', tabSelector));
|
localTimeAgo(document.querySelectorAll(`${tabSelector} .js-timeago`));
|
||||||
|
|
||||||
this.toggleLoading(false);
|
this.toggleLoading(false);
|
||||||
})
|
})
|
||||||
|
@ -209,7 +209,7 @@ export default class UserTabs {
|
||||||
container,
|
container,
|
||||||
url: $(`${container} .overview-content-list`).data('href'),
|
url: $(`${container} .overview-content-list`).data('href'),
|
||||||
...options,
|
...options,
|
||||||
postRenderCallback: () => localTimeAgo($('.js-timeago', container)),
|
postRenderCallback: () => localTimeAgo(document.querySelectorAll(`${container} .js-timeago`)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,13 @@ export default {
|
||||||
title() {
|
title() {
|
||||||
return this.isCurrentUser
|
return this.isCurrentUser
|
||||||
? s__('OnCallSchedules|You are currently a part of:')
|
? s__('OnCallSchedules|You are currently a part of:')
|
||||||
: sprintf(s__('OnCallSchedules|User %{name} is currently part of:'), {
|
: sprintf(
|
||||||
name: this.userName,
|
s__('OnCallSchedules|User %{name} is currently part of:'),
|
||||||
});
|
{
|
||||||
|
name: this.userName,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
footer() {
|
footer() {
|
||||||
return this.isCurrentUser
|
return this.isCurrentUser
|
||||||
|
|
|
@ -1,14 +1,24 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
# rubocop:disable Style/SignalException
|
# rubocop:disable Style/SignalException
|
||||||
|
|
||||||
|
DATA_WAREHOUSE_LABELS = [
|
||||||
|
"Data Warehouse::Impact Check",
|
||||||
|
"Data Warehouse::Impacted",
|
||||||
|
"Data Warehouse::Not Impacted"
|
||||||
|
].freeze
|
||||||
|
|
||||||
CHANGED_SCHEMA_MESSAGE = <<~MSG
|
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
|
MSG
|
||||||
|
|
||||||
db_schema_updated = !git.modified_files.grep(%r{\Adb/structure\.sql}).empty?
|
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)
|
markdown(CHANGED_SCHEMA_MESSAGE)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
# Extended Markdown configuration to enforce no-trailing-spaces rule
|
# Extended Markdown configuration to enforce no-trailing-spaces rule
|
||||||
"extends": "../../.markdownlint.yml"
|
"extends": "../../.markdownlint.yml"
|
||||||
"no-trailing-spaces": true
|
"no-trailing-spaces": true
|
||||||
|
|
|
@ -120,6 +120,7 @@ exceptions:
|
||||||
- PUT
|
- PUT
|
||||||
- RAID
|
- RAID
|
||||||
- RAM
|
- RAM
|
||||||
|
- RBAC
|
||||||
- RDP
|
- RDP
|
||||||
- REST
|
- REST
|
||||||
- RFC
|
- RFC
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
# Error: gitlab.HeaderGerunds
|
# Suggestion: gitlab.HeaderGerunds
|
||||||
#
|
#
|
||||||
# Checks for headers that start with gerunds (ing words).
|
# Checks for headers that start with gerunds (ing words).
|
||||||
# Related to: https://docs.gitlab.com/ee/development/documentation/structure.html
|
# Related to: https://docs.gitlab.com/ee/development/documentation/structure.html
|
||||||
|
|
|
@ -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.
|
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).
|
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.
|
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. Scan the QR code.
|
||||||
1. Decide what personal details to share.
|
1. Decide what personal details to share.
|
||||||
1. Sign in to your GitLab installation.
|
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
|
<!-- ## Troubleshooting
|
||||||
|
|
||||||
|
|
|
@ -186,7 +186,8 @@ Gitaly Cluster supports:
|
||||||
- [Strong consistency](praefect.md#strong-consistency) of the secondary replicas.
|
- [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.
|
- [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.
|
- 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)
|
Follow the [Gitaly Cluster epic](https://gitlab.com/groups/gitlab-org/-/epics/1489)
|
||||||
for improvements including
|
for improvements including
|
||||||
|
|
|
@ -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
|
repository. Combined with [configurable replication factors](#configure-replication-factor), you can
|
||||||
horizontally scale storage capacity and distribute write load across Gitaly nodes.
|
horizontally scale storage capacity and distribute write load across Gitaly nodes.
|
||||||
|
|
||||||
Primary elections are run when:
|
Primary elections are run:
|
||||||
|
|
||||||
- Praefect starts up.
|
- In GitLab 14.1 and later, lazily. This means that Praefect doesn't immediately elect
|
||||||
- The cluster's consensus of a Gitaly node's health changes.
|
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
|
- Is healthy. A Gitaly node is considered healthy if `>=50%` Praefect nodes have
|
||||||
previous ten seconds.
|
successfully health checked the Gitaly node in the previous ten seconds.
|
||||||
- Unhealthy otherwise.
|
- 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
|
If there are multiple primary node candidates, Praefect:
|
||||||
an unhealthy primary Gitaly node. The election is made:
|
|
||||||
|
|
||||||
- Randomly from healthy secondary Gitaly nodes that are the most up to date.
|
- Picks one of them randomly.
|
||||||
- Only from Gitaly nodes assigned to the host repository.
|
- 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.
|
- 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.
|
- 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
|
Gitaly Cluster recovers from a failing primary Gitaly node by promoting a healthy secondary as the
|
||||||
new primary.
|
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).
|
- 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.
|
- Elects the secondary with the least unreplicated writes from the primary to be the new
|
||||||
Because there can still be some unreplicated writes, [data loss can occur](#check-for-data-loss).
|
primary. Because there can still be some unreplicated writes,
|
||||||
|
[data loss can occur](#check-for-data-loss).
|
||||||
|
|
||||||
### Read-only mode
|
### Read-only mode
|
||||||
|
|
||||||
> - Introduced in GitLab 13.0 as [generally available](https://about.gitlab.com/handbook/product/gitlab-the-product/#generally-available-ga).
|
> - 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.
|
> - 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.
|
> - [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
|
In GitLab 13.0 to 14.0, when Gitaly Cluster switches to a new primary, repositories enter
|
||||||
date. This can happen after failing over to an outdated secondary. Read-only mode eases data
|
read-only mode if they are out of date. This can happen after failing over to an outdated
|
||||||
recovery efforts by preventing writes that may conflict with the unreplicated writes on other nodes.
|
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. [Check](#check-for-data-loss) for data loss.
|
||||||
1. Attempt to [recover](#data-recovery) missing data.
|
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
|
[accept data loss](#enable-writes-or-accept-data-loss) if necessary, depending on the version of
|
||||||
GitLab.
|
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
|
### Check for data loss
|
||||||
|
|
||||||
The Praefect `dataloss` sub-command identifies replicas that are likely to be outdated. This can help
|
The Praefect `dataloss` subcommand identifies:
|
||||||
identify potential data loss after a failover. The following parameters are
|
|
||||||
available:
|
|
||||||
|
|
||||||
- `-virtual-storage` that specifies which virtual storage to check. The default behavior is to
|
- Copies of repositories in GitLab 13.0 to GitLab 14.0 that at are likely to be outdated.
|
||||||
display outdated replicas of read-only repositories as they might require administrator action.
|
This can help identify potential data loss after a failover.
|
||||||
- In GitLab 13.3 and later, `-partially-replicated` that specifies whether to display a list of
|
- Repositories in GitLab 14.1 and later that are unavailable. This helps identify potential
|
||||||
[outdated replicas of writable repositories](#outdated-replicas-of-writable-repositories).
|
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. 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:
|
NOTE:
|
||||||
`dataloss` is still in beta and the output format is subject to change.
|
`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
|
```shell
|
||||||
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss [-virtual-storage <virtual-storage>]
|
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
|
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
|
Repositories are listed in the output that have either:
|
||||||
in the output. This information is printed for each repository:
|
|
||||||
|
- 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
|
- A repository's relative path to the storage directory identifies each repository and groups the related
|
||||||
information.
|
information.
|
||||||
- The repository's current status is printed in parentheses next to the disk path. If the repository's primary
|
- The repository's current status is printed in parentheses next to the disk path:
|
||||||
is outdated, the repository is in `read-only` mode and can't accept writes. Otherwise, the mode is `writable`.
|
- 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
|
- The primary field lists the repository's current primary. If the repository has no primary, the field shows
|
||||||
`No Primary`.
|
`No Primary`.
|
||||||
- The In-Sync Storages lists replicas which have replicated the latest successful write and all writes
|
- 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
|
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.
|
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
|
Additional information includes:
|
||||||
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
|
- Whether a node is assigned to host the repository is listed with each node's status.
|
||||||
act as replication sources to bring assigned replicas up to date.
|
`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:
|
Example output:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
Virtual storage: default
|
Virtual storage: default
|
||||||
Outdated repositories:
|
Outdated repositories:
|
||||||
@hashed/3f/db/3fdba35f04dc8c462986c992bcf875546257113072a909c162f7e470e581e278.git (read-only):
|
@hashed/3f/db/3fdba35f04dc8c462986c992bcf875546257113072a909c162f7e470e581e278.git (unavailable):
|
||||||
Primary: gitaly-1
|
Primary: gitaly-1
|
||||||
In-Sync Storages:
|
In-Sync Storages:
|
||||||
gitaly-2, assigned host
|
gitaly-2, assigned host, unhealthy
|
||||||
Outdated Storages:
|
Outdated Storages:
|
||||||
gitaly-1 is behind by 3 changes or less, assigned host
|
gitaly-1 is behind by 3 changes or less, assigned host
|
||||||
gitaly-3 is behind by 3 changes or less
|
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
|
```shell
|
||||||
Virtual storage: default
|
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
|
To also list information of repositories which are available but are unavailable from some of the assigned nodes,
|
||||||
replicas are outdated, use the `-partially-replicated` flag.
|
use the `-partially-unavailable` flag.
|
||||||
|
|
||||||
A repository is writable if the primary has the latest changes. Secondaries might be temporarily
|
A repository is available if there is a healthy, up to date replica available. Some of the assigned secondary
|
||||||
outdated while they are waiting to replicate the latest changes.
|
replicas may be temporarily unavailable for access while they are waiting to replicate the latest changes.
|
||||||
|
|
||||||
```shell
|
```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:
|
Example output:
|
||||||
|
@ -1462,7 +1513,7 @@ Example output:
|
||||||
```shell
|
```shell
|
||||||
Virtual storage: default
|
Virtual storage: default
|
||||||
Outdated repositories:
|
Outdated repositories:
|
||||||
@hashed/3f/db/3fdba35f04dc8c462986c992bcf875546257113072a909c162f7e470e581e278.git (writable):
|
@hashed/3f/db/3fdba35f04dc8c462986c992bcf875546257113072a909c162f7e470e581e278.git:
|
||||||
Primary: gitaly-1
|
Primary: gitaly-1
|
||||||
In-Sync Storages:
|
In-Sync Storages:
|
||||||
gitaly-1, assigned host
|
gitaly-1, assigned host
|
||||||
|
@ -1471,14 +1522,14 @@ Virtual storage: default
|
||||||
gitaly-3 is behind by 3 changes or less
|
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
|
With the `-partially-unavailable` flag set, a confirmation is printed out if every assigned replica is fully up to
|
||||||
date.
|
date and healthy.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
Virtual storage: default
|
Virtual storage: default
|
||||||
All repositories are up to date!
|
All repositories are fully available on all assigned storages!
|
||||||
```
|
```
|
||||||
|
|
||||||
### Check repository checksums
|
### Check repository checksums
|
||||||
|
@ -1486,30 +1537,50 @@ Virtual storage: default
|
||||||
To check a project's repository checksums across on all Gitaly nodes, run the
|
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.
|
[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
|
### 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
|
Praefect provides the following subcommands to re-enable writes or accept data loss:
|
||||||
recovery attempts.
|
|
||||||
|
|
||||||
```shell
|
- In GitLab 13.2 and earlier, `enable-writes` to re-enable virtual storage for writes after
|
||||||
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml enable-writes -virtual-storage <virtual-storage>
|
data recovery attempts:
|
||||||
```
|
|
||||||
|
|
||||||
- [In GitLab 13.3](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2415) and later,
|
```shell
|
||||||
`accept-dataloss` to accept data loss and re-enable writes for repositories after data recovery
|
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml enable-writes -virtual-storage <virtual-storage>
|
||||||
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.
|
- 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
|
```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>
|
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:
|
When accepting data loss, Praefect:
|
||||||
`accept-dataloss` causes permanent data loss by overwriting other versions of the repository. Data
|
|
||||||
[recovery efforts](#data-recovery) must be performed before using it.
|
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
|
## Data recovery
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ milestone: "<%= milestone %>"
|
||||||
introduced_by_url:
|
introduced_by_url:
|
||||||
time_frame: <%= time_frame %>
|
time_frame: <%= time_frame %>
|
||||||
data_source:
|
data_source:
|
||||||
data_category: Operational
|
data_category: Optional
|
||||||
distribution:
|
distribution:
|
||||||
<%= distribution %>
|
<%= distribution %>
|
||||||
tier:
|
tier:
|
||||||
|
|
|
@ -48,7 +48,10 @@ module QA
|
||||||
Resource::Issue.fabricate_via_api!.visit!
|
Resource::Issue.fabricate_via_api!.visit!
|
||||||
end
|
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|
|
Page::Project::Issue::Show.perform do |show|
|
||||||
show.comment('See attached image for scale', attachment: file_to_attach)
|
show.comment('See attached image for scale', attachment: file_to_attach)
|
||||||
|
|
||||||
|
|
|
@ -81,14 +81,14 @@ RSpec.describe 'Dashboard Issues filtering', :js do
|
||||||
sort_by('Created date')
|
sort_by('Created date')
|
||||||
visit_issues(assignee_username: user.username)
|
visit_issues(assignee_username: user.username)
|
||||||
|
|
||||||
expect(find('.issues-filters')).to have_content('Created date')
|
expect(page).to have_button('Created date')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'keeps sorting issues after visiting Projects Issues page' do
|
it 'keeps sorting issues after visiting Projects Issues page' do
|
||||||
sort_by('Created date')
|
sort_by('Created date')
|
||||||
visit project_issues_path(project)
|
visit project_issues_path(project)
|
||||||
|
|
||||||
expect(find('.issues-filters')).to have_content('Created date')
|
expect(page).to have_button('Created date')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,11 @@ RSpec.describe "User sorts issues" do
|
||||||
create(:award_emoji, :upvote, awardable: issue2)
|
create(:award_emoji, :upvote, awardable: issue2)
|
||||||
|
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
|
|
||||||
visit(project_issues_path(project))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'keeps the sort option' do
|
it 'keeps the sort option' do
|
||||||
|
visit(project_issues_path(project))
|
||||||
|
|
||||||
find('.filter-dropdown-container .dropdown').click
|
find('.filter-dropdown-container .dropdown').click
|
||||||
|
|
||||||
page.within('ul.dropdown-menu.dropdown-menu-right li') do
|
page.within('ul.dropdown-menu.dropdown-menu-right li') do
|
||||||
|
@ -47,11 +47,10 @@ RSpec.describe "User sorts issues" do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sorts by popularity', :js do
|
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_button 'Created date'
|
||||||
click_link("Popularity")
|
click_on 'Popularity'
|
||||||
end
|
|
||||||
|
|
||||||
page.within(".issues-list") do
|
page.within(".issues-list") do
|
||||||
page.within("li.issue:nth-child(1)") do
|
page.within("li.issue:nth-child(1)") do
|
||||||
|
@ -129,7 +128,7 @@ RSpec.describe "User sorts issues" do
|
||||||
it 'filters by none' do
|
it 'filters by none' do
|
||||||
visit project_issues_path(project, due_date: Issue::NoDueDate.name)
|
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('foo')
|
||||||
expect(page).not_to have_content('bar')
|
expect(page).not_to have_content('bar')
|
||||||
expect(page).to have_content('baz')
|
expect(page).to have_content('baz')
|
||||||
|
@ -139,7 +138,7 @@ RSpec.describe "User sorts issues" do
|
||||||
it 'filters by any' do
|
it 'filters by any' do
|
||||||
visit project_issues_path(project, due_date: Issue::AnyDueDate.name)
|
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('foo')
|
||||||
expect(page).to have_content('bar')
|
expect(page).to have_content('bar')
|
||||||
expect(page).to have_content('baz')
|
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)
|
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('foo')
|
||||||
expect(page).to have_content('bar')
|
expect(page).to have_content('bar')
|
||||||
expect(page).not_to have_content('baz')
|
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)
|
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('foo')
|
||||||
expect(page).to have_content('bar')
|
expect(page).to have_content('bar')
|
||||||
expect(page).not_to have_content('baz')
|
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)
|
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('foo')
|
||||||
expect(page).not_to have_content('bar')
|
expect(page).not_to have_content('bar')
|
||||||
expect(page).to have_content('baz')
|
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)
|
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('foo')
|
||||||
expect(page).not_to have_content('bar')
|
expect(page).not_to have_content('bar')
|
||||||
expect(page).to have_content('baz')
|
expect(page).to have_content('baz')
|
||||||
|
|
|
@ -60,7 +60,7 @@ RSpec.describe 'User sorts merge requests' do
|
||||||
|
|
||||||
visit(project_issues_path(project))
|
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
|
end
|
||||||
|
|
||||||
context 'when merge requests have awards' do
|
context 'when merge requests have awards' do
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import $ from 'jquery';
|
|
||||||
import { getTimeago, localTimeAgo, timeFor } from '~/lib/utils/datetime/timeago_utility';
|
import { getTimeago, localTimeAgo, timeFor } from '~/lib/utils/datetime/timeago_utility';
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
import '~/commons/bootstrap';
|
import '~/commons/bootstrap';
|
||||||
|
@ -81,16 +80,16 @@ describe('TimeAgo utils', () => {
|
||||||
`With User Setting timeDisplayRelative: $timeDisplayRelative`,
|
`With User Setting timeDisplayRelative: $timeDisplayRelative`,
|
||||||
({ timeDisplayRelative, text }) => {
|
({ timeDisplayRelative, text }) => {
|
||||||
it.each`
|
it.each`
|
||||||
timeagoArg | title
|
updateTooltip | title
|
||||||
${false} | ${'some time'}
|
${false} | ${'some time'}
|
||||||
${true} | ${'Feb 18, 2020 10:22pm UTC'}
|
${true} | ${'Feb 18, 2020 10:22pm UTC'}
|
||||||
`(
|
`(
|
||||||
`has content: '${text}' and tooltip: '$title' with timeagoArg = $timeagoArg`,
|
`has content: '${text}' and tooltip: '$title' with updateTooltip = $updateTooltip`,
|
||||||
({ timeagoArg, title }) => {
|
({ updateTooltip, title }) => {
|
||||||
window.gon = { time_display_relative: timeDisplayRelative };
|
window.gon = { time_display_relative: timeDisplayRelative };
|
||||||
|
|
||||||
const element = document.querySelector('time');
|
const element = document.querySelector('time');
|
||||||
localTimeAgo($(element), timeagoArg);
|
localTimeAgo([element], updateTooltip);
|
||||||
|
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
|
||||||
|
|
||||||
<oncall-schedules-list-stub
|
<oncall-schedules-list-stub
|
||||||
schedules="schedule1,schedule2"
|
schedules="schedule1,schedule2"
|
||||||
|
username="username"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -18,7 +18,7 @@ const mockSchedules = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const userName = 'User 1';
|
const userName = "O'User";
|
||||||
|
|
||||||
describe('On-call schedules list', () => {
|
describe('On-call schedules list', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
|
@ -6,7 +6,7 @@ RSpec::Matchers.define :have_issuable_counts do |opts|
|
||||||
end
|
end
|
||||||
|
|
||||||
match do |actual|
|
match do |actual|
|
||||||
actual.within '.issues-state-filters' do
|
actual.within '.top-area' do
|
||||||
expected_counts.each do |expected_count|
|
expected_counts.each do |expected_count|
|
||||||
expect(actual).to have_content(expected_count)
|
expect(actual).to have_content(expected_count)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue