Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-13 06:10:00 +00:00
parent 58f103ba8f
commit 33a43bde0e
68 changed files with 849 additions and 155 deletions

2
.nvmrc
View File

@ -1 +1 @@
16.14.0
16.15.0

View File

@ -1 +1 @@
15.1.0
15.2.0

View File

@ -93,7 +93,7 @@ gem 'gpgme', '~> 2.0.19'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
gem 'gitlab_omniauth-ldap', '~> 2.1.1', require: 'omniauth-ldap'
gem 'gitlab_omniauth-ldap', '~> 2.2.0', require: 'omniauth-ldap'
gem 'net-ldap', '~> 0.16.3'
# API

View File

@ -549,9 +549,9 @@ GEM
rubocop-rspec (~> 1.44)
gitlab_chronic_duration (0.10.6.2)
numerizer (~> 0.2)
gitlab_omniauth-ldap (2.1.1)
gitlab_omniauth-ldap (2.2.0)
net-ldap (~> 0.16)
omniauth (~> 1.3)
omniauth (>= 1.3, < 3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
globalid (1.0.0)
@ -1197,7 +1197,7 @@ GEM
ruby2_keywords (0.0.5)
ruby_parser (3.15.0)
sexp_processor (~> 4.9)
rubyntlm (0.6.2)
rubyntlm (0.6.3)
rubypants (0.2.0)
rubyzip (2.3.2)
rugged (1.2.0)
@ -1573,7 +1573,7 @@ DEPENDENCIES
gitlab-sidekiq-fetcher (= 0.8.0)
gitlab-styles (~> 7.1.0)
gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.1.1)
gitlab_omniauth-ldap (~> 2.2.0)
gon (~> 6.4.0)
google-api-client (~> 0.33)
google-protobuf (~> 3.19.0)

View File

@ -0,0 +1,9 @@
#import "../fragments/user.fragment.graphql"
query getUsersByUsernames($usernames: [String!]) {
users(usernames: $usernames) {
nodes {
...User
}
}
}

View File

@ -0,0 +1,9 @@
#import "../fragments/user.fragment.graphql"
query searchAllUsers($search: String!, $first: Int = null) {
users(search: $search, first: $first) {
nodes {
...User
}
}
}

View File

@ -84,7 +84,7 @@ export default {
<gl-icon
v-if="hasState"
ref="iconElementXL"
class="mr-2 d-block"
class="gl-mr-3"
:class="iconClasses"
:name="iconName"
:title="stateTitle"

View File

@ -0,0 +1,14 @@
import { s__ } from '~/locale';
export const timelineTabI18n = Object.freeze({
title: s__('Incident|Timeline'),
emptyDescription: s__('Incident|No timeline items have been added yet.'),
addEventButton: s__('Incident|Add new timeline event'),
});
export const timelineFormI18n = Object.freeze({
createError: s__('Incident|Error creating incident timeline event: %{error}'),
areaPlaceholder: s__('Incident|Timeline text...'),
saveAndAdd: s__('Incident|Save and add another event'),
areaLabel: s__('Incident|Timeline text'),
});

View File

@ -0,0 +1,13 @@
mutation CreateTimelineEvent($input: TimelineEventCreateInput!) {
timelineEventCreate(input: $input) {
timelineEvent {
id
note
noteHtml
action
occurredAt
createdAt
}
errors
}
}

View File

@ -4,17 +4,11 @@ query GetTimelineEvents($fullPath: ID!, $incidentId: IssueID!) {
incidentManagementTimelineEvents(incidentId: $incidentId) {
nodes {
id
author {
id
name
username
}
note
noteHtml
action
occurredAt
createdAt
updatedAt
}
}
}

View File

@ -0,0 +1,252 @@
<script>
import { GlDatepicker, GlFormInput, GlFormGroup, GlButton, GlIcon } from '@gitlab/ui';
import { produce } from 'immer';
import { sortBy } from 'lodash';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPE_ISSUE } from '~/graphql_shared/constants';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { createAlert } from '~/flash';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import { sprintf } from '~/locale';
import { displayAndLogError, getUtcShiftedDateNow } from './utils';
import { timelineFormI18n } from './constants';
import CreateTimelineEvent from './graphql/queries/create_timeline_event.mutation.graphql';
import getTimelineEvents from './graphql/queries/get_timeline_events.query.graphql';
export default {
name: 'IncidentTimelineEventForm',
restrictedToolBarItems: [
'quote',
'strikethrough',
'bullet-list',
'numbered-list',
'task-list',
'collapsible-section',
'table',
'full-screen',
],
components: {
MarkdownField,
GlDatepicker,
GlFormInput,
GlFormGroup,
GlButton,
GlIcon,
},
i18n: timelineFormI18n,
directives: {
autofocusonshow,
},
inject: ['fullPath', 'issuableId'],
props: {
hasTimelineEvents: {
type: Boolean,
required: true,
},
},
data() {
// Create shifted date to force the datepicker to format in UTC
const utcShiftedDate = getUtcShiftedDateNow();
return {
currentDate: utcShiftedDate,
currentHour: utcShiftedDate.getHours(),
currentMinute: utcShiftedDate.getMinutes(),
timelineText: '',
createTimelineEventActive: false,
datepickerTextInput: null,
};
},
methods: {
hideIncidentTimelineEventForm() {
this.$emit('hide-incident-timeline-event-form');
},
focusDate() {
this.$refs.datepicker.$el.focus();
},
updateCache(store, { data }) {
const { timelineEvent: event, errors } = data?.timelineEventCreate || {};
if (errors.length) {
return;
}
const variables = {
incidentId: convertToGraphQLId(TYPE_ISSUE, this.issuableId),
fullPath: this.fullPath,
};
const sourceData = store.readQuery({
query: getTimelineEvents,
variables,
});
const newData = produce(sourceData, (draftData) => {
const { nodes: draftEventList } = draftData.project.incidentManagementTimelineEvents;
draftEventList.push(event);
// ISOStrings sort correctly in lexical order
const sortedEvents = sortBy(draftEventList, 'occurredAt');
draftData.project.incidentManagementTimelineEvents.nodes = sortedEvents;
});
store.writeQuery({
query: getTimelineEvents,
variables,
data: newData,
});
},
createIncidentTimelineEvent(addOneEvent) {
this.createTimelineEventActive = true;
return this.$apollo
.mutate({
mutation: CreateTimelineEvent,
variables: {
input: {
incidentId: convertToGraphQLId(TYPE_ISSUE, this.issuableId),
note: this.timelineText,
occurredAt: this.createDateString(),
},
},
update: this.updateCache,
})
.then(({ data = {} }) => {
const errors = data.timelineEventCreate?.errors;
if (errors.length) {
createAlert({
message: sprintf(this.$options.i18n.createError, { error: errors.join('. ') }, false),
});
}
})
.catch(displayAndLogError)
.finally(() => {
this.createTimelineEventActive = false;
this.timelineText = '';
if (addOneEvent) {
this.hideIncidentTimelineEventForm();
}
});
},
createDateString() {
const [years, months, days] = this.datepickerTextInput.split('-');
const utcDate = new Date(
Date.UTC(years, months - 1, days, this.currentHour, this.currentMinute),
);
return utcDate.toISOString();
},
},
};
</script>
<template>
<div
class="gl-relative gl-display-flex gl-align-items-center"
:class="{ 'timeline-entry-vertical-line': hasTimelineEvents }"
>
<div
v-if="hasTimelineEvents"
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-align-self-start gl-bg-white gl-text-gray-200 gl-border-gray-100 gl-border-1 gl-border-solid gl-rounded-full gl-mt-2 gl-mr-3 gl-w-8 gl-h-8 gl-z-index-1"
>
<gl-icon name="comment" class="note-icon" />
</div>
<form class="gl-flex-grow-1" :class="{ 'gl-border-t': hasTimelineEvents }">
<div
class="gl-display-flex gl-flex-direction-column gl-sm-flex-direction-row datetime-picker"
>
<gl-form-group :label="__('Date')" class="gl-mt-3 gl-mr-3">
<gl-datepicker id="incident-date" #default="{ formattedDate }" v-model="currentDate">
<gl-form-input
id="incident-date"
ref="datepicker"
v-model="datepickerTextInput"
class="gl-datepicker-input gl-pr-7!"
:value="formattedDate"
:placeholder="__('YYYY-MM-DD')"
@keydown.enter="onKeydown"
/>
</gl-datepicker>
</gl-form-group>
<div class="gl-display-flex gl-mt-3">
<gl-form-group :label="__('Time')">
<div class="gl-display-flex">
<label label-for="timeline-input-hours" class="sr-only"></label>
<gl-form-input
id="timeline-input-hours"
v-model="currentHour"
data-testid="input-hours"
size="xs"
type="number"
min="00"
max="23"
/>
<label label-for="timeline-input-minutes" class="sr-only"></label>
<gl-form-input
id="timeline-input-minutes"
v-model="currentMinute"
class="gl-ml-3"
data-testid="input-minutes"
size="xs"
type="number"
min="00"
max="59"
/>
</div>
</gl-form-group>
<p class="gl-ml-3 gl-align-self-end gl-line-height-32">{{ __('UTC') }}</p>
</div>
</div>
<div class="common-note-form">
<gl-form-group :label="$options.i18n.areaLabel">
<markdown-field
:can-attach-file="false"
:add-spacing-classes="false"
:show-comment-tool-bar="false"
:textarea-value="timelineText"
:restricted-tool-bar-items="$options.restrictedToolBarItems"
markdown-docs-path=""
class="bordered-box gl-mt-0"
>
<template #textarea>
<textarea
v-model="timelineText"
class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto"
data-supports-quick-actions="false"
:aria-label="__('Description')"
:placeholder="$options.i18n.areaPlaceholder"
>
</textarea>
</template>
</markdown-field>
</gl-form-group>
</div>
<gl-form-group class="gl-mb-0">
<gl-button
variant="confirm"
category="primary"
class="gl-mr-3"
:loading="createTimelineEventActive"
@click="createIncidentTimelineEvent(true)"
>
{{ __('Save') }}
</gl-button>
<gl-button
variant="confirm"
category="secondary"
class="gl-mr-3 gl-ml-n2"
:loading="createTimelineEventActive"
@click="createIncidentTimelineEvent(false)"
>
{{ $options.i18n.saveAndAdd }}
</gl-button>
<gl-button
class="gl-ml-n2"
:disabled="createTimelineEventActive"
@click="hideIncidentTimelineEventForm"
>
{{ __('Cancel') }}
</gl-button>
<div class="gl-border-b gl-pt-5"></div>
</gl-form-group>
</form>
</div>
</template>

