Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d4c968c95c
commit
f1a1bd96b7
37 changed files with 393 additions and 156 deletions
|
@ -146,7 +146,7 @@ review-stop:
|
|||
|
||||
.allure-report-base:
|
||||
image:
|
||||
name: ${GITLAB_DEPENDENCY_PROXY}andrcuns/allure-report-publisher:0.3.2
|
||||
name: ${GITLAB_DEPENDENCY_PROXY}andrcuns/allure-report-publisher:0.3.4
|
||||
entrypoint: [""]
|
||||
stage: post-qa
|
||||
variables:
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 13.12.5 (2021-06-21)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
||||
- [Fix failing spec](gitlab-org/gitlab@7d1a9b0155195eb082f5b33ba1310deed742a7a4) ([merge request](gitlab-org/gitlab!64488))
|
||||
- [Advanced Search Settings page does not load if the ES url is unreachable](gitlab-org/gitlab@80b262f0e79f02a89724ed4e3988e686f53c959c) ([merge request](gitlab-org/gitlab!64488)) **GitLab Enterprise Edition**
|
||||
- [Fix Password expired error on git fetch via SSH for LDAP user](gitlab-org/gitlab@19a7d7a6d3cd43f1c7559c729532ad3b9dafb75c) ([merge request](gitlab-org/gitlab!64488))
|
||||
|
||||
## 13.12.4 (2021-06-14)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlLabel, GlTooltipDirective, GlIcon, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { GlLabel, GlTooltip, GlTooltipDirective, GlIcon, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { sortBy } from 'lodash';
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner';
|
||||
|
@ -16,6 +16,7 @@ import IssueTimeEstimate from './issue_time_estimate.vue';
|
|||
|
||||
export default {
|
||||
components: {
|
||||
GlTooltip,
|
||||
GlLabel,
|
||||
GlLoadingIcon,
|
||||
GlIcon,
|
||||
|
@ -55,7 +56,7 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isShowingLabels', 'issuableType']),
|
||||
...mapState(['isShowingLabels', 'issuableType', 'allowSubEpics']),
|
||||
...mapGetters(['isEpicBoard']),
|
||||
cappedAssignees() {
|
||||
// e.g. maxRender is 4,
|
||||
|
@ -99,6 +100,9 @@ export default {
|
|||
}
|
||||
return false;
|
||||
},
|
||||
shouldRenderEpicCountables() {
|
||||
return this.isEpicBoard && this.item.hasIssues;
|
||||
},
|
||||
showLabelFooter() {
|
||||
return this.isShowingLabels && this.item.labels.find(this.showLabel);
|
||||
},
|
||||
|
@ -115,6 +119,17 @@ export default {
|
|||
}
|
||||
return __('Blocked issue');
|
||||
},
|
||||
totalEpicsCount() {
|
||||
return this.item.descendantCounts.openedEpics + this.item.descendantCounts.closedEpics;
|
||||
},
|
||||
totalIssuesCount() {
|
||||
return this.item.descendantCounts.openedIssues + this.item.descendantCounts.closedIssues;
|
||||
},
|
||||
totalWeight() {
|
||||
return (
|
||||
this.item.descendantWeightSum.openedIssues + this.item.descendantWeightSum.closedIssues
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['performSearch', 'setError']),
|
||||
|
@ -227,17 +242,71 @@ export default {
|
|||
{{ itemId }}
|
||||
</span>
|
||||
<span class="board-info-items gl-mt-3 gl-display-inline-block">
|
||||
<issue-due-date
|
||||
v-if="item.dueDate"
|
||||
:date="item.dueDate"
|
||||
:closed="item.closed || Boolean(item.closedAt)"
|
||||
/>
|
||||
<issue-time-estimate v-if="item.timeEstimate" :estimate="item.timeEstimate" />
|
||||
<issue-card-weight
|
||||
v-if="validIssueWeight(item)"
|
||||
:weight="item.weight"
|
||||
@click="filterByWeight(item.weight)"
|
||||
/>
|
||||
<span v-if="shouldRenderEpicCountables" data-testid="epic-countables">
|
||||
<gl-tooltip :target="() => $refs.countBadge" data-testid="epic-countables-tooltip">
|
||||
<p v-if="allowSubEpics" class="gl-font-weight-bold gl-m-0">
|
||||
{{ __('Epics') }} •
|
||||
<span class="gl-font-weight-normal"
|
||||
>{{
|
||||
sprintf(__('%{openedEpics} open, %{closedEpics} closed'), {
|
||||
openedEpics: item.descendantCounts.openedEpics,
|
||||
closedEpics: item.descendantCounts.closedEpics,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</p>
|
||||
<p class="gl-font-weight-bold gl-m-0">
|
||||
{{ __('Issues') }} •
|
||||
<span class="gl-font-weight-normal"
|
||||
>{{
|
||||
sprintf(__('%{openedIssues} open, %{closedIssues} closed'), {
|
||||
openedIssues: item.descendantCounts.openedIssues,
|
||||
closedIssues: item.descendantCounts.closedIssues,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</p>
|
||||
<p class="gl-font-weight-bold gl-m-0">
|
||||
{{ __('Weight') }} •
|
||||
<span class="gl-font-weight-normal" data-testid="epic-countables-total-weight"
|
||||
>{{
|
||||
sprintf(__('%{closedWeight} complete, %{openWeight} incomplete'), {
|
||||
openWeight: item.descendantWeightSum.openedIssues,
|
||||
closedWeight: item.descendantWeightSum.closedIssues,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</p>
|
||||
</gl-tooltip>
|
||||
|
||||
<span ref="countBadge" class="issue-count-badge board-card-info">
|
||||
<span v-if="allowSubEpics" class="gl-mr-3">
|
||||
<gl-icon name="epic" />
|
||||
{{ totalEpicsCount }}
|
||||
</span>
|
||||
<span class="gl-mr-3" data-testid="epic-countables-counts-issues">
|
||||
<gl-icon name="issues" />
|
||||
{{ totalIssuesCount }}
|
||||
</span>
|
||||
<span class="gl-mr-3" data-testid="epic-countables-weight-issues">
|
||||
<gl-icon name="weight" />
|
||||
{{ totalWeight }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span v-if="!isEpicBoard">
|
||||
<issue-due-date
|
||||
v-if="item.dueDate"
|
||||
:date="item.dueDate"
|
||||
:closed="item.closed || Boolean(item.closedAt)"
|
||||
/>
|
||||
<issue-time-estimate v-if="item.timeEstimate" :estimate="item.timeEstimate" />
|
||||
<issue-card-weight
|
||||
v-if="validIssueWeight(item)"
|
||||
:weight="item.weight"
|
||||
@click="filterByWeight(item.weight)"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="board-card-assignee gl-display-flex">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global Flash */
|
||||
/* global createFlash */
|
||||
|
||||
import $ from 'jquery';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
|
@ -71,5 +71,9 @@ export function fetchCommitMergeRequests() {
|
|||
|
||||
$container.html($content);
|
||||
})
|
||||
.catch(() => Flash(s__('Commits|An error occurred while fetching merge requests data.')));
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
message: s__('Commits|An error occurred while fetching merge requests data.'),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
/* global Flash */
|
||||
|
||||
import { GlLoadingIcon, GlModal } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { getParameterByName } from '~/lib/utils/common_utils';
|
||||
import { HIDDEN_CLASS } from '~/lib/utils/constants';
|
||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
|
@ -116,7 +115,7 @@ export default {
|
|||
this.isLoading = false;
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
|
||||
Flash(COMMON_STR.FAILURE);
|
||||
createFlash({ message: COMMON_STR.FAILURE });
|
||||
});
|
||||
},
|
||||
fetchAllGroups() {
|
||||
|
@ -202,7 +201,7 @@ export default {
|
|||
if (err.status === 403) {
|
||||
message = COMMON_STR.LEAVE_FORBIDDEN;
|
||||
}
|
||||
Flash(message);
|
||||
createFlash({ message });
|
||||
this.targetGroup.isBeingRemoved = false;
|
||||
});
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@ import $ from 'jquery';
|
|||
import loadAwardsHandler from '~/awards_handler';
|
||||
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
|
||||
import Diff from '~/diff';
|
||||
import flash from '~/flash';
|
||||
import createFlash from '~/flash';
|
||||
import initChangesDropdown from '~/init_changes_dropdown';
|
||||
import initNotes from '~/init_notes';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
@ -39,7 +39,7 @@ if (filesContainer.length) {
|
|||
new Diff();
|
||||
})
|
||||
.catch(() => {
|
||||
flash({ message: __('An error occurred while retrieving diff files') });
|
||||
createFlash({ message: __('An error occurred while retrieving diff files') });
|
||||
});
|
||||
} else {
|
||||
new Diff();
|
||||
|
|
|
@ -170,7 +170,7 @@ export default {
|
|||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
createFlash(ERROR_MESSAGE);
|
||||
createFlash({ message: ERROR_MESSAGE });
|
||||
});
|
||||
},
|
||||
formData() {
|
||||
|
|
|
@ -8,7 +8,7 @@ class Admin::RunnersController < Admin::ApplicationController
|
|||
push_frontend_feature_flag(:runner_list_view_vue_ui, current_user, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
feature_category :continuous_integration
|
||||
feature_category :runner
|
||||
|
||||
NUMBER_OF_RUNNERS_PER_PAGE = 30
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ class Groups::RunnersController < Groups::ApplicationController
|
|||
|
||||
before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
|
||||
|
||||
feature_category :continuous_integration
|
||||
feature_category :runner
|
||||
|
||||
def show
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ class Projects::RunnersController < Projects::ApplicationController
|
|||
|
||||
layout 'project_settings'
|
||||
|
||||
feature_category :continuous_integration
|
||||
feature_category :runner
|
||||
|
||||
def index
|
||||
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')
|
||||
|
|
23
db/migrate/20210617161348_cascade_delete_freeze_periods.rb
Normal file
23
db/migrate/20210617161348_cascade_delete_freeze_periods.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class CascadeDeleteFreezePeriods < ActiveRecord::Migration[6.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
OLD_PROJECT_FK = 'fk_rails_2e02bbd1a6'
|
||||
NEW_PROJECT_FK = 'fk_2e02bbd1a6'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :ci_freeze_periods, :projects, column: :project_id, on_delete: :cascade, name: NEW_PROJECT_FK
|
||||
remove_foreign_key_if_exists :ci_freeze_periods, :projects, column: :project_id, name: OLD_PROJECT_FK
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key :ci_freeze_periods, :projects, column: :project_id, on_delete: nil, name: OLD_PROJECT_FK
|
||||
remove_foreign_key_if_exists :ci_freeze_periods, :projects, column: :project_id, name: NEW_PROJECT_FK
|
||||
end
|
||||
end
|
1
db/schema_migrations/20210617161348
Normal file
1
db/schema_migrations/20210617161348
Normal file
|
@ -0,0 +1 @@
|
|||
3f73aa7d2cff11d00b330d88e76daaa058f82b7012da3c244f246da6e538921c
|
|
@ -25627,6 +25627,9 @@ ALTER TABLE ONLY geo_event_log
|
|||
ALTER TABLE ONLY deployments
|
||||
ADD CONSTRAINT fk_289bba3222 FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY ci_freeze_periods
|
||||
ADD CONSTRAINT fk_2e02bbd1a6 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY notes
|
||||
ADD CONSTRAINT fk_2e82291620 FOREIGN KEY (review_id) REFERENCES reviews(id) ON DELETE SET NULL;
|
||||
|
||||
|
@ -26575,9 +26578,6 @@ ALTER TABLE ONLY onboarding_progresses
|
|||
ALTER TABLE ONLY protected_branch_unprotect_access_levels
|
||||
ADD CONSTRAINT fk_rails_2d2aba21ef FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ci_freeze_periods
|
||||
ADD CONSTRAINT fk_rails_2e02bbd1a6 FOREIGN KEY (project_id) REFERENCES projects(id);
|
||||
|
||||
ALTER TABLE ONLY issuable_severities
|
||||
ADD CONSTRAINT fk_rails_2fbb74ad6d FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
@ -90,7 +90,9 @@ export default {
|
|||
try {
|
||||
await this.contentEditor.setSerializedContent(this.content);
|
||||
} catch (e) {
|
||||
createFlash(__('There was an error loading content in the editor'), e);
|
||||
createFlash({
|
||||
message: __('There was an error loading content in the editor'), error: e
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -106,7 +106,7 @@ In this file, we write the actions that call mutations for handling a list of us
|
|||
.then(({ data }) => commit(types.RECEIVE_USERS_SUCCESS, data))
|
||||
.catch((error) => {
|
||||
commit(types.RECEIVE_USERS_ERROR, error)
|
||||
createFlash('There was an error')
|
||||
createFlash({ message: 'There was an error' })
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ install GitLab:
|
|||
| [Helm charts](https://docs.gitlab.com/charts/) | The cloud native Helm chart for installing GitLab and all of its components on Kubernetes. | When installing GitLab on Kubernetes, there are some trade-offs that you need to be aware of: <br/>- Administration and troubleshooting requires Kubernetes knowledge.<br/>- It can be more expensive for smaller installations. The default installation requires more resources than a single node Linux package deployment, as most services are deployed in a redundant fashion.<br/>- There are some feature [limitations to be aware of](https://docs.gitlab.com/charts/#limitations).<br/><br/> Use this method if your infrastructure is built on Kubernetes and you're familiar with how it works. The methods for management, observability, and some concepts are different than traditional deployments. |
|
||||
| [Docker](https://docs.gitlab.com/omnibus/docker/) | The GitLab packages, Dockerized. | Use this method if you're familiar with Docker. |
|
||||
| [Source](installation.md) | Install GitLab and all of its components from scratch. | Use this method if none of the previous methods are available for your platform. Useful for unsupported systems like \*BSD.|
|
||||
| [GitLab Environment Toolkit (GET)](https://gitlab.com/gitlab-org/quality/gitlab-environment-toolkit#documentation) | The GitLab Environment toolkit provides a set of automation tools to deploy a [reference architecture](../administration/reference_architectures/index.md) on most major cloud providers. | Since GET is in beta and not yet recommended for production use, use this method if you want to test deploying GitLab in scalable environment. |
|
||||
| [GitLab Environment Toolkit (GET)](https://gitlab.com/gitlab-org/quality/gitlab-environment-toolkit#documentation) | The GitLab Environment toolkit provides a set of automation tools to deploy a [reference architecture](../administration/reference_architectures/index.md) on most major cloud providers. | Customers are very welcome to trial and evaluate GET today, however be aware of [key limitations](https://gitlab.com/gitlab-org/quality/gitlab-environment-toolkit#missing-features-to-be-aware-of) of the current iteration. For production environments further manual setup will be required based on your specific requirements. |
|
||||
|
||||
## Install GitLab on cloud providers
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated
|
|||
type: reference, howto
|
||||
---
|
||||
|
||||
# Threads **(FREE)**
|
||||
# Comments and threads **(FREE)**
|
||||
|
||||
GitLab encourages communication through comments, threads, and
|
||||
[code suggestions](../project/merge_requests/reviews/suggestions.md).
|
||||
|
|
|
@ -112,6 +112,12 @@ You can filter by the following:
|
|||
- Author
|
||||
- Label
|
||||
|
||||
### View count of issues and weight in an epic
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331330) in GitLab 14.1.
|
||||
|
||||
Epics on the **Epic Boards** show a summary of their issues and weight. Hovering over the total counts will show the number of open and closed issues, as well as the completed and incomplete weight.
|
||||
|
||||
### Move epics and lists
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5079) in GitLab 14.0.
|
||||
|
|
|
@ -147,7 +147,7 @@ To use a custom issue template with Service Desk, in your project:
|
|||
1. Go to **Settings > General > Service Desk**.
|
||||
1. From the dropdown **Template to append to all Service Desk issues**, select your template.
|
||||
|
||||
### Using custom email display name
|
||||
### Using a custom email display name
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7529) in GitLab 12.8.
|
||||
|
||||
|
@ -160,22 +160,29 @@ To edit the custom email display name:
|
|||
1. Enter a new name in **Email display name**.
|
||||
1. Select **Save Changes**.
|
||||
|
||||
### Using custom email address **(FREE SELF)**
|
||||
### Using a custom email address **(FREE SELF)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2201) in GitLab Premium 13.0.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/284656) in GitLab 13.8.
|
||||
|
||||
If the `service_desk_email` is configured, then you can create Service Desk
|
||||
issues by sending emails to the Service Desk email address. The default
|
||||
address has the following format:
|
||||
`project_contact+%{key}@example.com`.
|
||||
It is possible to customize the email address used by Service Desk. To do this, you must configure
|
||||
both a [custom mailbox](#configuring-a-custom-mailbox) and a
|
||||
[custom suffix](#configuring-a-custom-email-address-suffix).
|
||||
|
||||
The `%{key}` part is used to find the project where the issue should be created. The
|
||||
`%{key}` part combines the path to the project and configurable project name suffix:
|
||||
`<project_full_path>-<project_name_suffix>`.
|
||||
#### Configuring a custom mailbox
|
||||
|
||||
You can set the project name suffix in your project's Service Desk settings.
|
||||
It can contain only lowercase letters (`a-z`), numbers (`0-9`), or underscores (`_`).
|
||||
NOTE:
|
||||
On GitLab.com a custom mailbox is already configured with `contact-project+%{key}@incoming.gitlab.com` as the email address, so you only have to configure the
|
||||
[custom suffix](#configuring-a-custom-email-address-suffix) in project settings.
|
||||
|
||||
Using the `service_desk_email` configuration, you can customize the mailbox
|
||||
used by Service Desk. This allows you to have a separate email address for
|
||||
Service Desk by also configuring a [custom suffix](#configuring-a-custom-email-address-suffix)
|
||||
in project settings.
|
||||
|
||||
The `address` must include the `+%{key}` placeholder within the 'user'
|
||||
portion of the address, before the `@`. This is used to identify the project
|
||||
where the issue should be created.
|
||||
|
||||
NOTE:
|
||||
The `service_desk_email` and `incoming_email` configurations should
|
||||
|
@ -183,7 +190,7 @@ always use separate mailboxes. This is important, because emails picked from
|
|||
`service_desk_email` mailbox are processed by a different worker and it would
|
||||
not recognize `incoming_email` emails.
|
||||
|
||||
To configure a custom email address for Service Desk with IMAP, add the following snippets to your configuration file:
|
||||
To configure a custom mailbox for Service Desk with IMAP, add the following snippets to your configuration file in full:
|
||||
|
||||
- Example for installations from source:
|
||||
|
||||
|
@ -207,36 +214,22 @@ To configure a custom email address for Service Desk with IMAP, add the followin
|
|||
|
||||
```ruby
|
||||
gitlab_rails['service_desk_email_enabled'] = true
|
||||
|
||||
gitlab_rails['service_desk_email_address'] = "project_contact+%{key}@gmail.com"
|
||||
|
||||
gitlab_rails['service_desk_email_email'] = "project_support@gmail.com"
|
||||
|
||||
gitlab_rails['service_desk_email_password'] = "[REDACTED]"
|
||||
|
||||
gitlab_rails['service_desk_email_mailbox_name'] = "inbox"
|
||||
|
||||
gitlab_rails['service_desk_email_idle_timeout'] = 60
|
||||
|
||||
gitlab_rails['service_desk_email_log_file'] = "/var/log/gitlab/mailroom/mail_room_json.log"
|
||||
|
||||
gitlab_rails['service_desk_email_host'] = "imap.gmail.com"
|
||||
|
||||
gitlab_rails['service_desk_email_port'] = 993
|
||||
|
||||
gitlab_rails['service_desk_email_ssl'] = true
|
||||
|
||||
gitlab_rails['service_desk_email_start_tls'] = false
|
||||
```
|
||||
|
||||
In this case, suppose the `mygroup/myproject` project Service Desk settings has the project name
|
||||
suffix set to `support`, and a user sends an email to `project_contact+mygroup-myproject-support@example.com`.
|
||||
As a result, a new Service Desk issue is created from this email in the `mygroup/myproject` project.
|
||||
|
||||
The configuration options are the same as for configuring
|
||||
[incoming email](../../administration/incoming_email.md#set-it-up).
|
||||
|
||||
#### Microsoft Graph
|
||||
##### Microsoft Graph
|
||||
|
||||
> Introduced in [GitLab 13.11](https://gitlab.com/gitlab-org/gitlab/-/issues/214900)
|
||||
|
||||
|
@ -247,17 +240,11 @@ Graph API instead of IMAP. Follow the [documentation in the incoming e-mail sect
|
|||
|
||||
```ruby
|
||||
gitlab_rails['service_desk_email_enabled'] = true
|
||||
|
||||
gitlab_rails['service_desk_email_address'] = "project_contact+%{key}@example.onmicrosoft.com"
|
||||
|
||||
gitlab_rails['service_desk_email_email'] = "project_contact@example.onmicrosoft.com"
|
||||
|
||||
gitlab_rails['service_desk_email_mailbox_name'] = "inbox"
|
||||
|
||||
gitlab_rails['service_desk_email_log_file'] = "/var/log/gitlab/mailroom/mail_room_json.log"
|
||||
|
||||
gitlab_rails['service_desk_inbox_method'] = 'microsoft_graph'
|
||||
|
||||
gitlab_rails['service_desk_inbox_options'] = {
|
||||
'tenant_id': '<YOUR-TENANT-ID>',
|
||||
'client_id': '<YOUR-CLIENT-ID>',
|
||||
|
@ -268,6 +255,22 @@ Graph API instead of IMAP. Follow the [documentation in the incoming e-mail sect
|
|||
|
||||
The Microsoft Graph API is not yet supported in source installations. See [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/326169) for more details.
|
||||
|
||||
#### Configuring a custom email address suffix
|
||||
|
||||
You can set a custom suffix in your project's Service Desk settings once you have configured a [custom mailbox](#configuring-a-custom-mailbox).
|
||||
It can contain only lowercase letters (`a-z`), numbers (`0-9`), or underscores (`_`).
|
||||
|
||||
When configured, the custom suffix creates a new Service Desk email address, consisting of the
|
||||
`service_desk_email_address` setting and a key of the format: `<project_full_path>-<custom_suffix>`
|
||||
|
||||
For example, suppose the `mygroup/myproject` project Service Desk settings has the following configured:
|
||||
|
||||
- Project name suffix is set to `support`.
|
||||
- Service Desk email address is configured to `contact+%{key}@example.com`.
|
||||
|
||||
The Service Desk email address for this project is: `contact+mygroup-myproject-support@example.com`.
|
||||
The [incoming email](../../administration/incoming_email.md) address still works.
|
||||
|
||||
## Using Service Desk
|
||||
|
||||
You can use Service Desk to [create an issue](#as-an-end-user-issue-creator) or [respond to one](#as-a-responder-to-the-issue).
|
||||
|
|
|
@ -436,6 +436,9 @@ msgid_plural "%{bold_start}%{count}%{bold_end} opened merge requests"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%{closedWeight} complete, %{openWeight} incomplete"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{code_open}Masked:%{code_close} Hidden in job logs. Must match masking requirements."
|
||||
msgstr ""
|
||||
|
||||
|
@ -6352,6 +6355,9 @@ msgstr ""
|
|||
msgid "Checkout|Failed to load states. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|Failed to load the payment form. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|Failed to register credit card. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
@ -31434,9 +31440,6 @@ msgstr ""
|
|||
msgid "SuperSonics|Cloud license"
|
||||
msgstr ""
|
||||
|
||||
msgid "SuperSonics|Enter activation code"
|
||||
msgstr ""
|
||||
|
||||
msgid "SuperSonics|Expires on"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -72,8 +72,14 @@ module QA
|
|||
testcase = example.metadata[:testcase]
|
||||
example.tms('Testcase', testcase) if testcase
|
||||
|
||||
issue = example.metadata.dig(:quarantine, :issue)
|
||||
example.issue('Issue', issue) if issue
|
||||
quarantine_issue = example.metadata.dig(:quarantine, :issue)
|
||||
example.issue('Quarantine issue', quarantine_issue) if quarantine_issue
|
||||
|
||||
spec_file = example.file_path.split('/').last
|
||||
example.issue(
|
||||
'Failure issues',
|
||||
"https://gitlab.com/gitlab-org/gitlab/-/issues?scope=all&state=opened&search=#{spec_file}"
|
||||
)
|
||||
|
||||
example.add_link(name: "Job(#{Env.ci_job_name})", url: Env.ci_job_url) if Env.running_in_ci?
|
||||
end
|
||||
|
|
|
@ -14,20 +14,20 @@ module QA
|
|||
let!(:api_client) { Runtime::API::Client.new(user: user) }
|
||||
let!(:personal_access_token) { api_client.personal_access_token }
|
||||
|
||||
let!(:sandbox) do
|
||||
let(:sandbox) do
|
||||
Resource::Sandbox.fabricate_via_api! do |group|
|
||||
group.api_client = admin_api_client
|
||||
end
|
||||
end
|
||||
|
||||
let!(:source_group) do
|
||||
let(:source_group) do
|
||||
Resource::Sandbox.fabricate_via_api! do |group|
|
||||
group.api_client = api_client
|
||||
group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
end
|
||||
|
||||
let!(:subgroup) do
|
||||
let(:subgroup) do
|
||||
Resource::Group.fabricate_via_api! do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = source_group
|
||||
|
@ -63,6 +63,10 @@ module QA
|
|||
before do
|
||||
sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
|
||||
|
||||
# create groups explicitly before connecting gitlab instance
|
||||
source_group
|
||||
subgroup
|
||||
|
||||
Flow::Login.sign_in(as: user)
|
||||
Page::Main::Menu.perform(&:go_to_create_group)
|
||||
Page::Group::New.perform do |group|
|
||||
|
@ -73,6 +77,7 @@ module QA
|
|||
|
||||
# Non blocking issues:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/331252
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/333678 <- can cause 500 when creating user and group back to back
|
||||
it(
|
||||
'imports group with subgroups and labels',
|
||||
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785',
|
||||
|
@ -96,9 +101,9 @@ module QA
|
|||
Page::Group::BulkImport.perform do |import_page|
|
||||
import_page.import_group(source_group.path, sandbox.path)
|
||||
|
||||
aggregate_failures do
|
||||
expect(import_page).to have_imported_group(source_group.path, wait: 180)
|
||||
expect(import_page).to have_imported_group(source_group.path, wait: 180)
|
||||
|
||||
aggregate_failures do
|
||||
expect { imported_group.reload! }.to eventually_eq(source_group).within(duration: 10)
|
||||
expect { imported_group.labels }.to eventually_include(*source_group.labels).within(duration: 10)
|
||||
|
||||
|
|
|
@ -39,9 +39,9 @@ module QA
|
|||
end
|
||||
|
||||
context 'when using attachments in comments', :object_storage do
|
||||
let(:gif_file_name) { 'banana_sample.gif' }
|
||||
let(:png_file_name) { 'testfile.png' }
|
||||
let(:file_to_attach) do
|
||||
File.absolute_path(File.join('qa', 'fixtures', 'designs', gif_file_name))
|
||||
File.absolute_path(File.join('qa', 'fixtures', 'designs', png_file_name))
|
||||
end
|
||||
|
||||
before do
|
||||
|
@ -50,9 +50,9 @@ module QA
|
|||
|
||||
it 'comments on an issue with an attachment', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1742' do
|
||||
Page::Project::Issue::Show.perform do |show|
|
||||
show.comment('See attached banana for scale', attachment: file_to_attach)
|
||||
show.comment('See attached image for scale', attachment: file_to_attach)
|
||||
|
||||
expect(show.noteable_note_item.find("img[src$='#{gif_file_name}']")).to be_visible
|
||||
expect(show.noteable_note_item.find("img[src$='#{png_file_name}']")).to be_visible
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
9
spec/factories/ci/pending_builds.rb
Normal file
9
spec/factories/ci/pending_builds.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :ci_pending_build, class: 'Ci::PendingBuild' do
|
||||
build factory: :ci_build
|
||||
project
|
||||
protected { build.protected }
|
||||
end
|
||||
end
|
10
spec/factories/ci/running_builds.rb
Normal file
10
spec/factories/ci/running_builds.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :ci_running_build, class: 'Ci::RunningBuild' do
|
||||
build factory: :ci_build
|
||||
project
|
||||
runner factory: :ci_runner
|
||||
runner_type { runner.runner_type }
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
import { GlLabel, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { GlLabel, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
|
||||
import { range } from 'lodash';
|
||||
import Vuex from 'vuex';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import BoardBlockedIcon from '~/boards/components/board_blocked_icon.vue';
|
||||
import BoardCardInner from '~/boards/components/board_card_inner.vue';
|
||||
import { issuableTypes } from '~/boards/constants';
|
||||
|
@ -35,8 +35,14 @@ describe('Board card component', () => {
|
|||
let store;
|
||||
|
||||
const findBoardBlockedIcon = () => wrapper.find(BoardBlockedIcon);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findEpicCountablesTotalTooltip = () => wrapper.findComponent(GlTooltip);
|
||||
const findEpicCountables = () => wrapper.findByTestId('epic-countables');
|
||||
const findEpicCountablesBadgeIssues = () => wrapper.findByTestId('epic-countables-counts-issues');
|
||||
const findEpicCountablesBadgeWeight = () => wrapper.findByTestId('epic-countables-weight-issues');
|
||||
const findEpicCountablesTotalWeight = () => wrapper.findByTestId('epic-countables-total-weight');
|
||||
|
||||
const createStore = () => {
|
||||
const createStore = ({ isEpicBoard = false } = {}) => {
|
||||
store = new Vuex.Store({
|
||||
...defaultStore,
|
||||
state: {
|
||||
|
@ -45,16 +51,14 @@ describe('Board card component', () => {
|
|||
},
|
||||
getters: {
|
||||
isGroupBoard: () => true,
|
||||
isEpicBoard: () => false,
|
||||
isEpicBoard: () => isEpicBoard,
|
||||
isProjectBoard: () => false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const createWrapper = (props = {}) => {
|
||||
createStore();
|
||||
|
||||
wrapper = mount(BoardCardInner, {
|
||||
wrapper = mountExtended(BoardCardInner, {
|
||||
store,
|
||||
propsData: {
|
||||
list,
|
||||
|
@ -88,6 +92,7 @@ describe('Board card component', () => {
|
|||
weight: 1,
|
||||
};
|
||||
|
||||
createStore();
|
||||
createWrapper({ item: issue, list });
|
||||
});
|
||||
|
||||
|
@ -414,7 +419,90 @@ describe('Board card component', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is an epic board', () => {
|
||||
const descendantCounts = {
|
||||
closedEpics: 0,
|
||||
closedIssues: 0,
|
||||
openedEpics: 0,
|
||||
openedIssues: 0,
|
||||
};
|
||||
|
||||
const descendantWeightSum = {
|
||||
closedIssues: 0,
|
||||
openedIssues: 0,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createStore({ isEpicBoard: true });
|
||||
});
|
||||
|
||||
it('should render if the item has issues', () => {
|
||||
createWrapper({
|
||||
item: {
|
||||
...issue,
|
||||
descendantCounts,
|
||||
descendantWeightSum,
|
||||
hasIssues: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findEpicCountables().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should not render if the item does not have issues', () => {
|
||||
createWrapper({
|
||||
item: {
|
||||
...issue,
|
||||
descendantCounts,
|
||||
descendantWeightSum,
|
||||
hasIssues: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findEpicCountablesBadgeIssues().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows render item countBadge and weights correctly', () => {
|
||||
createWrapper({
|
||||
item: {
|
||||
...issue,
|
||||
descendantCounts: {
|
||||
...descendantCounts,
|
||||
openedIssues: 1,
|
||||
},
|
||||
descendantWeightSum: {
|
||||
...descendantWeightSum,
|
||||
openedIssues: 2,
|
||||
},
|
||||
hasIssues: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findEpicCountablesBadgeIssues().text()).toBe('1');
|
||||
expect(findEpicCountablesBadgeWeight().text()).toBe('2');
|
||||
});
|
||||
|
||||
it('renders the tooltip with the correct data', () => {
|
||||
createWrapper({
|
||||
item: {
|
||||
...issue,
|
||||
descendantCounts,
|
||||
descendantWeightSum: {
|
||||
closedIssues: 10,
|
||||
openedIssues: 5,
|
||||
},
|
||||
hasIssues: true,
|
||||
},
|
||||
});
|
||||
|
||||
const tooltip = findEpicCountablesTotalTooltip();
|
||||
expect(tooltip).toBeDefined();
|
||||
|
||||
expect(findEpicCountablesTotalWeight().text()).toBe('10 complete, 5 incomplete');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,33 +1,17 @@
|
|||
import { GlButton } from '@gitlab/ui';
|
||||
import { Extension } from '@tiptap/core';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import ToolbarButton from '~/content_editor/components/toolbar_button.vue';
|
||||
import { createContentEditor } from '~/content_editor/services/create_content_editor';
|
||||
import { createTestEditor, mockChainedCommands } from '../test_utils';
|
||||
|
||||
describe('content_editor/components/toolbar_button', () => {
|
||||
let wrapper;
|
||||
let tiptapEditor;
|
||||
let toggleFooSpy;
|
||||
const CONTENT_TYPE = 'bold';
|
||||
const ICON_NAME = 'bold';
|
||||
const LABEL = 'Bold';
|
||||
|
||||
const buildEditor = () => {
|
||||
toggleFooSpy = jest.fn();
|
||||
tiptapEditor = createContentEditor({
|
||||
extensions: [
|
||||
{
|
||||
tiptapExtension: Extension.create({
|
||||
addCommands() {
|
||||
return {
|
||||
toggleFoo: () => toggleFooSpy,
|
||||
};
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
renderMarkdown: () => true,
|
||||
}).tiptapEditor;
|
||||
tiptapEditor = createTestEditor();
|
||||
|
||||
jest.spyOn(tiptapEditor, 'isActive');
|
||||
};
|
||||
|
@ -78,20 +62,28 @@ describe('content_editor/components/toolbar_button', () => {
|
|||
|
||||
describe('when button is clicked', () => {
|
||||
it('executes the content type command when executeCommand = true', async () => {
|
||||
buildWrapper({ editorCommand: 'toggleFoo' });
|
||||
const editorCommand = 'toggleFoo';
|
||||
const mockCommands = mockChainedCommands(tiptapEditor, [editorCommand, 'focus', 'run']);
|
||||
|
||||
buildWrapper({ editorCommand });
|
||||
|
||||
await findButton().trigger('click');
|
||||
|
||||
expect(toggleFooSpy).toHaveBeenCalled();
|
||||
expect(mockCommands[editorCommand]).toHaveBeenCalled();
|
||||
expect(mockCommands.focus).toHaveBeenCalled();
|
||||
expect(mockCommands.run).toHaveBeenCalled();
|
||||
expect(wrapper.emitted().execute).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not executes the content type command when executeCommand = false', async () => {
|
||||
const editorCommand = 'toggleFoo';
|
||||
const mockCommands = mockChainedCommands(tiptapEditor, [editorCommand, 'run']);
|
||||
|
||||
buildWrapper();
|
||||
|
||||
await findButton().trigger('click');
|
||||
|
||||
expect(toggleFooSpy).not.toHaveBeenCalled();
|
||||
expect(mockCommands[editorCommand]).not.toHaveBeenCalled();
|
||||
expect(wrapper.emitted().execute).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,21 +2,16 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import ToolbarTextStyleDropdown from '~/content_editor/components/toolbar_text_style_dropdown.vue';
|
||||
import { TEXT_STYLE_DROPDOWN_ITEMS } from '~/content_editor/constants';
|
||||
import { createTestContentEditorExtension, createTestEditor } from '../test_utils';
|
||||
import { tiptapExtension as Heading } from '~/content_editor/extensions/heading';
|
||||
import { createTestEditor, mockChainedCommands } from '../test_utils';
|
||||
|
||||
describe('content_editor/components/toolbar_headings_dropdown', () => {
|
||||
let wrapper;
|
||||
let tiptapEditor;
|
||||
let commandMocks;
|
||||
|
||||
const buildEditor = () => {
|
||||
const testExtension = createTestContentEditorExtension({
|
||||
commands: TEXT_STYLE_DROPDOWN_ITEMS.map((item) => item.editorCommand),
|
||||
});
|
||||
|
||||
commandMocks = testExtension.commandMocks;
|
||||
tiptapEditor = createTestEditor({
|
||||
extensions: [testExtension.tiptapExtension],
|
||||
extensions: [Heading],
|
||||
});
|
||||
|
||||
jest.spyOn(tiptapEditor, 'isActive');
|
||||
|
@ -104,9 +99,12 @@ describe('content_editor/components/toolbar_headings_dropdown', () => {
|
|||
|
||||
TEXT_STYLE_DROPDOWN_ITEMS.forEach((textStyle, index) => {
|
||||
const { editorCommand, commandParams } = textStyle;
|
||||
const commands = mockChainedCommands(tiptapEditor, [editorCommand, 'focus', 'run']);
|
||||
|
||||
wrapper.findAllComponents(GlDropdownItem).at(index).vm.$emit('click');
|
||||
expect(commandMocks[editorCommand]).toHaveBeenCalledWith(commandParams || {});
|
||||
expect(commands[editorCommand]).toHaveBeenCalledWith(commandParams || {});
|
||||
expect(commands.focus).toHaveBeenCalled();
|
||||
expect(commands.run).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
import { BulletList } from '@tiptap/extension-bullet-list';
|
||||
import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight';
|
||||
import { Document } from '@tiptap/extension-document';
|
||||
import { Heading } from '@tiptap/extension-heading';
|
||||
import { ListItem } from '@tiptap/extension-list-item';
|
||||
import { Paragraph } from '@tiptap/extension-paragraph';
|
||||
import { Text } from '@tiptap/extension-text';
|
||||
import { Editor } from '@tiptap/vue-2';
|
||||
import { mockTracking } from 'helpers/tracking_helper';
|
||||
import {
|
||||
KEYBOARD_SHORTCUT_TRACKING_ACTION,
|
||||
INPUT_RULE_TRACKING_ACTION,
|
||||
CONTENT_EDITOR_TRACKING_LABEL,
|
||||
} from '~/content_editor/constants';
|
||||
import { tiptapExtension as BulletList } from '~/content_editor/extensions/bullet_list';
|
||||
import { tiptapExtension as CodeBlockLowlight } from '~/content_editor/extensions/code_block_highlight';
|
||||
import { tiptapExtension as Heading } from '~/content_editor/extensions/heading';
|
||||
import { tiptapExtension as ListItem } from '~/content_editor/extensions/list_item';
|
||||
import trackInputRulesAndShortcuts from '~/content_editor/services/track_input_rules_and_shortcuts';
|
||||
import { ENTER_KEY, BACKSPACE_KEY } from '~/lib/utils/keys';
|
||||
import { createTestEditor } from '../test_utils';
|
||||
|
||||
describe('content_editor/services/track_input_rules_and_shortcuts', () => {
|
||||
let trackingSpy;
|
||||
let editor;
|
||||
let trackedExtensions;
|
||||
const HEADING_TEXT = 'Heading text';
|
||||
const extensions = [Document, Paragraph, Text, Heading, CodeBlockLowlight, BulletList, ListItem];
|
||||
const extensions = [Heading, CodeBlockLowlight, BulletList, ListItem];
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, null, jest.spyOn);
|
||||
|
@ -29,7 +26,7 @@ describe('content_editor/services/track_input_rules_and_shortcuts', () => {
|
|||
describe('given the heading extension is instrumented', () => {
|
||||
beforeEach(() => {
|
||||
trackedExtensions = extensions.map(trackInputRulesAndShortcuts);
|
||||
editor = new Editor({
|
||||
editor = createTestEditor({
|
||||
extensions: extensions.map(trackInputRulesAndShortcuts),
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ import { Editor } from '@tiptap/vue-2';
|
|||
* include in the editor
|
||||
* @returns An instance of a Tiptap’s Editor class
|
||||
*/
|
||||
export const createTestEditor = ({ extensions = [] }) => {
|
||||
export const createTestEditor = ({ extensions = [] } = {}) => {
|
||||
return new Editor({
|
||||
extensions: [Document, Text, Paragraph, ...extensions],
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import '~/flash';
|
||||
import { GlModal, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
import Vue from 'vue';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import appComponent from '~/groups/components/app.vue';
|
||||
import groupFolderComponent from '~/groups/components/group_folder.vue';
|
||||
import groupItemComponent from '~/groups/components/group_item.vue';
|
||||
|
@ -27,6 +27,7 @@ import {
|
|||
const $toast = {
|
||||
show: jest.fn(),
|
||||
};
|
||||
jest.mock('~/flash');
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let wrapper;
|
||||
|
@ -123,12 +124,12 @@ describe('AppComponent', () => {
|
|||
mock.onGet('/dashboard/groups.json').reply(400);
|
||||
|
||||
jest.spyOn(window, 'scrollTo').mockImplementation(() => {});
|
||||
jest.spyOn(window, 'Flash').mockImplementation(() => {});
|
||||
|
||||
return vm.fetchGroups({}).then(() => {
|
||||
expect(vm.isLoading).toBe(false);
|
||||
expect(window.scrollTo).toHaveBeenCalledWith({ behavior: 'smooth', top: 0 });
|
||||
expect(window.Flash).toHaveBeenCalledWith('An error occurred. Please try again.');
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message: 'An error occurred. Please try again.',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -324,15 +325,13 @@ describe('AppComponent', () => {
|
|||
const message = 'An error occurred. Please try again.';
|
||||
jest.spyOn(vm.service, 'leaveGroup').mockRejectedValue({ status: 500 });
|
||||
jest.spyOn(vm.store, 'removeGroup');
|
||||
jest.spyOn(window, 'Flash').mockImplementation(() => {});
|
||||
|
||||
vm.leaveGroup();
|
||||
|
||||
expect(vm.targetGroup.isBeingRemoved).toBe(true);
|
||||
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
|
||||
return waitForPromises().then(() => {
|
||||
expect(vm.store.removeGroup).not.toHaveBeenCalled();
|
||||
expect(window.Flash).toHaveBeenCalledWith(message);
|
||||
expect(createFlash).toHaveBeenCalledWith({ message });
|
||||
expect(vm.targetGroup.isBeingRemoved).toBe(false);
|
||||
});
|
||||
});
|
||||
|
@ -341,15 +340,13 @@ describe('AppComponent', () => {
|
|||
const message = 'Failed to leave the group. Please make sure you are not the only owner.';
|
||||
jest.spyOn(vm.service, 'leaveGroup').mockRejectedValue({ status: 403 });
|
||||
jest.spyOn(vm.store, 'removeGroup');
|
||||
jest.spyOn(window, 'Flash').mockImplementation(() => {});
|
||||
|
||||
vm.leaveGroup(childGroupItem, groupItem);
|
||||
|
||||
expect(vm.targetGroup.isBeingRemoved).toBe(true);
|
||||
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
|
||||
return waitForPromises().then(() => {
|
||||
expect(vm.store.removeGroup).not.toHaveBeenCalled();
|
||||
expect(window.Flash).toHaveBeenCalledWith(message);
|
||||
expect(createFlash).toHaveBeenCalledWith({ message });
|
||||
expect(vm.targetGroup.isBeingRemoved).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -190,7 +190,9 @@ describe('UploadBlobModal', () => {
|
|||
});
|
||||
|
||||
it('creates a flash error', () => {
|
||||
expect(createFlash).toHaveBeenCalledWith('Error uploading file. Please try again.');
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message: 'Error uploading file. Please try again.',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!('cascade_delete_freeze_periods')
|
||||
|
||||
RSpec.describe CascadeDeleteFreezePeriods do
|
||||
let(:namespace) { table(:namespaces).create!(name: 'deploy_freeze', path: 'deploy_freeze') }
|
||||
let(:project) { table(:projects).create!(id: 1, namespace_id: namespace.id) }
|
||||
let(:freeze_periods) { table(:ci_freeze_periods) }
|
||||
|
||||
describe "#up" do
|
||||
it 'allows for a project to be deleted' do
|
||||
freeze_periods.create!(id: 1, project_id: project.id, freeze_start: '5 * * * *', freeze_end: '6 * * * *', cron_timezone: 'UTC')
|
||||
migrate!
|
||||
|
||||
project.delete
|
||||
|
||||
expect(freeze_periods.where(project_id: project.id).count).to be_zero
|
||||
end
|
||||
end
|
||||
end
|
|
@ -384,7 +384,7 @@ RSpec.describe Ci::Build do
|
|||
|
||||
context 'when there is a queuing entry already present' do
|
||||
before do
|
||||
::Ci::PendingBuild.create!(build: build, project: build.project)
|
||||
create(:ci_pending_build, build: build, project: build.project)
|
||||
end
|
||||
|
||||
it 'does not raise an error' do
|
||||
|
@ -396,7 +396,7 @@ RSpec.describe Ci::Build do
|
|||
context 'when both failure scenario happen at the same time' do
|
||||
before do
|
||||
::Ci::Build.find(build.id).update_column(:lock_version, 100)
|
||||
::Ci::PendingBuild.create!(build: build, project: build.project)
|
||||
create(:ci_pending_build, build: build, project: build.project)
|
||||
end
|
||||
|
||||
it 'raises stale object error exception' do
|
||||
|
@ -478,7 +478,7 @@ RSpec.describe Ci::Build do
|
|||
let(:build) { create(:ci_build, :pending) }
|
||||
|
||||
before do
|
||||
::Ci::PendingBuild.create!(build: build, project: build.project)
|
||||
create(:ci_pending_build, build: build, project: build.project)
|
||||
::Ci::Build.find(build.id).update_column(:lock_version, 100)
|
||||
end
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ RSpec.describe Ci::PendingBuild do
|
|||
|
||||
context 'when another queuing entry exists for given build' do
|
||||
before do
|
||||
described_class.create!(build: build, project: project, protected: false)
|
||||
create(:ci_pending_build, build: build, project: project)
|
||||
end
|
||||
|
||||
it 'returns a build id as a result' do
|
||||
|
|
|
@ -21,10 +21,7 @@ RSpec.describe Ci::RunningBuild do
|
|||
|
||||
context 'when another queuing entry exists for given build' do
|
||||
before do
|
||||
described_class.create!(build: build,
|
||||
project: project,
|
||||
runner: runner,
|
||||
runner_type: runner.runner_type)
|
||||
create(:ci_running_build, build: build, project: project, runner: runner)
|
||||
end
|
||||
|
||||
it 'returns a build id as a result' do
|
||||
|
|
|
@ -45,7 +45,7 @@ RSpec.describe Ci::UpdateBuildQueueService do
|
|||
|
||||
context 'when duplicate entry exists' do
|
||||
before do
|
||||
::Ci::PendingBuild.create!(build: build, project: project)
|
||||
create(:ci_pending_build, build: build, project: build.project)
|
||||
end
|
||||
|
||||
it 'does nothing and returns build id' do
|
||||
|
@ -66,7 +66,7 @@ RSpec.describe Ci::UpdateBuildQueueService do
|
|||
|
||||
context 'when pending build exists' do
|
||||
before do
|
||||
Ci::PendingBuild.create!(build: build, project: project)
|
||||
create(:ci_pending_build, build: build, project: build.project)
|
||||
end
|
||||
|
||||
it 'removes pending build in a transaction' do
|
||||
|
@ -146,9 +146,7 @@ RSpec.describe Ci::UpdateBuildQueueService do
|
|||
|
||||
context 'when duplicate entry exists' do
|
||||
before do
|
||||
::Ci::RunningBuild.create!(
|
||||
build: build, project: project, runner: runner, runner_type: runner.runner_type
|
||||
)
|
||||
create(:ci_running_build, build: build, project: project, runner: runner)
|
||||
end
|
||||
|
||||
it 'does nothing and returns build id' do
|
||||
|
@ -169,9 +167,7 @@ RSpec.describe Ci::UpdateBuildQueueService do
|
|||
|
||||
context 'when shared runner build tracking entry exists' do
|
||||
before do
|
||||
Ci::RunningBuild.create!(
|
||||
build: build, project: project, runner: runner, runner_type: runner.runner_type
|
||||
)
|
||||
create(:ci_running_build, build: build, project: project, runner: runner)
|
||||
end
|
||||
|
||||
it 'removes shared runner build' do
|
||||
|
|
Loading…
Reference in a new issue