Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d4c968c95c
commit
f1a1bd96b7
|
@ -146,7 +146,7 @@ review-stop:
|
||||||
|
|
||||||
.allure-report-base:
|
.allure-report-base:
|
||||||
image:
|
image:
|
||||||
name: ${GITLAB_DEPENDENCY_PROXY}andrcuns/allure-report-publisher:0.3.2
|
name: ${GITLAB_DEPENDENCY_PROXY}andrcuns/allure-report-publisher:0.3.4
|
||||||
entrypoint: [""]
|
entrypoint: [""]
|
||||||
stage: post-qa
|
stage: post-qa
|
||||||
variables:
|
variables:
|
||||||
|
|
|
@ -2,6 +2,14 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
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)
|
## 13.12.4 (2021-06-14)
|
||||||
|
|
||||||
### Fixed (3 changes)
|
### Fixed (3 changes)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlLabel, GlTooltipDirective, GlIcon, GlLoadingIcon } from '@gitlab/ui';
|
import { GlLabel, GlTooltip, GlTooltipDirective, GlIcon, GlLoadingIcon } from '@gitlab/ui';
|
||||||
import { sortBy } from 'lodash';
|
import { sortBy } from 'lodash';
|
||||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||||
import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner';
|
import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner';
|
||||||
|
@ -16,6 +16,7 @@ import IssueTimeEstimate from './issue_time_estimate.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
GlTooltip,
|
||||||
GlLabel,
|
GlLabel,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
GlIcon,
|
GlIcon,
|
||||||
|
@ -55,7 +56,7 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['isShowingLabels', 'issuableType']),
|
...mapState(['isShowingLabels', 'issuableType', 'allowSubEpics']),
|
||||||
...mapGetters(['isEpicBoard']),
|
...mapGetters(['isEpicBoard']),
|
||||||
cappedAssignees() {
|
cappedAssignees() {
|
||||||
// e.g. maxRender is 4,
|
// e.g. maxRender is 4,
|
||||||
|
@ -99,6 +100,9 @@ export default {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
shouldRenderEpicCountables() {
|
||||||
|
return this.isEpicBoard && this.item.hasIssues;
|
||||||
|
},
|
||||||
showLabelFooter() {
|
showLabelFooter() {
|
||||||
return this.isShowingLabels && this.item.labels.find(this.showLabel);
|
return this.isShowingLabels && this.item.labels.find(this.showLabel);
|
||||||
},
|
},
|
||||||
|
@ -115,6 +119,17 @@ export default {
|
||||||
}
|
}
|
||||||
return __('Blocked issue');
|
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: {
|
methods: {
|
||||||
...mapActions(['performSearch', 'setError']),
|
...mapActions(['performSearch', 'setError']),
|
||||||
|
@ -227,6 +242,59 @@ export default {
|
||||||
{{ itemId }}
|
{{ itemId }}
|
||||||
</span>
|
</span>
|
||||||
<span class="board-info-items gl-mt-3 gl-display-inline-block">
|
<span class="board-info-items gl-mt-3 gl-display-inline-block">
|
||||||
|
<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
|
<issue-due-date
|
||||||
v-if="item.dueDate"
|
v-if="item.dueDate"
|
||||||
:date="item.dueDate"
|
:date="item.dueDate"
|
||||||
|
@ -239,6 +307,7 @@ export default {
|
||||||
@click="filterByWeight(item.weight)"
|
@click="filterByWeight(item.weight)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="board-card-assignee gl-display-flex">
|
<div class="board-card-assignee gl-display-flex">
|
||||||
<user-avatar-link
|
<user-avatar-link
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global Flash */
|
/* global createFlash */
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import axios from './lib/utils/axios_utils';
|
import axios from './lib/utils/axios_utils';
|
||||||
|
@ -71,5 +71,9 @@ export function fetchCommitMergeRequests() {
|
||||||
|
|
||||||
$container.html($content);
|
$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>
|
<script>
|
||||||
/* global Flash */
|
|
||||||
|
|
||||||
import { GlLoadingIcon, GlModal } from '@gitlab/ui';
|
import { GlLoadingIcon, GlModal } from '@gitlab/ui';
|
||||||
|
import createFlash from '~/flash';
|
||||||
import { getParameterByName } from '~/lib/utils/common_utils';
|
import { getParameterByName } from '~/lib/utils/common_utils';
|
||||||
import { HIDDEN_CLASS } from '~/lib/utils/constants';
|
import { HIDDEN_CLASS } from '~/lib/utils/constants';
|
||||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||||
|
@ -116,7 +115,7 @@ export default {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
|
||||||
Flash(COMMON_STR.FAILURE);
|
createFlash({ message: COMMON_STR.FAILURE });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fetchAllGroups() {
|
fetchAllGroups() {
|
||||||
|
@ -202,7 +201,7 @@ export default {
|
||||||
if (err.status === 403) {
|
if (err.status === 403) {
|
||||||
message = COMMON_STR.LEAVE_FORBIDDEN;
|
message = COMMON_STR.LEAVE_FORBIDDEN;
|
||||||
}
|
}
|
||||||
Flash(message);
|
createFlash({ message });
|
||||||
this.targetGroup.isBeingRemoved = false;
|
this.targetGroup.isBeingRemoved = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@ import $ from 'jquery';
|
||||||
import loadAwardsHandler from '~/awards_handler';
|
import loadAwardsHandler from '~/awards_handler';
|
||||||
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
|
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
|
||||||
import Diff from '~/diff';
|
import Diff from '~/diff';
|
||||||
import flash from '~/flash';
|
import createFlash from '~/flash';
|
||||||
import initChangesDropdown from '~/init_changes_dropdown';
|
import initChangesDropdown from '~/init_changes_dropdown';
|
||||||
import initNotes from '~/init_notes';
|
import initNotes from '~/init_notes';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
@ -39,7 +39,7 @@ if (filesContainer.length) {
|
||||||
new Diff();
|
new Diff();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
flash({ message: __('An error occurred while retrieving diff files') });
|
createFlash({ message: __('An error occurred while retrieving diff files') });
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
new Diff();
|
new Diff();
|
||||||
|
|
|
@ -170,7 +170,7 @@ export default {
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
createFlash(ERROR_MESSAGE);
|
createFlash({ message: ERROR_MESSAGE });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
formData() {
|
formData() {
|
||||||
|
|
|
@ -8,7 +8,7 @@ class Admin::RunnersController < Admin::ApplicationController
|
||||||
push_frontend_feature_flag(:runner_list_view_vue_ui, current_user, default_enabled: :yaml)
|
push_frontend_feature_flag(:runner_list_view_vue_ui, current_user, default_enabled: :yaml)
|
||||||
end
|
end
|
||||||
|
|
||||||
feature_category :continuous_integration
|
feature_category :runner
|
||||||
|
|
||||||
NUMBER_OF_RUNNERS_PER_PAGE = 30
|
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]
|
before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
|
||||||
|
|
||||||
feature_category :continuous_integration
|
feature_category :runner
|
||||||
|
|
||||||
def show
|
def show
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Projects::RunnersController < Projects::ApplicationController
|
||||||
|
|
||||||
layout 'project_settings'
|
layout 'project_settings'
|
||||||
|
|
||||||
feature_category :continuous_integration
|
feature_category :runner
|
||||||
|
|
||||||
def index
|
def index
|
||||||
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')
|
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
3f73aa7d2cff11d00b330d88e76daaa058f82b7012da3c244f246da6e538921c
|
|
@ -25627,6 +25627,9 @@ ALTER TABLE ONLY geo_event_log
|
||||||
ALTER TABLE ONLY deployments
|
ALTER TABLE ONLY deployments
|
||||||
ADD CONSTRAINT fk_289bba3222 FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE SET NULL;
|
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
|
ALTER TABLE ONLY notes
|
||||||
ADD CONSTRAINT fk_2e82291620 FOREIGN KEY (review_id) REFERENCES reviews(id) ON DELETE SET NULL;
|
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
|
ALTER TABLE ONLY protected_branch_unprotect_access_levels
|
||||||
ADD CONSTRAINT fk_rails_2d2aba21ef FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
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
|
ALTER TABLE ONLY issuable_severities
|
||||||
ADD CONSTRAINT fk_rails_2fbb74ad6d FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_rails_2fbb74ad6d FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,9 @@ export default {
|
||||||
try {
|
try {
|
||||||
await this.contentEditor.setSerializedContent(this.content);
|
await this.contentEditor.setSerializedContent(this.content);
|
||||||
} catch (e) {
|
} 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: {
|
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))
|
.then(({ data }) => commit(types.RECEIVE_USERS_SUCCESS, data))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
commit(types.RECEIVE_USERS_ERROR, 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. |
|
| [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. |
|
| [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.|
|
| [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
|
## Install GitLab on cloud providers
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated
|
||||||
type: reference, howto
|
type: reference, howto
|
||||||
---
|
---
|
||||||
|
|
||||||
# Threads **(FREE)**
|
# Comments and threads **(FREE)**
|
||||||
|
|
||||||
GitLab encourages communication through comments, threads, and
|
GitLab encourages communication through comments, threads, and
|
||||||
[code suggestions](../project/merge_requests/reviews/suggestions.md).
|
[code suggestions](../project/merge_requests/reviews/suggestions.md).
|
||||||
|
|
|
@ -112,6 +112,12 @@ You can filter by the following:
|
||||||
- Author
|
- Author
|
||||||
- Label
|
- 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
|
### Move epics and lists
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5079) in GitLab 14.0.
|
> [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. Go to **Settings > General > Service Desk**.
|
||||||
1. From the dropdown **Template to append to all Service Desk issues**, select your template.
|
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.
|
> [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. Enter a new name in **Email display name**.
|
||||||
1. Select **Save Changes**.
|
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.
|
> - [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.
|
> - [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
|
It is possible to customize the email address used by Service Desk. To do this, you must configure
|
||||||
issues by sending emails to the Service Desk email address. The default
|
both a [custom mailbox](#configuring-a-custom-mailbox) and a
|
||||||
address has the following format:
|
[custom suffix](#configuring-a-custom-email-address-suffix).
|
||||||
`project_contact+%{key}@example.com`.
|
|
||||||
|
|
||||||
The `%{key}` part is used to find the project where the issue should be created. The
|
#### Configuring a custom mailbox
|
||||||
`%{key}` part combines the path to the project and configurable project name suffix:
|
|
||||||
`<project_full_path>-<project_name_suffix>`.
|
|
||||||
|
|
||||||
You can set the project name suffix in your project's Service Desk settings.
|
NOTE:
|
||||||
It can contain only lowercase letters (`a-z`), numbers (`0-9`), or underscores (`_`).
|
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:
|
NOTE:
|
||||||
The `service_desk_email` and `incoming_email` configurations should
|
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
|
`service_desk_email` mailbox are processed by a different worker and it would
|
||||||
not recognize `incoming_email` emails.
|
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:
|
- Example for installations from source:
|
||||||
|
|
||||||
|
@ -207,36 +214,22 @@ To configure a custom email address for Service Desk with IMAP, add the followin
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
gitlab_rails['service_desk_email_enabled'] = true
|
gitlab_rails['service_desk_email_enabled'] = true
|
||||||
|
|
||||||
gitlab_rails['service_desk_email_address'] = "project_contact+%{key}@gmail.com"
|
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_email'] = "project_support@gmail.com"
|
||||||
|
|
||||||
gitlab_rails['service_desk_email_password'] = "[REDACTED]"
|
gitlab_rails['service_desk_email_password'] = "[REDACTED]"
|
||||||
|
|
||||||
gitlab_rails['service_desk_email_mailbox_name'] = "inbox"
|
gitlab_rails['service_desk_email_mailbox_name'] = "inbox"
|
||||||
|
|
||||||
gitlab_rails['service_desk_email_idle_timeout'] = 60
|
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_log_file'] = "/var/log/gitlab/mailroom/mail_room_json.log"
|
||||||
|
|
||||||
gitlab_rails['service_desk_email_host'] = "imap.gmail.com"
|
gitlab_rails['service_desk_email_host'] = "imap.gmail.com"
|
||||||
|
|
||||||
gitlab_rails['service_desk_email_port'] = 993
|
gitlab_rails['service_desk_email_port'] = 993
|
||||||
|
|
||||||
gitlab_rails['service_desk_email_ssl'] = true
|
gitlab_rails['service_desk_email_ssl'] = true
|
||||||
|
|
||||||
gitlab_rails['service_desk_email_start_tls'] = false
|
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
|
The configuration options are the same as for configuring
|
||||||
[incoming email](../../administration/incoming_email.md#set-it-up).
|
[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)
|
> 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
|
```ruby
|
||||||
gitlab_rails['service_desk_email_enabled'] = true
|
gitlab_rails['service_desk_email_enabled'] = true
|
||||||
|
|
||||||
gitlab_rails['service_desk_email_address'] = "project_contact+%{key}@example.onmicrosoft.com"
|
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_email'] = "project_contact@example.onmicrosoft.com"
|
||||||
|
|
||||||
gitlab_rails['service_desk_email_mailbox_name'] = "inbox"
|
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_email_log_file'] = "/var/log/gitlab/mailroom/mail_room_json.log"
|
||||||
|
|
||||||
gitlab_rails['service_desk_inbox_method'] = 'microsoft_graph'
|
gitlab_rails['service_desk_inbox_method'] = 'microsoft_graph'
|
||||||
|
|
||||||
gitlab_rails['service_desk_inbox_options'] = {
|
gitlab_rails['service_desk_inbox_options'] = {
|
||||||
'tenant_id': '<YOUR-TENANT-ID>',
|
'tenant_id': '<YOUR-TENANT-ID>',
|
||||||
'client_id': '<YOUR-CLIENT-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.
|
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
|
## 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).
|
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[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "%{closedWeight} complete, %{openWeight} incomplete"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "%{code_open}Masked:%{code_close} Hidden in job logs. Must match masking requirements."
|
msgid "%{code_open}Masked:%{code_close} Hidden in job logs. Must match masking requirements."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -6352,6 +6355,9 @@ msgstr ""
|
||||||
msgid "Checkout|Failed to load states. Please try again."
|
msgid "Checkout|Failed to load states. Please try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Checkout|Failed to load the payment form. Please try again."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Checkout|Failed to register credit card. Please try again."
|
msgid "Checkout|Failed to register credit card. Please try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -31434,9 +31440,6 @@ msgstr ""
|
||||||
msgid "SuperSonics|Cloud license"
|
msgid "SuperSonics|Cloud license"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "SuperSonics|Enter activation code"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "SuperSonics|Expires on"
|
msgid "SuperSonics|Expires on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,14 @@ module QA
|
||||||
testcase = example.metadata[:testcase]
|
testcase = example.metadata[:testcase]
|
||||||
example.tms('Testcase', testcase) if testcase
|
example.tms('Testcase', testcase) if testcase
|
||||||
|
|
||||||
issue = example.metadata.dig(:quarantine, :issue)
|
quarantine_issue = example.metadata.dig(:quarantine, :issue)
|
||||||
example.issue('Issue', issue) if 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?
|
example.add_link(name: "Job(#{Env.ci_job_name})", url: Env.ci_job_url) if Env.running_in_ci?
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,20 +14,20 @@ module QA
|
||||||
let!(:api_client) { Runtime::API::Client.new(user: user) }
|
let!(:api_client) { Runtime::API::Client.new(user: user) }
|
||||||
let!(:personal_access_token) { api_client.personal_access_token }
|
let!(:personal_access_token) { api_client.personal_access_token }
|
||||||
|
|
||||||
let!(:sandbox) do
|
let(:sandbox) do
|
||||||
Resource::Sandbox.fabricate_via_api! do |group|
|
Resource::Sandbox.fabricate_via_api! do |group|
|
||||||
group.api_client = admin_api_client
|
group.api_client = admin_api_client
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:source_group) do
|
let(:source_group) do
|
||||||
Resource::Sandbox.fabricate_via_api! do |group|
|
Resource::Sandbox.fabricate_via_api! do |group|
|
||||||
group.api_client = api_client
|
group.api_client = api_client
|
||||||
group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
|
group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:subgroup) do
|
let(:subgroup) do
|
||||||
Resource::Group.fabricate_via_api! do |group|
|
Resource::Group.fabricate_via_api! do |group|
|
||||||
group.api_client = api_client
|
group.api_client = api_client
|
||||||
group.sandbox = source_group
|
group.sandbox = source_group
|
||||||
|
@ -63,6 +63,10 @@ module QA
|
||||||
before do
|
before do
|
||||||
sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
|
sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
|
||||||
|
|
||||||
|
# create groups explicitly before connecting gitlab instance
|
||||||
|
source_group
|
||||||
|
subgroup
|
||||||
|
|
||||||
Flow::Login.sign_in(as: user)
|
Flow::Login.sign_in(as: user)
|
||||||
Page::Main::Menu.perform(&:go_to_create_group)
|
Page::Main::Menu.perform(&:go_to_create_group)
|
||||||
Page::Group::New.perform do |group|
|
Page::Group::New.perform do |group|
|
||||||
|
@ -73,6 +77,7 @@ module QA
|
||||||
|
|
||||||
# Non blocking issues:
|
# Non blocking issues:
|
||||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/331252
|
# 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(
|
it(
|
||||||
'imports group with subgroups and labels',
|
'imports group with subgroups and labels',
|
||||||
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785',
|
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785',
|
||||||
|
@ -96,9 +101,9 @@ module QA
|
||||||
Page::Group::BulkImport.perform do |import_page|
|
Page::Group::BulkImport.perform do |import_page|
|
||||||
import_page.import_group(source_group.path, sandbox.path)
|
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.reload! }.to eventually_eq(source_group).within(duration: 10)
|
||||||
expect { imported_group.labels }.to eventually_include(*source_group.labels).within(duration: 10)
|
expect { imported_group.labels }.to eventually_include(*source_group.labels).within(duration: 10)
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,9 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when using attachments in comments', :object_storage do
|
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
|
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
|
end
|
||||||
|
|
||||||
before do
|
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
|
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|
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -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 { GlLabel, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
|
||||||
import { mount } from '@vue/test-utils';
|
|
||||||
import { range } from 'lodash';
|
import { range } from 'lodash';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
|
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import BoardBlockedIcon from '~/boards/components/board_blocked_icon.vue';
|
import BoardBlockedIcon from '~/boards/components/board_blocked_icon.vue';
|
||||||
import BoardCardInner from '~/boards/components/board_card_inner.vue';
|
import BoardCardInner from '~/boards/components/board_card_inner.vue';
|
||||||
import { issuableTypes } from '~/boards/constants';
|
import { issuableTypes } from '~/boards/constants';
|
||||||
|
@ -35,8 +35,14 @@ describe('Board card component', () => {
|
||||||
let store;
|
let store;
|
||||||
|
|
||||||
const findBoardBlockedIcon = () => wrapper.find(BoardBlockedIcon);
|
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({
|
store = new Vuex.Store({
|
||||||
...defaultStore,
|
...defaultStore,
|
||||||
state: {
|
state: {
|
||||||
|
@ -45,16 +51,14 @@ describe('Board card component', () => {
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
isGroupBoard: () => true,
|
isGroupBoard: () => true,
|
||||||
isEpicBoard: () => false,
|
isEpicBoard: () => isEpicBoard,
|
||||||
isProjectBoard: () => false,
|
isProjectBoard: () => false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createWrapper = (props = {}) => {
|
const createWrapper = (props = {}) => {
|
||||||
createStore();
|
wrapper = mountExtended(BoardCardInner, {
|
||||||
|
|
||||||
wrapper = mount(BoardCardInner, {
|
|
||||||
store,
|
store,
|
||||||
propsData: {
|
propsData: {
|
||||||
list,
|
list,
|
||||||
|
@ -88,6 +92,7 @@ describe('Board card component', () => {
|
||||||
weight: 1,
|
weight: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
createStore();
|
||||||
createWrapper({ item: issue, list });
|
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 { GlButton } from '@gitlab/ui';
|
||||||
import { Extension } from '@tiptap/core';
|
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import ToolbarButton from '~/content_editor/components/toolbar_button.vue';
|
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', () => {
|
describe('content_editor/components/toolbar_button', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let tiptapEditor;
|
let tiptapEditor;
|
||||||
let toggleFooSpy;
|
|
||||||
const CONTENT_TYPE = 'bold';
|
const CONTENT_TYPE = 'bold';
|
||||||
const ICON_NAME = 'bold';
|
const ICON_NAME = 'bold';
|
||||||
const LABEL = 'Bold';
|
const LABEL = 'Bold';
|
||||||
|
|
||||||
const buildEditor = () => {
|
const buildEditor = () => {
|
||||||
toggleFooSpy = jest.fn();
|
tiptapEditor = createTestEditor();
|
||||||
tiptapEditor = createContentEditor({
|
|
||||||
extensions: [
|
|
||||||
{
|
|
||||||
tiptapExtension: Extension.create({
|
|
||||||
addCommands() {
|
|
||||||
return {
|
|
||||||
toggleFoo: () => toggleFooSpy,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
renderMarkdown: () => true,
|
|
||||||
}).tiptapEditor;
|
|
||||||
|
|
||||||
jest.spyOn(tiptapEditor, 'isActive');
|
jest.spyOn(tiptapEditor, 'isActive');
|
||||||
};
|
};
|
||||||
|
@ -78,20 +62,28 @@ describe('content_editor/components/toolbar_button', () => {
|
||||||
|
|
||||||
describe('when button is clicked', () => {
|
describe('when button is clicked', () => {
|
||||||
it('executes the content type command when executeCommand = true', async () => {
|
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');
|
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);
|
expect(wrapper.emitted().execute).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not executes the content type command when executeCommand = false', async () => {
|
it('does not executes the content type command when executeCommand = false', async () => {
|
||||||
|
const editorCommand = 'toggleFoo';
|
||||||
|
const mockCommands = mockChainedCommands(tiptapEditor, [editorCommand, 'run']);
|
||||||
|
|
||||||
buildWrapper();
|
buildWrapper();
|
||||||
|
|
||||||
await findButton().trigger('click');
|
await findButton().trigger('click');
|
||||||
|
|
||||||
expect(toggleFooSpy).not.toHaveBeenCalled();
|
expect(mockCommands[editorCommand]).not.toHaveBeenCalled();
|
||||||
expect(wrapper.emitted().execute).toHaveLength(1);
|
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 { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import ToolbarTextStyleDropdown from '~/content_editor/components/toolbar_text_style_dropdown.vue';
|
import ToolbarTextStyleDropdown from '~/content_editor/components/toolbar_text_style_dropdown.vue';
|
||||||
import { TEXT_STYLE_DROPDOWN_ITEMS } from '~/content_editor/constants';
|
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', () => {
|
describe('content_editor/components/toolbar_headings_dropdown', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let tiptapEditor;
|
let tiptapEditor;
|
||||||
let commandMocks;
|
|
||||||
|
|
||||||
const buildEditor = () => {
|
const buildEditor = () => {
|
||||||
const testExtension = createTestContentEditorExtension({
|
|
||||||
commands: TEXT_STYLE_DROPDOWN_ITEMS.map((item) => item.editorCommand),
|
|
||||||
});
|
|
||||||
|
|
||||||
commandMocks = testExtension.commandMocks;
|
|
||||||
tiptapEditor = createTestEditor({
|
tiptapEditor = createTestEditor({
|
||||||
extensions: [testExtension.tiptapExtension],
|
extensions: [Heading],
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.spyOn(tiptapEditor, 'isActive');
|
jest.spyOn(tiptapEditor, 'isActive');
|
||||||
|
@ -104,9 +99,12 @@ describe('content_editor/components/toolbar_headings_dropdown', () => {
|
||||||
|
|
||||||
TEXT_STYLE_DROPDOWN_ITEMS.forEach((textStyle, index) => {
|
TEXT_STYLE_DROPDOWN_ITEMS.forEach((textStyle, index) => {
|
||||||
const { editorCommand, commandParams } = textStyle;
|
const { editorCommand, commandParams } = textStyle;
|
||||||
|
const commands = mockChainedCommands(tiptapEditor, [editorCommand, 'focus', 'run']);
|
||||||
|
|
||||||
wrapper.findAllComponents(GlDropdownItem).at(index).vm.$emit('click');
|
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 { mockTracking } from 'helpers/tracking_helper';
|
||||||
import {
|
import {
|
||||||
KEYBOARD_SHORTCUT_TRACKING_ACTION,
|
KEYBOARD_SHORTCUT_TRACKING_ACTION,
|
||||||
INPUT_RULE_TRACKING_ACTION,
|
INPUT_RULE_TRACKING_ACTION,
|
||||||
CONTENT_EDITOR_TRACKING_LABEL,
|
CONTENT_EDITOR_TRACKING_LABEL,
|
||||||
} from '~/content_editor/constants';
|
} 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 trackInputRulesAndShortcuts from '~/content_editor/services/track_input_rules_and_shortcuts';
|
||||||
import { ENTER_KEY, BACKSPACE_KEY } from '~/lib/utils/keys';
|
import { ENTER_KEY, BACKSPACE_KEY } from '~/lib/utils/keys';
|
||||||
|
import { createTestEditor } from '../test_utils';
|
||||||
|
|
||||||
describe('content_editor/services/track_input_rules_and_shortcuts', () => {
|
describe('content_editor/services/track_input_rules_and_shortcuts', () => {
|
||||||
let trackingSpy;
|
let trackingSpy;
|
||||||
let editor;
|
let editor;
|
||||||
let trackedExtensions;
|
let trackedExtensions;
|
||||||
const HEADING_TEXT = 'Heading text';
|
const HEADING_TEXT = 'Heading text';
|
||||||
const extensions = [Document, Paragraph, Text, Heading, CodeBlockLowlight, BulletList, ListItem];
|
const extensions = [Heading, CodeBlockLowlight, BulletList, ListItem];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
trackingSpy = mockTracking(undefined, null, jest.spyOn);
|
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', () => {
|
describe('given the heading extension is instrumented', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
trackedExtensions = extensions.map(trackInputRulesAndShortcuts);
|
trackedExtensions = extensions.map(trackInputRulesAndShortcuts);
|
||||||
editor = new Editor({
|
editor = createTestEditor({
|
||||||
extensions: extensions.map(trackInputRulesAndShortcuts),
|
extensions: extensions.map(trackInputRulesAndShortcuts),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { Editor } from '@tiptap/vue-2';
|
||||||
* include in the editor
|
* include in the editor
|
||||||
* @returns An instance of a Tiptap’s Editor class
|
* @returns An instance of a Tiptap’s Editor class
|
||||||
*/
|
*/
|
||||||
export const createTestEditor = ({ extensions = [] }) => {
|
export const createTestEditor = ({ extensions = [] } = {}) => {
|
||||||
return new Editor({
|
return new Editor({
|
||||||
extensions: [Document, Text, Paragraph, ...extensions],
|
extensions: [Document, Text, Paragraph, ...extensions],
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import '~/flash';
|
|
||||||
import { GlModal, GlLoadingIcon } from '@gitlab/ui';
|
import { GlModal, GlLoadingIcon } from '@gitlab/ui';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import waitForPromises from 'helpers/wait_for_promises';
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
|
import createFlash from '~/flash';
|
||||||
import appComponent from '~/groups/components/app.vue';
|
import appComponent from '~/groups/components/app.vue';
|
||||||
import groupFolderComponent from '~/groups/components/group_folder.vue';
|
import groupFolderComponent from '~/groups/components/group_folder.vue';
|
||||||
import groupItemComponent from '~/groups/components/group_item.vue';
|
import groupItemComponent from '~/groups/components/group_item.vue';
|
||||||
|
@ -27,6 +27,7 @@ import {
|
||||||
const $toast = {
|
const $toast = {
|
||||||
show: jest.fn(),
|
show: jest.fn(),
|
||||||
};
|
};
|
||||||
|
jest.mock('~/flash');
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
@ -123,12 +124,12 @@ describe('AppComponent', () => {
|
||||||
mock.onGet('/dashboard/groups.json').reply(400);
|
mock.onGet('/dashboard/groups.json').reply(400);
|
||||||
|
|
||||||
jest.spyOn(window, 'scrollTo').mockImplementation(() => {});
|
jest.spyOn(window, 'scrollTo').mockImplementation(() => {});
|
||||||
jest.spyOn(window, 'Flash').mockImplementation(() => {});
|
|
||||||
|
|
||||||
return vm.fetchGroups({}).then(() => {
|
return vm.fetchGroups({}).then(() => {
|
||||||
expect(vm.isLoading).toBe(false);
|
expect(vm.isLoading).toBe(false);
|
||||||
expect(window.scrollTo).toHaveBeenCalledWith({ behavior: 'smooth', top: 0 });
|
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.';
|
const message = 'An error occurred. Please try again.';
|
||||||
jest.spyOn(vm.service, 'leaveGroup').mockRejectedValue({ status: 500 });
|
jest.spyOn(vm.service, 'leaveGroup').mockRejectedValue({ status: 500 });
|
||||||
jest.spyOn(vm.store, 'removeGroup');
|
jest.spyOn(vm.store, 'removeGroup');
|
||||||
jest.spyOn(window, 'Flash').mockImplementation(() => {});
|
|
||||||
|
|
||||||
vm.leaveGroup();
|
vm.leaveGroup();
|
||||||
|
|
||||||
expect(vm.targetGroup.isBeingRemoved).toBe(true);
|
expect(vm.targetGroup.isBeingRemoved).toBe(true);
|
||||||
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
|
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
|
||||||
return waitForPromises().then(() => {
|
return waitForPromises().then(() => {
|
||||||
expect(vm.store.removeGroup).not.toHaveBeenCalled();
|
expect(vm.store.removeGroup).not.toHaveBeenCalled();
|
||||||
expect(window.Flash).toHaveBeenCalledWith(message);
|
expect(createFlash).toHaveBeenCalledWith({ message });
|
||||||
expect(vm.targetGroup.isBeingRemoved).toBe(false);
|
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.';
|
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.service, 'leaveGroup').mockRejectedValue({ status: 403 });
|
||||||
jest.spyOn(vm.store, 'removeGroup');
|
jest.spyOn(vm.store, 'removeGroup');
|
||||||
jest.spyOn(window, 'Flash').mockImplementation(() => {});
|
|
||||||
|
|
||||||
vm.leaveGroup(childGroupItem, groupItem);
|
vm.leaveGroup(childGroupItem, groupItem);
|
||||||
|
|
||||||
expect(vm.targetGroup.isBeingRemoved).toBe(true);
|
expect(vm.targetGroup.isBeingRemoved).toBe(true);
|
||||||
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
|
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
|
||||||
return waitForPromises().then(() => {
|
return waitForPromises().then(() => {
|
||||||
expect(vm.store.removeGroup).not.toHaveBeenCalled();
|
expect(vm.store.removeGroup).not.toHaveBeenCalled();
|
||||||
expect(window.Flash).toHaveBeenCalledWith(message);
|
expect(createFlash).toHaveBeenCalledWith({ message });
|
||||||
expect(vm.targetGroup.isBeingRemoved).toBe(false);
|
expect(vm.targetGroup.isBeingRemoved).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -190,7 +190,9 @@ describe('UploadBlobModal', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates a flash error', () => {
|
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(() => {
|
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
|
context 'when there is a queuing entry already present' do
|
||||||
before do
|
before do
|
||||||
::Ci::PendingBuild.create!(build: build, project: build.project)
|
create(:ci_pending_build, build: build, project: build.project)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not raise an error' do
|
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
|
context 'when both failure scenario happen at the same time' do
|
||||||
before do
|
before do
|
||||||
::Ci::Build.find(build.id).update_column(:lock_version, 100)
|
::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
|
end
|
||||||
|
|
||||||
it 'raises stale object error exception' do
|
it 'raises stale object error exception' do
|
||||||
|
@ -478,7 +478,7 @@ RSpec.describe Ci::Build do
|
||||||
let(:build) { create(:ci_build, :pending) }
|
let(:build) { create(:ci_build, :pending) }
|
||||||
|
|
||||||
before do
|
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)
|
::Ci::Build.find(build.id).update_column(:lock_version, 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ RSpec.describe Ci::PendingBuild do
|
||||||
|
|
||||||
context 'when another queuing entry exists for given build' do
|
context 'when another queuing entry exists for given build' do
|
||||||
before do
|
before do
|
||||||
described_class.create!(build: build, project: project, protected: false)
|
create(:ci_pending_build, build: build, project: project)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns a build id as a result' do
|
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
|
context 'when another queuing entry exists for given build' do
|
||||||
before do
|
before do
|
||||||
described_class.create!(build: build,
|
create(:ci_running_build, build: build, project: project, runner: runner)
|
||||||
project: project,
|
|
||||||
runner: runner,
|
|
||||||
runner_type: runner.runner_type)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns a build id as a result' do
|
it 'returns a build id as a result' do
|
||||||
|
|
|
@ -45,7 +45,7 @@ RSpec.describe Ci::UpdateBuildQueueService do
|
||||||
|
|
||||||
context 'when duplicate entry exists' do
|
context 'when duplicate entry exists' do
|
||||||
before do
|
before do
|
||||||
::Ci::PendingBuild.create!(build: build, project: project)
|
create(:ci_pending_build, build: build, project: build.project)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does nothing and returns build id' do
|
it 'does nothing and returns build id' do
|
||||||
|
@ -66,7 +66,7 @@ RSpec.describe Ci::UpdateBuildQueueService do
|
||||||
|
|
||||||
context 'when pending build exists' do
|
context 'when pending build exists' do
|
||||||
before do
|
before do
|
||||||
Ci::PendingBuild.create!(build: build, project: project)
|
create(:ci_pending_build, build: build, project: build.project)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes pending build in a transaction' do
|
it 'removes pending build in a transaction' do
|
||||||
|
@ -146,9 +146,7 @@ RSpec.describe Ci::UpdateBuildQueueService do
|
||||||
|
|
||||||
context 'when duplicate entry exists' do
|
context 'when duplicate entry exists' do
|
||||||
before do
|
before do
|
||||||
::Ci::RunningBuild.create!(
|
create(:ci_running_build, build: build, project: project, runner: runner)
|
||||||
build: build, project: project, runner: runner, runner_type: runner.runner_type
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does nothing and returns build id' do
|
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
|
context 'when shared runner build tracking entry exists' do
|
||||||
before do
|
before do
|
||||||
Ci::RunningBuild.create!(
|
create(:ci_running_build, build: build, project: project, runner: runner)
|
||||||
build: build, project: project, runner: runner, runner_type: runner.runner_type
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes shared runner build' do
|
it 'removes shared runner build' do
|
||||||
|
|
Loading…
Reference in New Issue