View File

@ -1,23 +1,29 @@
<script>
import { GlEmptyState, GlLoadingIcon, GlTab } from '@gitlab/ui';
import { GlButton, GlEmptyState, GlLoadingIcon, GlTab } from '@gitlab/ui';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPE_ISSUE } from '~/graphql_shared/constants';
import { fetchPolicies } from '~/lib/graphql';
import getTimelineEvents from './graphql/queries/get_timeline_events.query.graphql';
import { displayAndLogError } from './utils';
import { timelineTabI18n } from './constants';
import IncidentTimelineEventForm from './timeline_events_form.vue';
import IncidentTimelineEventsList from './timeline_events_list.vue';
export default {
components: {
GlButton,
GlEmptyState,
GlLoadingIcon,
GlTab,
IncidentTimelineEventForm,
IncidentTimelineEventsList,
},
inject: ['fullPath', 'issuableId'],
i18n: timelineTabI18n,
inject: ['canUpdate', 'fullPath', 'issuableId'],
data() {
return {
isEventFormVisible: false,
timelineEvents: [],
};
},
@ -50,21 +56,42 @@ export default {
return !this.timelineEventLoading && !this.hasTimelineEvents;
},
},
methods: {
hideEventForm() {
this.isEventFormVisible = false;
},
async showEventForm() {
this.isEventFormVisible = true;
await this.$nextTick();
this.$refs.eventForm.focusDate();
},
},
};
</script>
<template>
<gl-tab :title="s__('Incident|Timeline')">
<gl-tab :title="$options.i18n.title">
<gl-loading-icon v-if="timelineEventLoading" size="lg" color="dark" class="gl-mt-5" />
<gl-empty-state
v-else-if="showEmptyState"
:compact="true"
:description="s__('Incident|No timeline items have been added yet.')"
:description="$options.i18n.emptyDescription"
/>
<incident-timeline-events-list
v-if="hasTimelineEvents"
:timeline-event-loading="timelineEventLoading"
:timeline-events="timelineEvents"
/>
<incident-timeline-event-form
v-show="isEventFormVisible"
ref="eventForm"
:has-timeline-events="hasTimelineEvents"
class="timeline-event-note timeline-event-note-form"
:class="{ 'gl-pl-0': !hasTimelineEvents }"
@hide-incident-timeline-event-form="hideEventForm"
/>
<gl-button v-if="canUpdate" variant="default" class="gl-mb-3 gl-mt-7" @click="showEventForm">
{{ $options.i18n.addEventButton }}
</gl-button>
</gl-tab>
</template>

View File

@ -18,3 +18,15 @@ const EVENT_ICONS = {
export const getEventIcon = (actionName) => {
return EVENT_ICONS[actionName] ?? EVENT_ICONS.default;
};
/**
* Returns a date shifted by the current timezone offset. Allows
* date.getHours() and similar to return UTC values.
*
* @returns {Date}
*/
export const getUtcShiftedDateNow = () => {
const date = new Date();
date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
return date;
};

View File

@ -8,10 +8,6 @@ import MrWidgetOptions from 'ee_else_ce/vue_merge_request_widget/mr_widget_optio
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import Translate from '../vue_shared/translate';
import { registerExtension } from './components/extensions';
import issuesExtension from './extensions/issues';
registerExtension(issuesExtension);
Vue.use(Translate);
Vue.use(VueApollo);

View File

@ -991,4 +991,19 @@
bottom: -10%;
}
}
&.timeline-event-note-form {
&::before {
top: -15% !important; // Override default positioning
height: 20%;
}
&::after {
content: none;
}
}
}
.timeline-event-note-form {
padding-left: 20px;
}

View File

@ -1,4 +1,4 @@
= form_errors(integration)
= form_errors(integration, pajamas_alert: true)
%div{ data: { testid: "integration-settings-form" } }
- if @default_integration

View File

@ -33,7 +33,7 @@ For more information, see the links shown on this page for each external provide
| **Provider-to-GitLab Role Sync** | SAML Group Sync | LDAP Group Sync<br>SAML Group Sync ([GitLab 15.1](https://gitlab.com/gitlab-org/gitlab/-/issues/285150) and later) |
| **User Removal** | SCIM (remove user from top-level group) | LDAP (remove user from groups and block from the instance) |
1. Using Just-In-Time (JIT) provisioning, user accounts are created when the user first signs in.
1. Using Just-In-Time (JIT) provisioning, user accounts are created when the user first signs in.
## Change apps or configuration

View File

@ -158,7 +158,7 @@ If the **primary** and **secondary** sites have a checksum verification mismatch
```shell
cd /var/opt/gitlab/git-data/repositories
```
1. Run the following command on the **primary** site, redirecting the output to a file:
```shell

View File

@ -157,7 +157,7 @@ To configure the _Gitaly token_, edit `/etc/gitlab/gitlab.rb`:
```ruby
gitaly['auth_token'] = 'abc123secret'
```
There are two ways to configure the _GitLab Shell token_.
Method 1 (recommended):

View File

@ -331,7 +331,7 @@ downtime. Otherwise, skip to the next section.
exit
```
GDB reports an error if the Puma process terminates before you can run these commands.
GDB reports an error if the Puma process terminates before you can run these commands.
To buy more time, you can always raise the
Puma worker timeout. For omnibus users, you can edit `/etc/gitlab/gitlab.rb` and
increase it from 60 seconds to 600:

View File

@ -13,7 +13,7 @@ PostgreSQL, and Gitaly instances.
By default, GitLab uses UNIX sockets and is not set up to communicate via TCP. To change this:
1. Edit the `/etc/gitlab/gitlab.rb` file on your GitLab instance and add the following:
1. Edit the `/etc/gitlab/gitlab.rb` file on your GitLab instance and add the following:
```ruby
@ -55,7 +55,7 @@ By default, GitLab uses UNIX sockets and is not set up to communicate via TCP. T
```shell
sudo gitlab-ctl reconfigure
```
1. Restart the `PostgreSQL` server:
```shell
@ -66,7 +66,7 @@ By default, GitLab uses UNIX sockets and is not set up to communicate via TCP. T
```ruby
gitlab_rails['auto_migrate'] = true
```
```
1. Run `reconfigure` again:

View File

@ -22,7 +22,7 @@ To use GitLab CI/CD with a Bitbucket Cloud repository:
- For **Git repository URL**, use the URL from the **Clone this repository** panel in Bitbucket.
- Leave the username blank.
- You can generate and use a [Bitbucket App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) for the password field.
GitLab imports the repository and enables [Pull Mirroring](../../user/project/repository/mirror/pull.md).
You can check that mirroring is working in the project by going to **Settings > Repository > Mirroring repositories**.

View File

@ -406,8 +406,11 @@ When you stop an environment:
to the list of **Stopped** environments.
- An [`on_stop` action](../yaml/index.md#environmenton_stop), if defined, is executed.
Dynamic environments stop automatically when their associated branch is
deleted.
There are multiple ways to clean up [dynamic environments](#create-a-dynamic-environment):
- If you use [merge request pipelines](../pipelines/merge_request_pipelines.md), GitLab stops an environment [when a merge request is merged or closed](#stop-an-environment-when-a-merge-request-is-merged-or-closed).
- If you do _NOT_ use [merge request pipelines](../pipelines/merge_request_pipelines.md), GitLab stops an environment [when the associated feature branch is deleted](#stop-an-environment-when-a-branch-is-deleted).
- If you set [an expiry period to an environment](../yaml/index.md#environmentauto_stop_in), GitLab stops an environment [when it's expired](#stop-an-environment-after-a-certain-time-period).
#### Stop an environment when a branch is deleted
@ -425,8 +428,6 @@ deploy_review:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_ENVIRONMENT_SLUG.example.com
on_stop: stop_review
rules:
- if: $CI_MERGE_REQUEST_ID
stop_review:
stage: deploy
@ -435,9 +436,7 @@ stop_review:
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
rules:
- if: $CI_MERGE_REQUEST_ID
when: manual
when: manual
```
Both jobs must have the same [`rules`](../yaml/index.md#rules)
@ -455,6 +454,39 @@ try to check out the code after the branch is deleted.
Read more in the [`.gitlab-ci.yml` reference](../yaml/index.md#environmenton_stop).
#### Stop an environment when a merge request is merged or closed
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/60885) in GitLab 11.10.
You can configure environments to stop when a merge request is merged or closed.
This stop trigger is automatically enabled when you use [merge request pipelines](../pipelines/merge_request_pipelines.md).
The following example shows a `deploy_review` job that calls a `stop_review` job
to clean up and stop the environment.
```yaml
deploy_review:
stage: deploy
script:
- echo "Deploy a review app"
environment:
name: review/$CI_COMMIT_REF_SLUG
on_stop: stop_review
rules:
- if: $CI_MERGE_REQUEST_ID
stop_review:
stage: deploy
script:
- echo "Remove review app"
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
rules:
- if: $CI_MERGE_REQUEST_ID
when: manual
```
#### Stop an environment when another job is finished
You can set an environment to stop when another job is finished.

View File

@ -85,6 +85,15 @@ place for it.
Do not include the same information in multiple places.
[Link to a single source of truth instead.](../styleguide/index.md#link-instead-of-repeating-text)
For example, if you have code in a repository other than the [primary repositories](index.md#architecture),
and documentation in the same repository, you can keep the documentation in that repository.
Then you can either:
- Publish it to <https://docs.gitlab.com>.
- Link to it from <https://docs.gitlab.com> by adding an entry in the global navigation.
View [an example](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/fedb6378a3c92274ba3b6031df0d34455594e4cc/content/_data/navigation.yaml#L2944).
## References across documents
- Give each folder an `index.md` page that introduces the topic, and both introduces

View File

@ -64,10 +64,9 @@ end
## Allow use of licensed EE features
To enable plans per namespace turn on the `Allow use of licensed EE features` option from the settings page.
This will make licensed EE features available to projects only if the project namespace's plan includes the feature
To enable plans per namespace turn on the `Allow use of licensed EE features` option from the settings page.
This will make licensed EE features available to projects only if the project namespace's plan includes the feature
or if the project is public. To enable it:
1. If you are developing locally, follow the steps in [simulate SaaS](ee_features.md#act-as-saas) to make the option available.
1. Visit Admin > Settings > General > "Account and limit" and
enabling "Allow use of licensed EE features".
1. Select Admin > Settings > General > "Account and limit" and enable "Allow use of licensed EE features".

View File

@ -156,7 +156,7 @@ To configure your agent, add content to the `config.yaml` file:
- For a GitOps workflow, [view the configuration reference](../gitops.md#gitops-configuration-reference).
- For a GitLab CI/CD workflow, [authorize the agent to access your projects](../ci_cd_workflow.md#authorize-the-agent). Then
[add `kubectl` commands to your `.gitlab-ci.yml` file](../ci_cd_workflow.md#update-your-gitlab-ciyml-file-to-run-kubectl-commands).
[add `kubectl` commands to your `.gitlab-ci.yml` file](../ci_cd_workflow.md#update-your-gitlab-ciyml-file-to-run-kubectl-commands).
## Install multiple agents in your cluster

View File

@ -146,7 +146,7 @@ If you encounter an error with [Yarn](https://classic.yarnpkg.com/en/), view
#### Instance-level npm endpoint
NOTE:
Note: Using `CI_JOB_TOKEN` to install npm packages with dependencies in another project will give you 404 errors. You can use a [personal access token](../../profile/personal_access_tokens.md) as a workaround. [GitLab-#352962](https://gitlab.com/gitlab-org/gitlab/-/issues/352962) proposes a fix to this bug.
Note: Using `CI_JOB_TOKEN` to install npm packages with dependencies in another project will give you 404 errors. You can use a [personal access token](../../profile/personal_access_tokens.md) as a workaround. [GitLab-#352962](https://gitlab.com/gitlab-org/gitlab/-/issues/352962) proposes a fix to this bug.
To use the [instance-level](#use-the-gitlab-endpoint-for-npm-packages) npm endpoint, set your npm configuration:

View File

@ -2732,24 +2732,6 @@ msgstr ""
msgid "AdminSettings|Limit the number of namespaces and projects that can be indexed."
msgstr ""
msgid "AdminSettings|Max number of repository downloads is not a number"
msgstr ""
msgid "AdminSettings|Max number of repository downloads must be greater than or equal to 0"
msgstr ""
msgid "AdminSettings|Max number of repository downloads must be less than or equal to 10000"
msgstr ""
msgid "AdminSettings|Max number of repository downloads within time period is not a number"
msgstr ""
msgid "AdminSettings|Max number of repository downloads within time period must be greater than or equal to 0"
msgstr ""
msgid "AdminSettings|Max number of repository downloads within time period must be less than or equal to 864000"
msgstr ""
msgid "AdminSettings|Maximum duration of a session for Git operations when 2FA is enabled."
msgstr ""
@ -2783,9 +2765,6 @@ msgstr ""
msgid "AdminSettings|No required pipeline"
msgstr ""
msgid "AdminSettings|Number of repositories"
msgstr ""
msgid "AdminSettings|Only enable search after installing the plugin, enabling indexing, and recreating the index."
msgstr ""
@ -2804,9 +2783,6 @@ msgstr ""
msgid "AdminSettings|Registration Features include:"
msgstr ""
msgid "AdminSettings|Reporting time period (seconds)"
msgstr ""
msgid "AdminSettings|Require users to prove ownership of custom domains"
msgstr ""
@ -2870,9 +2846,6 @@ msgstr ""
msgid "AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire."
msgstr ""
msgid "AdminSettings|The maximum number of unique repositories a user can download in the specified time period before they're banned."
msgstr ""
msgid "AdminSettings|The projects in this group can be selected as templates for new projects created on the instance. %{link_start}Learn more.%{link_end} "
msgstr ""
@ -4289,6 +4262,9 @@ msgstr ""
msgid "An error occurred while retrieving projects."
msgstr ""
msgid "An error occurred while retrieving your settings. Reload the page to try again."
msgstr ""
msgid "An error occurred while saving changes: %{error}"
msgstr ""
@ -4297,6 +4273,9 @@ msgid_plural "An error occurred while saving the settings"
msgstr[0] ""
msgstr[1] ""
msgid "An error occurred while saving your settings. Try saving them again."
msgstr ""
msgid "An error occurred while subscribing to notifications."
msgstr ""
@ -4504,6 +4483,9 @@ msgstr ""
msgid "Application settings saved successfully"
msgstr ""
msgid "Application settings saved successfully."
msgstr ""
msgid "Application settings update failed"
msgstr ""
@ -17371,6 +17353,42 @@ msgstr ""
msgid "Git version"
msgstr ""
msgid "GitAbuse|Excluded users"
msgstr ""
msgid "GitAbuse|Number of repositories"
msgstr ""
msgid "GitAbuse|Number of repositories can't be blank. Set to 0 for no limit."
msgstr ""
msgid "GitAbuse|Number of repositories must be a number."
msgstr ""
msgid "GitAbuse|Number of repositories should be between %{minNumRepos}-%{maxNumRepos}."
msgstr ""
msgid "GitAbuse|Reporting time period (seconds)"
msgstr ""
msgid "GitAbuse|Reporting time period can't be blank. Set to 0 for no limit."
msgstr ""
msgid "GitAbuse|Reporting time period must be a number."
msgstr ""
msgid "GitAbuse|Reporting time period should be between %{minTimePeriod}-%{maxTimePeriod} seconds."
msgstr ""
msgid "GitAbuse|The maximum number of unique repositories a user can download in the specified time period before they're banned."
msgstr ""
msgid "GitAbuse|Users who are excluded from the Git abuse rate limit."
msgstr ""
msgid "GitAbuse|You cannot specify more than %{maxExcludedUsers} excluded users."
msgstr ""
msgid "GitHub API rate limit exceeded. Try again after %{reset_time}"
msgstr ""
@ -20568,6 +20586,9 @@ msgstr ""
msgid "Incidents|Must start with http or https"
msgstr ""
msgid "Incident|Add new timeline event"
msgstr ""
msgid "Incident|Alert details"
msgstr ""
@ -20586,12 +20607,18 @@ msgstr ""
msgid "Incident|Editing %{filename}"
msgstr ""
msgid "Incident|Error creating incident timeline event: %{error}"
msgstr ""
msgid "Incident|Metrics"
msgstr ""
msgid "Incident|No timeline items have been added yet."
msgstr ""
msgid "Incident|Save and add another event"
msgstr ""
msgid "Incident|Something went wrong while fetching incident timeline events."
msgstr ""
@ -20607,6 +20634,12 @@ msgstr ""
msgid "Incident|Timeline"
msgstr ""
msgid "Incident|Timeline text"
msgstr ""
msgid "Incident|Timeline text..."
msgstr ""
msgid "Include author name in notification email body"
msgstr ""
@ -26109,6 +26142,9 @@ msgstr ""
msgid "No repository"
msgstr ""
msgid "No results"
msgstr ""
msgid "No runner executable"
msgstr ""

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
FactoryBot.define do
factory :ci_stage_entity, class: 'Ci::Stage' do
factory :ci_stage, class: 'Ci::Stage' do
project factory: :project
pipeline factory: :ci_empty_pipeline

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Incident timeline events', :js do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user) }
let_it_be(:incident) { create(:incident, project: project) }
before_all do
project.add_developer(developer)
end
before do
stub_feature_flags(incident_timeline: true)
sign_in(developer)
visit project_issues_incident_path(project, incident)
wait_for_requests
click_link 'Timeline'
end
context 'when add event is clicked' do
it 'submits event data when save is clicked' do
click_button 'Add new timeline event'
expect(page).to have_selector('.common-note-form')
fill_in 'Description', with: 'Event note goes here'
fill_in 'timeline-input-hours', with: '07'
fill_in 'timeline-input-minutes', with: '25'
click_button 'Save'
expect(page).to have_selector('.incident-timeline-events')
page.within '.timeline-event-note' do
expect(page).to have_content('Event note goes here')
expect(page).to have_content('07:25')
end
end
end
end

View File

@ -70,3 +70,17 @@ export const timelineEventsQueryEmptyResponse = {
},
},
};
export const timelineEventsCreateEventResponse = {
timelineEvent: {
...mockEvents[0],
},
errors: [],
};
export const timelineEventsCreateEventError = {
timelineEvent: {
...mockEvents[0],
},
errors: ['Error creating timeline event'],
};

View File

@ -0,0 +1,141 @@
import VueApollo from 'vue-apollo';
import Vue, { nextTick } from 'vue';
import { GlDatepicker } from '@gitlab/ui';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import IncidentTimelineEventForm from '~/issues/show/components/incidents/timeline_events_form.vue';
import createTimelineEventMutation from '~/issues/show/components/incidents/graphql/queries/create_timeline_event.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { createAlert } from '~/flash';
import { timelineEventsCreateEventResponse, timelineEventsCreateEventError } from './mock_data';
Vue.use(VueApollo);
jest.mock('~/flash');
const addEventResponse = jest.fn().mockResolvedValue(timelineEventsCreateEventResponse);
function createMockApolloProvider(response = addEventResponse) {
const requestHandlers = [[createTimelineEventMutation, response]];
return createMockApollo(requestHandlers);
}
describe('Timeline events form', () => {
let wrapper;
const mountComponent = ({ mockApollo, mountMethod = shallowMountExtended, stubs }) => {
wrapper = mountMethod(IncidentTimelineEventForm, {
propsData: {
hasTimelineEvents: true,
},
provide: {
fullPath: 'group/project',
issuableId: '1',
},
apolloProvider: mockApollo,
stubs,
});
};
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
const findSubmitButton = () => wrapper.findByText('Save');
const findSubmitAndAddButton = () => wrapper.findByText('Save and add another event');
const findCancelButton = () => wrapper.findByText('Cancel');
const findDatePicker = () => wrapper.findComponent(GlDatepicker);
const findHourInput = () => wrapper.findByTestId('input-hours');
const findMinuteInput = () => wrapper.findByTestId('input-minutes');
const submitForm = async () => {
findSubmitButton().trigger('click');
await waitForPromises();
};
const submitFormAndAddAnother = async () => {
findSubmitAndAddButton().trigger('click');
await waitForPromises();
};
const cancelForm = async () => {
findCancelButton().trigger('click');
await waitForPromises();
};
describe('form button behaviour', () => {
const closeFormEvent = { 'hide-incident-timeline-event-form': [[]] };
beforeEach(() => {
mountComponent({ mockApollo: createMockApolloProvider(), mountMethod: mountExtended });
});
it('should close the form on submit', async () => {
await submitForm();
expect(wrapper.emitted()).toEqual(closeFormEvent);
});
it('should not close the form on "submit and add another"', async () => {
await submitFormAndAddAnother();
expect(wrapper.emitted()).toEqual({});
});
it('should close the form on cancel', async () => {
await cancelForm();
expect(wrapper.emitted()).toEqual(closeFormEvent);
});
});
describe('addTimelineEventQuery', () => {
const expectedData = {
input: {
incidentId: 'gid://gitlab/Issue/1',
note: '',
occurredAt: '2020-07-06T00:00:00.000Z',
},
};
let mockApollo;
beforeEach(() => {
mockApollo = createMockApolloProvider();
mountComponent({ mockApollo, mountMethod: mountExtended });
});
it('should call the mutation with the right variables', async () => {
await submitForm();
expect(addEventResponse).toHaveBeenCalledWith(expectedData);
});
it('should call the mutation with user selected variables', async () => {
const expectedUserSelectedData = {
input: {
...expectedData.input,
occurredAt: '2021-08-12T05:45:00.000Z',
},
};
findDatePicker().vm.$emit('input', new Date('2021-08-12'));
findHourInput().vm.$emit('input', 5);
findMinuteInput().vm.$emit('input', 45);
await nextTick();
await submitForm();
expect(addEventResponse).toHaveBeenCalledWith(expectedUserSelectedData);
});
});
describe('error handling', () => {
const mockApollo = createMockApolloProvider(timelineEventsCreateEventError);
beforeEach(() => {
mountComponent({ mockApollo, mountMethod: mountExtended });
});
it('should show an error when submission fails', async () => {
await submitForm();
expect(createAlert).toHaveBeenCalled();
});
});
});

View File

@ -1,13 +1,15 @@
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import Vue from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import TimelineEventsTab from '~/issues/show/components/incidents/timeline_events_tab.vue';
import IncidentTimelineEventsList from '~/issues/show/components/incidents/timeline_events_list.vue';
import IncidentTimelineEventForm from '~/issues/show/components/incidents/timeline_events_form.vue';
import timelineEventsQuery from '~/issues/show/components/incidents/graphql/queries/get_timeline_events.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { createAlert } from '~/flash';
import { timelineTabI18n } from '~/issues/show/components/incidents/constants';
import { timelineEventsQueryListResponse, timelineEventsQueryEmptyResponse } from './mock_data';
Vue.use(VueApollo);
@ -28,14 +30,17 @@ describe('TimelineEventsTab', () => {
let wrapper;
const mountComponent = (options = {}) => {
const { mockApollo, mountMethod = shallowMountExtended } = options;
const { mockApollo, mountMethod = shallowMountExtended, stubs, provide } = options;
wrapper = mountMethod(TimelineEventsTab, {
provide: {
fullPath: 'group/project',
issuableId: '1',
canUpdate: true,
...provide,
},
apolloProvider: mockApollo,
stubs,
});
};
@ -48,6 +53,8 @@ describe('TimelineEventsTab', () => {
const findLoadingSpinner = () => wrapper.findComponent(GlLoadingIcon);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findTimelineEventsList = () => wrapper.findComponent(IncidentTimelineEventsList);
const findTimelineEventForm = () => wrapper.findComponent(IncidentTimelineEventForm);
const findAddEventButton = () => wrapper.findByText(timelineTabI18n.addEventButton);
describe('Timeline events tab', () => {
describe('empty state', () => {
@ -102,4 +109,50 @@ describe('TimelineEventsTab', () => {
expect(findTimelineEventsList().props('timelineEvents')).toHaveLength(3);
});
});
describe('add new event form', () => {
beforeEach(async () => {
mountComponent({
mockApollo: createMockApolloProvider(emptyResponse),
mountMethod: mountExtended,
stubs: {
'incident-timeline-events-list': true,
'gl-tab': true,
},
});
await waitForPromises();
});
it('should show a button when user can update', () => {
expect(findAddEventButton().exists()).toBe(true);
});
it('should not show a button when user cannot update', () => {
mountComponent({
mockApollo: createMockApolloProvider(emptyResponse),
provide: { canUpdate: false },
});
expect(findAddEventButton().exists()).toBe(false);
});
it('should not show a form by default', () => {
expect(findTimelineEventForm().isVisible()).toBe(false);
});
it('should show a form when button is clicked', async () => {
await findAddEventButton().trigger('click');
expect(findTimelineEventForm().isVisible()).toBe(true);
});
it('should hide the form when the hide event is emitted', async () => {
// open the form
await findAddEventButton().trigger('click');
await findTimelineEventForm().vm.$emit('hide-incident-timeline-event-form');
expect(findTimelineEventForm().isVisible()).toBe(false);
});
});
});

View File

@ -1,4 +1,9 @@
import { displayAndLogError, getEventIcon } from '~/issues/show/components/incidents/utils';
import timezoneMock from 'timezone-mock';
import {
displayAndLogError,
getEventIcon,
getUtcShiftedDateNow,
} from '~/issues/show/components/incidents/utils';
import { createAlert } from '~/flash';
jest.mock('~/flash');
@ -28,4 +33,22 @@ describe('incident utils', () => {
expect(getEventIcon('non-existent-icon-name')).toBe('comment');
});
});
describe('getUtcShiftedDateNow', () => {
beforeEach(() => {
timezoneMock.register('US/Pacific');
});
afterEach(() => {
timezoneMock.unregister();
});
it('should shift the date by the timezone offset', () => {
const date = new Date();
const shiftedDate = getUtcShiftedDateNow();
expect(shiftedDate > date).toBe(true);
});
});
});

View File

@ -13,7 +13,7 @@ RSpec.describe Types::Ci::DetailedStatusType do
:label, :text, :tooltip, :action)
end
let_it_be(:stage) { create(:ci_stage_entity, status: :skipped) }
let_it_be(:stage) { create(:ci_stage, status: :skipped) }
describe 'id field' do
it 'correctly renders the field' do

View File

@ -22,7 +22,7 @@ RSpec.describe Types::Ci::StatusActionType do
describe 'id field' do
it 'correctly renders the field' do
stage = build(:ci_stage_entity, status: :skipped)
stage = build(:ci_stage, status: :skipped)
status = stage.detailed_status(stage.pipeline.user)
expected_id = "#{stage.class.name}-#{status.id}"

View File

@ -6,7 +6,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CreateDeployments do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) }
let(:stage) { build(:ci_stage, project: project, statuses: [job]) }
let(:pipeline) { create(:ci_pipeline, project: project, stages: [stage]) }
let(:command) do

View File

@ -59,7 +59,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do
context 'tags persistence' do
let(:stage) do
build(:ci_stage_entity, pipeline: pipeline, project: project)
build(:ci_stage, pipeline: pipeline, project: project)
end
let(:job) do

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) }
let(:stage) { build(:ci_stage, project: project, statuses: [job]) }
let(:pipeline) { build(:ci_pipeline, project: project, stages: [stage]) }
let(:command) do

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureResourceGroups do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) }
let(:stage) { build(:ci_stage, project: project, statuses: [job]) }
let(:pipeline) { build(:ci_pipeline, project: project, stages: [stage]) }
let!(:environment) { create(:environment, name: 'production', project: project) }

View File

@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Common do
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:stage) do
build(:ci_stage_entity, pipeline: pipeline, name: 'test')
build(:ci_stage, pipeline: pipeline, name: 'test')
end
subject do

View File

@ -7,7 +7,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:stage) { create(:ci_stage_entity, pipeline: pipeline) }
let(:stage) { create(:ci_stage, pipeline: pipeline) }
subject do
described_class.new(stage, user)
@ -24,7 +24,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do
context 'when stage has a core status' do
(Ci::HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status|
context "when core status is #{core_status}" do
let(:stage) { create(:ci_stage_entity, pipeline: pipeline, status: core_status) }
let(:stage) { create(:ci_stage, pipeline: pipeline, status: core_status) }
it "fabricates a core status #{core_status}" do
expect(status).to be_a(
@ -42,7 +42,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do
context 'when stage has warnings' do
let(:stage) do
create(:ci_stage_entity, status: :success, pipeline: pipeline)
create(:ci_stage, status: :success, pipeline: pipeline)
end
before do
@ -64,7 +64,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do
context 'when stage has manual builds' do
(Ci::HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status|
context "when status is #{core_status}" do
let(:stage) { create(:ci_stage_entity, pipeline: pipeline, status: core_status) }
let(:stage) { create(:ci_stage, pipeline: pipeline, status: core_status) }
it 'fabricates a play manual status' do
expect(status).to be_a(Gitlab::Ci::Status::Stage::PlayManual)

View File

@ -25,7 +25,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::PlayManual do
end
describe '#action_path' do
let(:stage) { create(:ci_stage_entity, status: 'manual') }
let(:stage) { create(:ci_stage, status: 'manual') }
let(:pipeline) { stage.pipeline }
let(:play_manual) { stage.detailed_status(create(:user)) }
@ -46,25 +46,25 @@ RSpec.describe Gitlab::Ci::Status::Stage::PlayManual do
subject { described_class.matches?(stage, user) }
context 'when stage is skipped' do
let(:stage) { create(:ci_stage_entity, status: :skipped) }
let(:stage) { create(:ci_stage, status: :skipped) }
it { is_expected.to be_truthy }
end
context 'when stage is manual' do
let(:stage) { create(:ci_stage_entity, status: :manual) }
let(:stage) { create(:ci_stage, status: :manual) }
it { is_expected.to be_truthy }
end
context 'when stage is scheduled' do
let(:stage) { create(:ci_stage_entity, status: :scheduled) }
let(:stage) { create(:ci_stage, status: :scheduled) }
it { is_expected.to be_truthy }
end
context 'when stage is success' do
let(:stage) { create(:ci_stage_entity, status: :success) }
let(:stage) { create(:ci_stage, status: :success) }
context 'and does not have manual builds' do
it { is_expected.to be_falsy }

View File

@ -63,19 +63,22 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do
Gitlab::Database.schemas_to_base_models.fetch(parent_table_schema)
end
it 'all `to_table` tables are present' do
it 'all `to_table` tables are present', :aggregate_failures do
definitions.each do |definition|
base_models_for(definition.to_table).each do |model|
expect(model.connection).to be_table_exist(definition.to_table)
expect(model.connection).to be_table_exist(definition.to_table),
"Table #{definition.from_table} does not exist"
end
end
end
it 'all `from_table` tables are present' do
it 'all `from_table` tables are present', :aggregate_failures do
definitions.each do |definition|
base_models_for(definition.from_table).each do |model|
expect(model.connection).to be_table_exist(definition.from_table)
expect(model.connection).to be_column_exist(definition.from_table, definition.column)
expect(model.connection).to be_table_exist(definition.from_table),
"Table #{definition.from_table} does not exist"
expect(model.connection).to be_column_exist(definition.from_table, definition.column),
"Column #{definition.column} in #{definition.from_table} does not exist"
end
end
end

View File

@ -70,7 +70,7 @@ RSpec.describe Ci::Group do
describe '.fabricate' do
let(:pipeline) { create(:ci_empty_pipeline) }
let(:stage) { create(:ci_stage_entity, pipeline: pipeline) }
let(:stage) { create(:ci_stage, pipeline: pipeline) }
before do
create_build(:ci_build, name: 'rspec 0 2')

View File

@ -1350,7 +1350,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let(:pipeline) { build(:ci_empty_pipeline, :created) }
before do
create(:ci_stage_entity, project: project,
create(:ci_stage, project: project,
pipeline: pipeline,
position: 4,
name: 'deploy')
@ -1367,12 +1367,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
stage_idx: 2,
name: 'build')
create(:ci_stage_entity, project: project,
create(:ci_stage, project: project,
pipeline: pipeline,
position: 1,
name: 'sanity')
create(:ci_stage_entity, project: project,
create(:ci_stage, project: project,
pipeline: pipeline,
position: 5,
name: 'cleanup')
@ -4258,7 +4258,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let(:stage_name) { 'test' }
let(:stage) do
create(:ci_stage_entity,
create(:ci_stage,
pipeline: pipeline,
project: pipeline.project,
name: 'test')

View File

@ -24,7 +24,7 @@ RSpec.describe Ci::Processable do
new_proc
end
let_it_be(:stage) { create(:ci_stage_entity, project: project, pipeline: pipeline, name: 'test') }
let_it_be(:stage) { create(:ci_stage, project: project, pipeline: pipeline, name: 'test') }
shared_context 'processable bridge' do
let_it_be(:downstream_project) { create(:project, :repository) }

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::Stage, :models do
let_it_be(:pipeline) { create(:ci_empty_pipeline) }
let(:stage) { create(:ci_stage_entity, pipeline: pipeline, project: pipeline.project) }
let(:stage) { create(:ci_stage, pipeline: pipeline, project: pipeline.project) }
it_behaves_like 'having unique enum values'
@ -30,9 +30,9 @@ RSpec.describe Ci::Stage, :models do
describe '.by_position' do
it 'finds stages by position' do
a = create(:ci_stage_entity, position: 1)
b = create(:ci_stage_entity, position: 2)
c = create(:ci_stage_entity, position: 3)
a = create(:ci_stage, position: 1)
b = create(:ci_stage, position: 2)
c = create(:ci_stage, position: 3)
expect(described_class.by_position(1)).to contain_exactly(a)
expect(described_class.by_position(2)).to contain_exactly(b)
@ -42,9 +42,9 @@ RSpec.describe Ci::Stage, :models do
describe '.by_name' do
it 'finds stages by name' do
a = create(:ci_stage_entity, name: 'a')
b = create(:ci_stage_entity, name: 'b')
c = create(:ci_stage_entity, name: 'c')
a = create(:ci_stage, name: 'a')
b = create(:ci_stage, name: 'b')
c = create(:ci_stage, name: 'c')
expect(described_class.by_name('a')).to contain_exactly(a)
expect(described_class.by_name('b')).to contain_exactly(b)
@ -54,7 +54,7 @@ RSpec.describe Ci::Stage, :models do
describe '#status' do
context 'when stage is pending' do
let(:stage) { create(:ci_stage_entity, status: 'pending') }
let(:stage) { create(:ci_stage, status: 'pending') }
it 'has a correct status value' do
expect(stage.status).to eq 'pending'
@ -62,7 +62,7 @@ RSpec.describe Ci::Stage, :models do
end
context 'when stage is success' do
let(:stage) { create(:ci_stage_entity, status: 'success') }
let(:stage) { create(:ci_stage, status: 'success') }
it 'has a correct status value' do
expect(stage.status).to eq 'success'
@ -119,7 +119,7 @@ RSpec.describe Ci::Stage, :models do
end
context 'when stage has only created builds' do
let(:stage) { create(:ci_stage_entity, status: :created) }
let(:stage) { create(:ci_stage, status: :created) }
before do
create(:ci_build, :created, stage_id: stage.id)
@ -206,7 +206,7 @@ RSpec.describe Ci::Stage, :models do
using RSpec::Parameterized::TableSyntax
let(:user) { create(:user) }
let(:stage) { create(:ci_stage_entity, status: :created) }
let(:stage) { create(:ci_stage, status: :created) }
subject { stage.detailed_status(user) }
@ -269,7 +269,7 @@ RSpec.describe Ci::Stage, :models do
describe '#delay' do
subject { stage.delay }
let(:stage) { create(:ci_stage_entity, status: :created) }
let(:stage) { create(:ci_stage, status: :created) }
it 'updates stage status' do
subject
@ -361,12 +361,12 @@ RSpec.describe Ci::Stage, :models do
end
end
it_behaves_like 'manual playable stage', :ci_stage_entity
it_behaves_like 'manual playable stage', :ci_stage
context 'loose foreign key on ci_stages.project_id' do
it_behaves_like 'cleanup by a loose foreign key' do
let!(:parent) { create(:project) }
let!(:model) { create(:ci_stage_entity, project: parent) }
let!(:model) { create(:ci_stage, project: parent) }
end
end
end

View File

@ -803,7 +803,7 @@ RSpec.describe CommitStatus do
describe 'ensure stage assignment' do
context 'when commit status has a stage_id assigned' do
let!(:stage) do
create(:ci_stage_entity, project: project, pipeline: pipeline)
create(:ci_stage, project: project, pipeline: pipeline)
end
let(:commit_status) do
@ -836,7 +836,7 @@ RSpec.describe CommitStatus do
context 'when commit status does not have stage but it exists' do
let!(:stage) do
create(:ci_stage_entity, project: project,
create(:ci_stage, project: project,
pipeline: pipeline,
name: 'test')
end

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Ci::StagePresenter do
let(:stage) { create(:ci_stage_entity) }
let(:stage) { create(:ci_stage) }
let(:presenter) { described_class.new(stage) }
let!(:build) { create(:ci_build, :tags, :artifacts, pipeline: stage.pipeline, stage: stage.name) }

View File

@ -13,8 +13,8 @@ RSpec.describe 'Query.project(fullPath).pipelines.job(id)' do
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:prepare_stage) { create(:ci_stage_entity, pipeline: pipeline, project: project, name: 'prepare') }
let_it_be(:test_stage) { create(:ci_stage_entity, pipeline: pipeline, project: project, name: 'test') }
let_it_be(:prepare_stage) { create(:ci_stage, pipeline: pipeline, project: project, name: 'prepare') }
let_it_be(:test_stage) { create(:ci_stage, pipeline: pipeline, project: project, name: 'test') }
let_it_be(:job_1) { create(:ci_build, pipeline: pipeline, stage: 'prepare', name: 'Job 1') }
let_it_be(:job_2) { create(:ci_build, pipeline: pipeline, stage: 'test', name: 'Job 2') }

View File

@ -14,7 +14,7 @@ RSpec.describe 'Query.project.pipeline' do
describe '.stages.groups.jobs' do
let(:pipeline) do
pipeline = create(:ci_pipeline, project: project, user: user)
stage = create(:ci_stage_entity, project: project, pipeline: pipeline, name: 'first', position: 1)
stage = create(:ci_stage, project: project, pipeline: pipeline, name: 'first', position: 1)
create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'my test job', scheduling_type: :stage)
pipeline
@ -84,8 +84,8 @@ RSpec.describe 'Query.project.pipeline' do
context 'when there is more than one stage and job needs' do
before do
build_stage = create(:ci_stage_entity, position: 2, name: 'build', project: project, pipeline: pipeline)
test_stage = create(:ci_stage_entity, position: 3, name: 'test', project: project, pipeline: pipeline)
build_stage = create(:ci_stage, position: 2, name: 'build', project: project, pipeline: pipeline)
test_stage = create(:ci_stage, position: 3, name: 'test', project: project, pipeline: pipeline)
create(:ci_build, pipeline: pipeline, name: 'docker 1 2', scheduling_type: :stage, stage: build_stage, stage_idx: build_stage.position)
create(:ci_build, pipeline: pipeline, name: 'docker 2 2', stage: build_stage, stage_idx: build_stage.position, scheduling_type: :dag)

View File

@ -86,8 +86,8 @@ RSpec.describe 'Query.project(fullPath).pipelines' do
describe '.stages' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_empty_pipeline, project: project) }
let_it_be(:stage) { create(:ci_stage_entity, pipeline: pipeline, project: project) }
let_it_be(:other_stage) { create(:ci_stage_entity, pipeline: pipeline, project: project, name: 'other') }
let_it_be(:stage) { create(:ci_stage, pipeline: pipeline, project: project) }
let_it_be(:other_stage) { create(:ci_stage, pipeline: pipeline, project: project, name: 'other') }
let(:first_n) { var('Int') }
let(:query_path) do

View File

@ -36,7 +36,7 @@ RSpec.describe 'Query.project.pipeline.stages' do
end
before_all do
create(:ci_stage_entity, pipeline: pipeline, name: 'deploy')
create(:ci_stage, pipeline: pipeline, name: 'deploy')
create_list(:ci_build, 2, pipeline: pipeline, stage: 'deploy')
end

View File

@ -31,8 +31,8 @@ RSpec.describe 'Query.project.jobs' do
end
it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
build_stage = create(:ci_stage_entity, position: 1, name: 'build', project: project, pipeline: pipeline)
test_stage = create(:ci_stage_entity, position: 2, name: 'test', project: project, pipeline: pipeline)
build_stage = create(:ci_stage, position: 1, name: 'build', project: project, pipeline: pipeline)
test_stage = create(:ci_stage, position: 2, name: 'test', project: project, pipeline: pipeline)
create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 1 2', stage: build_stage)
create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 2 2', stage: build_stage)
create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 1 2', stage: test_stage)

View File

@ -290,8 +290,8 @@ RSpec.describe 'getting pipeline information nested in a project' do
end
it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
build_stage = create(:ci_stage_entity, position: 1, name: 'build', project: project, pipeline: pipeline)
test_stage = create(:ci_stage_entity, position: 2, name: 'test', project: project, pipeline: pipeline)
build_stage = create(:ci_stage, position: 1, name: 'build', project: project, pipeline: pipeline)
test_stage = create(:ci_stage, position: 2, name: 'test', project: project, pipeline: pipeline)
create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 1 2', stage: build_stage)
create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 2 2', stage: build_stage)
create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 1 2', stage: test_stage)

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::DagJobGroupEntity do
let_it_be(:request) { double(:request) }
let_it_be(:pipeline) { create(:ci_pipeline) }
let_it_be(:stage) { create(:ci_stage_entity, pipeline: pipeline) }
let_it_be(:stage) { create(:ci_stage, pipeline: pipeline) }
let(:group) { Ci::Group.new(pipeline.project, stage, name: 'test', jobs: jobs) }
let(:entity) { described_class.new(group, request: request) }

View File

@ -43,9 +43,9 @@ RSpec.describe Ci::DagPipelineEntity do
end
context 'when pipeline has parallel jobs, DAG needs and GenericCommitStatus' do
let!(:stage_build) { create(:ci_stage_entity, name: 'build', position: 1, pipeline: pipeline) }
let!(:stage_test) { create(:ci_stage_entity, name: 'test', position: 2, pipeline: pipeline) }
let!(:stage_deploy) { create(:ci_stage_entity, name: 'deploy', position: 3, pipeline: pipeline) }
let!(:stage_build) { create(:ci_stage, name: 'build', position: 1, pipeline: pipeline) }
let!(:stage_test) { create(:ci_stage, name: 'test', position: 2, pipeline: pipeline) }
let!(:stage_deploy) { create(:ci_stage, name: 'deploy', position: 3, pipeline: pipeline) }
let!(:job_build_1) { create(:ci_build, name: 'build 1', stage: 'build', pipeline: pipeline) }
let!(:job_build_2) { create(:ci_build, name: 'build 2', stage: 'build', pipeline: pipeline) }

View File

@ -6,7 +6,7 @@ RSpec.describe Ci::DagStageEntity do
let_it_be(:pipeline) { create(:ci_pipeline) }
let_it_be(:request) { double(:request) }
let(:stage) { create(:ci_stage_entity, pipeline: pipeline, name: 'test') }
let(:stage) { create(:ci_stage, pipeline: pipeline, name: 'test') }
let(:entity) { described_class.new(stage, request: request) }
let!(:job) { create(:ci_build, :success, pipeline: pipeline, stage_id: stage.id) }

View File

@ -12,7 +12,7 @@ RSpec.describe StageEntity do
end
let(:stage) do
create(:ci_stage_entity, pipeline: pipeline, status: :success)
create(:ci_stage, pipeline: pipeline, status: :success)
end
before do
@ -74,7 +74,7 @@ RSpec.describe StageEntity do
end
context 'with a skipped stage ' do
let(:stage) { create(:ci_stage_entity, status: 'skipped') }
let(:stage) { create(:ci_stage, status: 'skipped') }
it 'contains play_all_manual' do
expect(subject[:status][:action]).to be_present
@ -82,7 +82,7 @@ RSpec.describe StageEntity do
end
context 'with a scheduled stage ' do
let(:stage) { create(:ci_stage_entity, status: 'scheduled') }
let(:stage) { create(:ci_stage, status: 'scheduled') }
it 'contains play_all_manual' do
expect(subject[:status][:action]).to be_present
@ -90,7 +90,7 @@ RSpec.describe StageEntity do
end
context 'with a manual stage ' do
let(:stage) { create(:ci_stage_entity, status: 'manual') }
let(:stage) { create(:ci_stage, status: 'manual') }
it 'contains play_all_manual' do
expect(subject[:status][:action]).to be_present

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe StageSerializer do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:resource) { create(:ci_stage_entity) }
let(:resource) { create(:ci_stage) }
let(:serializer) do
described_class.new(current_user: user, project: project)
@ -21,7 +21,7 @@ RSpec.describe StageSerializer do
end
context 'with an array of entities' do
let(:resource) { create_list(:ci_stage_entity, 2) }
let(:resource) { create_list(:ci_stage, 2) }
it 'serializes the array of pipelines' do
expect(subject).not_to be_empty

View File

@ -12,13 +12,13 @@ RSpec.describe Ci::AbortPipelinesService do
let_it_be(:cancelable_build, reload: true) { create(:ci_build, :running, pipeline: cancelable_pipeline) }
let_it_be(:non_cancelable_build, reload: true) { create(:ci_build, :success, pipeline: cancelable_pipeline) }
let_it_be(:cancelable_stage, reload: true) { create(:ci_stage_entity, name: 'stageA', status: :running, pipeline: cancelable_pipeline, project: project) }
let_it_be(:non_cancelable_stage, reload: true) { create(:ci_stage_entity, name: 'stageB', status: :success, pipeline: cancelable_pipeline, project: project) }
let_it_be(:cancelable_stage, reload: true) { create(:ci_stage, name: 'stageA', status: :running, pipeline: cancelable_pipeline, project: project) }
let_it_be(:non_cancelable_stage, reload: true) { create(:ci_stage, name: 'stageB', status: :success, pipeline: cancelable_pipeline, project: project) }
let_it_be(:manual_pipeline_cancelable_build, reload: true) { create(:ci_build, :created, pipeline: manual_pipeline) }
let_it_be(:manual_pipeline_non_cancelable_build, reload: true) { create(:ci_build, :manual, pipeline: manual_pipeline) }
let_it_be(:manual_pipeline_cancelable_stage, reload: true) { create(:ci_stage_entity, name: 'stageA', status: :created, pipeline: manual_pipeline, project: project) }
let_it_be(:manual_pipeline_non_cancelable_stage, reload: true) { create(:ci_stage_entity, name: 'stageB', status: :success, pipeline: manual_pipeline, project: project) }
let_it_be(:manual_pipeline_cancelable_stage, reload: true) { create(:ci_stage, name: 'stageA', status: :created, pipeline: manual_pipeline, project: project) }
let_it_be(:manual_pipeline_non_cancelable_stage, reload: true) { create(:ci_stage, name: 'stageB', status: :success, pipeline: manual_pipeline, project: project) }
describe '#execute' do
def expect_correct_pipeline_cancellations

View File

@ -6,7 +6,7 @@ RSpec.describe Ci::EnsureStageService, '#execute' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:stage) { create(:ci_stage_entity) }
let(:stage) { create(:ci_stage) }
let(:job) { build(:ci_build) }
let(:service) { described_class.new(project, user) }

View File

@ -11,7 +11,7 @@ RSpec.describe Ci::PlayManualStageService, '#execute' do
let(:stage_status) { 'manual' }
let(:stage) do
create(:ci_stage_entity,
create(:ci_stage,
pipeline: pipeline,
project: project,
name: 'test')

View File

@ -12,7 +12,7 @@ RSpec.describe Ci::RetryJobService do
end
let_it_be(:stage) do
create(:ci_stage_entity, project: project,
create(:ci_stage, project: project,
pipeline: pipeline,
name: 'test')
end
@ -154,7 +154,7 @@ RSpec.describe Ci::RetryJobService do
end
context 'when the pipeline has other jobs' do
let!(:stage2) { create(:ci_stage_entity, project: project, pipeline: pipeline, name: 'deploy') }
let!(:stage2) { create(:ci_stage, project: project, pipeline: pipeline, name: 'deploy') }
let!(:build2) { create(:ci_build, pipeline: pipeline, stage_id: stage.id ) }
let!(:deploy) { create(:ci_build, pipeline: pipeline, stage_id: stage2.id) }
let!(:deploy_needs_build2) { create(:ci_build_need, build: deploy, name: build2.name) }

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe StageUpdateWorker do
describe '#perform' do
context 'when stage exists' do
let(:stage) { create(:ci_stage_entity) }
let(:stage) { create(:ci_stage) }
it 'updates stage status' do
expect_any_instance_of(Ci::Stage).to receive(:set_status).with('skipped')

View File

@ -26,7 +26,7 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/smartystreets/goconvey v1.7.2
github.com/stretchr/testify v1.8.0
gitlab.com/gitlab-org/gitaly/v15 v15.1.0
gitlab.com/gitlab-org/gitaly/v15 v15.1.2
gitlab.com/gitlab-org/golang-archive-zip v0.1.1
gitlab.com/gitlab-org/labkit v1.15.0
gocloud.dev v0.25.0

View File

@ -1112,8 +1112,8 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK
gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
gitlab.com/gitlab-org/gitaly/v15 v15.1.0 h1:fxnBpIKtaTpkcDUPHKrxuhZj/VWrMqtWRZaO3mwp0WI=
gitlab.com/gitlab-org/gitaly/v15 v15.1.0/go.mod h1:fZSeTXPLtbmTxNRk9AECM2shtJ6JNzIiLxAgknWBdT4=
gitlab.com/gitlab-org/gitaly/v15 v15.1.2 h1:6NcpZENpvV4ialIjtrPuxCFHLn6VOrE3mJ9rd/4DuzU=
gitlab.com/gitlab-org/gitaly/v15 v15.1.2/go.mod h1:fZSeTXPLtbmTxNRk9AECM2shtJ6JNzIiLxAgknWBdT4=
gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
gitlab.com/gitlab-org/golang-archive-zip v0.1.1 h1:35k9giivbxwF03+8A05Cm8YoxoakU8FBCj5gysjCTCE=