Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d176911166
commit
91aa68c3ac
|
|
@ -6,7 +6,7 @@ workflow:
|
|||
|
||||
include:
|
||||
- local: .gitlab/ci/version.yml
|
||||
- component: "gitlab.com/gitlab-org/quality/pipeline-common/allure-report@9.9.0"
|
||||
- component: "gitlab.com/gitlab-org/quality/pipeline-common/allure-report@9.10.0"
|
||||
inputs:
|
||||
job_name: "e2e-test-report"
|
||||
job_stage: "report"
|
||||
|
|
@ -16,7 +16,7 @@ include:
|
|||
gitlab_auth_token_variable_name: "PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE"
|
||||
allure_job_name: "${QA_RUN_TYPE}"
|
||||
- project: gitlab-org/quality/pipeline-common
|
||||
ref: 9.9.0
|
||||
ref: 9.10.0
|
||||
file:
|
||||
- /ci/base.gitlab-ci.yml
|
||||
- /ci/knapsack-report.yml
|
||||
|
|
|
|||
|
|
@ -18,4 +18,4 @@ variables:
|
|||
# Retry failed specs in separate process
|
||||
QA_RETRY_FAILED_SPECS: "true"
|
||||
# helm chart ref used by test-on-cng pipeline
|
||||
GITLAB_HELM_CHART_REF: "35cdd0031eee7d33b6fdfbb35911fd8c3eb96b32"
|
||||
GITLAB_HELM_CHART_REF: "56110ba29f58e91a94b119dd8911d241edb89d9a"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import DurationBadge from './duration_badge.vue';
|
|||
import LineNumber from './line_number.vue';
|
||||
|
||||
export default {
|
||||
name: 'LineHeader',
|
||||
components: {
|
||||
GlIcon,
|
||||
LineNumber,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'LineNumber',
|
||||
functional: true,
|
||||
props: {
|
||||
lineNumber: {
|
||||
|
|
|
|||
|
|
@ -30,15 +30,19 @@ export default {
|
|||
if (window.location.hash) {
|
||||
const lineNumber = getLocationHash();
|
||||
|
||||
this.unwatchJobLog = this.$watch('jobLog', async () => {
|
||||
if (this.jobLog.length) {
|
||||
await this.$nextTick();
|
||||
this.unwatchJobLog = this.$watch(
|
||||
'jobLog',
|
||||
async () => {
|
||||
if (this.jobLog.length) {
|
||||
await this.$nextTick();
|
||||
|
||||
const el = document.getElementById(lineNumber);
|
||||
scrollToElement(el);
|
||||
this.unwatchJobLog();
|
||||
}
|
||||
});
|
||||
const el = document.getElementById(lineNumber);
|
||||
scrollToElement(el);
|
||||
this.unwatchJobLog();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
}
|
||||
|
||||
this.setupFullScreenListeners();
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ export default {
|
|||
canCreateSnippet: false,
|
||||
isDeleteModalVisible: false,
|
||||
isDropdownShown: false,
|
||||
embedDropdown: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
dropdownOpen: false,
|
||||
showCreateWorkItemModal: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -93,15 +94,22 @@ export default {
|
|||
trigger-source="top_nav"
|
||||
:trigger-element="$options.TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN"
|
||||
/>
|
||||
<create-work-item-modal
|
||||
<gl-disclosure-dropdown-item
|
||||
v-else-if="isCreateWorkItem(groupItem)"
|
||||
:key="`${groupItem.text}-modal-trigger`"
|
||||
as-dropdown-item
|
||||
is-group
|
||||
:work-item-type-name="$options.WORK_ITEM_TYPE_ENUM_EPIC"
|
||||
:item="groupItem"
|
||||
@action="showCreateWorkItemModal = true"
|
||||
/>
|
||||
<gl-disclosure-dropdown-item v-else :key="groupItem.text" :item="groupItem" />
|
||||
</template>
|
||||
</gl-disclosure-dropdown-group>
|
||||
<create-work-item-modal
|
||||
v-if="showCreateWorkItemModal"
|
||||
visible
|
||||
hide-button
|
||||
is-group
|
||||
:work-item-type-name="$options.WORK_ITEM_TYPE_ENUM_EPIC"
|
||||
@hideModal="showCreateWorkItemModal = false"
|
||||
/>
|
||||
</gl-disclosure-dropdown>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ export default {
|
|||
class="gl-flex gl-flex-wrap gl-items-center gl-gap-2"
|
||||
data-testid="approvals-summary-content"
|
||||
>
|
||||
<span class="gl-font-bold">{{ approvalLeftMessage }}</span>
|
||||
<span v-if="approvalLeftMessage" class="gl-font-bold">{{ approvalLeftMessage }}</span>
|
||||
<template v-if="hasApprovers">
|
||||
<span v-if="approvalLeftMessage">{{ message }}</span>
|
||||
<span v-else class="gl-font-bold">{{ message }}</span>
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class Issue < ApplicationRecord
|
|||
validates :work_item_type, presence: true
|
||||
validates :confidential, inclusion: { in: [true, false], message: 'must be a boolean' }
|
||||
|
||||
validate :allowed_work_item_type_change, on: :update, if: :work_item_type_id_changed?
|
||||
validate :allowed_work_item_type_change, on: :update, if: :correct_work_item_type_id_changed?
|
||||
validate :due_date_after_start_date, if: :validate_due_date?
|
||||
validate :parent_link_confidentiality
|
||||
|
||||
|
|
@ -236,10 +236,10 @@ class Issue < ApplicationRecord
|
|||
|
||||
scope :service_desk, -> {
|
||||
where(
|
||||
"(author_id = ? AND work_item_type_id = ?) OR work_item_type_id = ?",
|
||||
"(author_id = ? AND correct_work_item_type_id = ?) OR correct_work_item_type_id = ?",
|
||||
Users::Internal.support_bot.id,
|
||||
WorkItems::Type.default_issue_type.id,
|
||||
WorkItems::Type.default_by_type(:ticket).id
|
||||
WorkItems::Type.default_issue_type.correct_id,
|
||||
WorkItems::Type.default_by_type(:ticket).correct_id
|
||||
)
|
||||
}
|
||||
scope :inc_relations_for_view, -> do
|
||||
|
|
@ -358,6 +358,11 @@ class Issue < ApplicationRecord
|
|||
correct_work_item_type
|
||||
end
|
||||
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/499911
|
||||
def work_item_type_id
|
||||
correct_work_item_type&.id
|
||||
end
|
||||
|
||||
def work_item_type_id=(input_work_item_type_id)
|
||||
work_item_type = WorkItems::Type.find_by_correct_id_with_fallback(input_work_item_type_id)
|
||||
|
||||
|
|
@ -917,15 +922,19 @@ class Issue < ApplicationRecord
|
|||
end
|
||||
|
||||
def ensure_work_item_type
|
||||
return if work_item_type.present? || work_item_type_id.present? || work_item_type_id_change&.last.present?
|
||||
return if work_item_type.present? ||
|
||||
correct_work_item_type_id.present? ||
|
||||
correct_work_item_type_id_change&.last.present?
|
||||
|
||||
self.work_item_type = WorkItems::Type.default_by_type(DEFAULT_ISSUE_TYPE)
|
||||
end
|
||||
|
||||
def allowed_work_item_type_change
|
||||
return unless changes[:work_item_type_id]
|
||||
return unless changes[:correct_work_item_type_id]
|
||||
|
||||
involved_types = WorkItems::Type.where(id: changes[:work_item_type_id].compact).pluck(:base_type).uniq
|
||||
involved_types = WorkItems::Type.where(
|
||||
correct_id: changes[:correct_work_item_type_id].compact
|
||||
).pluck(:base_type).uniq
|
||||
disallowed_types = involved_types - WorkItems::Type::CHANGEABLE_BASE_TYPES
|
||||
|
||||
return if disallowed_types.empty?
|
||||
|
|
|
|||
|
|
@ -235,17 +235,13 @@ class WorkItem < Issue
|
|||
end
|
||||
|
||||
def max_depth_reached?(child_type)
|
||||
# Using the association here temporarily. We should use the `work_item_type_id` column value after the cleanup
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/499911
|
||||
restriction = ::WorkItems::HierarchyRestriction.find_by_parent_type_id_and_child_type_id(
|
||||
work_item_type.id,
|
||||
work_item_type_id,
|
||||
child_type.id
|
||||
)
|
||||
return false unless restriction&.maximum_depth
|
||||
|
||||
# Using the association here temporarily. We should use the `work_item_type_id` column value after the cleanup
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/499911
|
||||
if work_item_type.id == child_type.id
|
||||
if work_item_type_id == child_type.id
|
||||
same_type_base_and_ancestors.count >= restriction.maximum_depth
|
||||
else
|
||||
hierarchy(different_type_id: child_type.id).base_and_ancestors.count >= restriction.maximum_depth
|
||||
|
|
@ -282,7 +278,7 @@ class WorkItem < Issue
|
|||
|
||||
override :allowed_work_item_type_change
|
||||
def allowed_work_item_type_change
|
||||
return unless work_item_type_id_changed?
|
||||
return unless correct_work_item_type_id_changed?
|
||||
|
||||
child_links = WorkItems::ParentLink.for_parents(id)
|
||||
parent_link = ::WorkItems::ParentLink.find_by(work_item: self)
|
||||
|
|
@ -337,7 +333,7 @@ class WorkItem < Issue
|
|||
return unless restriction&.maximum_depth
|
||||
|
||||
children_with_new_type = self.class.where(id: child_links.select(:work_item_id))
|
||||
.where(work_item_type_id: work_item_type_id)
|
||||
.where(correct_work_item_type_id: correct_work_item_type_id)
|
||||
max_child_depth = ::Gitlab::WorkItems::WorkItemHierarchy.new(children_with_new_type).max_descendants_depth.to_i
|
||||
|
||||
ancestor_depth =
|
||||
|
|
|
|||
|
|
@ -179,13 +179,15 @@ module Issues
|
|||
end
|
||||
|
||||
def handle_issue_type_change(issue)
|
||||
return unless issue.previous_changes.include?('work_item_type_id')
|
||||
return unless issue.previous_changes.include?('correct_work_item_type_id')
|
||||
|
||||
do_handle_issue_type_change(issue)
|
||||
end
|
||||
|
||||
def do_handle_issue_type_change(issue)
|
||||
old_work_item_type = ::WorkItems::Type.find(issue.work_item_type_id_before_last_save).base_type
|
||||
old_work_item_type = ::WorkItems::Type.find_by_correct_id(
|
||||
issue.correct_work_item_type_id_before_last_save
|
||||
).base_type
|
||||
SystemNoteService.change_issue_type(issue, current_user, old_work_item_type)
|
||||
|
||||
::IncidentManagement::IssuableEscalationStatuses::CreateService.new(issue).execute if issue.supports_escalation?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveIssuesWorkItemTypeIdFk < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
milestone '17.8'
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists :issues, column: :work_item_type_id
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key :issues,
|
||||
:work_item_types,
|
||||
column: :work_item_type_id,
|
||||
target_column: :id,
|
||||
on_delete: nil
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FixWorkItemTypesIdColumnValues < Gitlab::Database::Migration[2.2]
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
milestone '17.8'
|
||||
|
||||
def up
|
||||
connection.execute(
|
||||
<<~SQL
|
||||
UPDATE work_item_types SET id = correct_id;
|
||||
SQL
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
# For newer instances, old_id might be null as those instances did not go through the id cleanup process.
|
||||
# These instances were created with all the records in the work_item_types table already in the correct state.
|
||||
# So, running the migration would leave the records in the same state that they already had.
|
||||
# correct_id was already eaqual to id in every record.
|
||||
connection.execute(
|
||||
<<~SQL
|
||||
UPDATE work_item_types SET id = old_id WHERE old_id IS NOT NULL;
|
||||
SQL
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
b1eb892643ab9436c79a5bdf6afa066d0f64dc7eba28f4dafe4e7d2c496c568c
|
||||
|
|
@ -0,0 +1 @@
|
|||
9d90f065475e1710750b265edc8fc106f47ec28335e6923355f0226eb4892e60
|
||||
|
|
@ -37474,9 +37474,6 @@ ALTER TABLE ONLY member_approvals
|
|||
ALTER TABLE ONLY related_epic_links
|
||||
ADD CONSTRAINT fk_b30520b698 FOREIGN KEY (issue_link_id) REFERENCES issue_links(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY issues
|
||||
ADD CONSTRAINT fk_b37be69be6 FOREIGN KEY (work_item_type_id) REFERENCES work_item_types(id);
|
||||
|
||||
ALTER TABLE ONLY duo_workflows_checkpoints
|
||||
ADD CONSTRAINT fk_b3d9cea509 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -827,7 +827,7 @@ However, you should avoid putting too many links on any page. Too many links can
|
|||
To link to another documentation (`.md`) file in the same repository:
|
||||
|
||||
- Use an inline link with a relative file path. For example, `[GitLab.com settings](../user/gitlab_com/index.md)`.
|
||||
- Put the entire link on a single line, even if the link is very long. ([Vale](../testing/vale.md) rule: [`SubstitutionWarning.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab_base/MultiLineLinks.yml)).
|
||||
- Put the entire link on a single line, even if the link is very long. ([Vale](../testing/vale.md) rule: [`MultiLineLinks.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab_base/MultiLineLinks.yml)).
|
||||
|
||||
To link to a file outside of the documentation files, for example to link from development
|
||||
documentation to a specific code file, you can:
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ If you are on a self-managed GitLab instance, create a personal access token.
|
|||
|
||||
After you configure the plugin in your IDE, connect it to your GitLab account:
|
||||
|
||||
1. Go to your IDE's top menu bar and select **Settings**.
|
||||
1. In your IDE, on the top bar, select your IDE's name, then select **Settings**.
|
||||
1. On the left sidebar, expand **Tools**, then select **GitLab Duo**.
|
||||
1. Select an authentication method:
|
||||
- For GitLab.com, use `OAuth`.
|
||||
|
|
|
|||
|
|
@ -25,39 +25,20 @@ More logs are available in the **GitLab Extension Output** window:
|
|||
1. Verify that the debug log contains similar output:
|
||||
|
||||
```shell
|
||||
14:48:21:344 GitlabProposalSource.GetCodeSuggestionAsync
|
||||
14:48:21:344 LsClient.SendTextDocumentCompletionAsync("GitLab.Extension.Test\TestData.cs", 34, 0)
|
||||
14:48:21:346 LS(55096): time="2023-07-17T14:48:21-05:00" level=info msg="update context"
|
||||
GetProposalManagerAsync: Code suggestions enabled. ContentType (csharp) or file extension (cs) is supported.
|
||||
GitlabProposalSourceProvider.GetProposalSourceAsync
|
||||
```
|
||||
|
||||
## Extension not loaded on startup
|
||||
### View activity log
|
||||
|
||||
After restarting, the following error is displayed:
|
||||
If your extension does not load or crashes, check the activity log for errors.
|
||||
Your activity log is available in this location:
|
||||
|
||||
```plaintext
|
||||
SetSite failed for package [VisualStudioPackage]Source: 'Microsoft.VisualStudio.Composition' Description: Expected 1 export(s) with contract name "Microsoft.VisualStudio.Language.Suggestions.SuggestionServiceBase" but found 0 after applying applicable constraints.
|
||||
Microsoft.VisualStudio.Composition.CompositionFailedException: Expected 1 export(s) with contract name "Microsoft.VisualStudio.Language.Suggestions.SuggestionServiceBase" but found 0 after applying applicable constraints.
|
||||
at Microsoft.VisualStudio.Composition.ExportProvider.GetExports(ImportDefinition importDefinition)
|
||||
at Microsoft.VisualStudio.Composition.ExportProvider.GetExports[T,TMetadataView](String contractName, ImportCardinality cardinality)
|
||||
at Microsoft.VisualStudio.Composition.ExportProvider.GetExport[T,TMetadataView](String contractName)
|
||||
at Microsoft.VisualStudio.Composition.ExportProvider.GetExportedValue[T]()
|
||||
at Microsoft.VisualStudio.ComponentModelHost.ComponentModel.GetService[T]()
|
||||
[...]
|
||||
C:\Users\WINDOWS_USERNAME\AppData\Roaming\Microsoft\VisualStudio\VS_VERSION\ActivityLog.xml
|
||||
```
|
||||
|
||||
To fix this issue, install the IntelliCode component for Visual Studio.
|
||||
Replace these values in the directory path:
|
||||
|
||||
## Error: unable to find last release
|
||||
|
||||
If you receive this error message, your commits are likely on the main branch of
|
||||
your fork, instead of a feature branch:
|
||||
|
||||
```plaintext
|
||||
buildtag.sh: Error: unable to find last release.
|
||||
```
|
||||
|
||||
To resolve this issue:
|
||||
|
||||
1. Create a separate feature branch for your changes.
|
||||
1. Cherry-pick your commits into your feature branch.
|
||||
1. Retry your command.
|
||||
- `WINDOWS_USERNAME`: Your Windows username.
|
||||
- `VS_VERSION`: The version of your Visual Studio installation.
|
||||
|
|
|
|||
|
|
@ -312,6 +312,11 @@ Your subscription cost is based on the maximum number of seats you use during th
|
|||
- If restricted access is turned off, when there are no seats left in your subscription groups can continue to add billable
|
||||
users. GitLab [bills you for the overage](../quarterly_reconciliation.md).
|
||||
|
||||
You cannot add seats to your subscription if either:
|
||||
|
||||
- You purchased your subscription through an [authorized reseller](../customers_portal.md#customers-that-purchased-through-a-reseller) (including GCP and AWS marketplaces). Contact the reseller to add more seats.
|
||||
- You have a multi-year subscription. Contact the [sales team](https://about.gitlab.com/sales/) to add more seats.
|
||||
|
||||
To add seats to a subscription:
|
||||
|
||||
1. Sign in to the [Customers Portal](https://customers.gitlab.com/).
|
||||
|
|
@ -325,8 +330,6 @@ To add seats to a subscription:
|
|||
|
||||
You receive the payment receipt by email. You can also access the receipt in the Customers Portal under [**Invoices**](https://customers.gitlab.com/invoices).
|
||||
|
||||
For multi-year subscriptions, the **Add seats** option is unavailable. To purchase additional seats, please contact the [sales team](https://about.gitlab.com/sales/).
|
||||
|
||||
## Remove users from subscription
|
||||
|
||||
To remove a billable user from your GitLab.com subscription:
|
||||
|
|
|
|||
|
|
@ -204,6 +204,11 @@ period is prorated from the date of purchase through to the end of the subscript
|
|||
period. You can continue to add users even if you reach the number of users in
|
||||
license count. GitLab [bills you for the overage](../quarterly_reconciliation.md).
|
||||
|
||||
You cannot add seats to your subscription if either:
|
||||
|
||||
- You purchased your subscription through an [authorized reseller](../customers_portal.md#customers-that-purchased-through-a-reseller) (including GCP and AWS marketplaces). Contact the reseller to add more seats.
|
||||
- You have a multi-year subscription. Contact the [sales team](https://about.gitlab.com/sales/) to add more seats.
|
||||
|
||||
To add seats to a subscription:
|
||||
|
||||
1. Sign in to the [Customers Portal](https://customers.gitlab.com/).
|
||||
|
|
@ -223,10 +228,6 @@ your instance immediately. If you're using a license file, you receive an update
|
|||
To add the seats, [add the license file](../../administration/license_file.md)
|
||||
to your instance.
|
||||
|
||||
If you purchased your subscription through an [authorized reseller](../customers_portal.md#customers-that-purchased-through-a-reseller) (including GCP and AWS marketplaces), contact the reseller to add more seats.
|
||||
|
||||
For multi-year subscriptions, the **Add seats** option is unavailable. To purchase additional seats, please contact the [sales team](https://about.gitlab.com/sales/).
|
||||
|
||||
## Subscription data synchronization
|
||||
|
||||
Prerequisites:
|
||||
|
|
|
|||
|
|
@ -169,7 +169,38 @@ GitLab Language Server process are invalid. To re-enable Code Suggestions:
|
|||
The following documentation is for Code Suggestions-specific troubleshooting for
|
||||
Microsoft Visual Studio.
|
||||
|
||||
For non-Code Suggestions troubleshooting for Microsoft Visual Studio, see [Visual Studio troubleshooting](../../../../editor_extensions/visual_studio/visual_studio_troubleshooting.md).
|
||||
For non-Code Suggestions troubleshooting for Microsoft Visual Studio, see
|
||||
[Visual Studio troubleshooting](../../../../editor_extensions/visual_studio/visual_studio_troubleshooting.md).
|
||||
|
||||
### IntelliCode is missing
|
||||
|
||||
Code Suggestions requires the **IntelliCode** component of Visual Studio. If the component
|
||||
is missing, you might see an error like this when you start Visual Studio:
|
||||
|
||||
```plaintext
|
||||
SetSite failed for package [VisualStudioPackage]Source: 'Microsoft.VisualStudio.Composition'
|
||||
Description: Expected 1 export(s) with contract name "Microsoft.VisualStudio.Language.Suggestions.SuggestionServiceBase"
|
||||
but found 0 after applying applicable constraints.
|
||||
|
||||
Microsoft.VisualStudio.Composition.CompositionFailedException:
|
||||
Expected 1 export(s) with contract name "Microsoft.VisualStudio.Language.Suggestions.SuggestionServiceBase"
|
||||
but found 0 after applying applicable constraints.
|
||||
|
||||
at Microsoft.VisualStudio.Composition.ExportProvider.GetExports(ImportDefinition importDefinition)
|
||||
at Microsoft.VisualStudio.Composition.ExportProvider.GetExports[T,TMetadataView](String contractName, ImportCardinality cardinality)
|
||||
at Microsoft.VisualStudio.Composition.ExportProvider.GetExport[T,TMetadataView](String contractName)
|
||||
at Microsoft.VisualStudio.Composition.ExportProvider.GetExportedValue[T]()
|
||||
at Microsoft.VisualStudio.ComponentModelHost.ComponentModel.GetService[T]()
|
||||
[...]
|
||||
```
|
||||
|
||||
To fix this problem, install the **IntelliCode** component:
|
||||
|
||||
1. In the Windows start menu, search for the **Visual Studio Installer** and open it.
|
||||
1. Select your Visual Studio instance, then select **Modify**.
|
||||
1. In the **Individual components** tab, search for **IntelliCode**.
|
||||
1. Select the component's checkbox, then on the bottom right, select **Modify**.
|
||||
1. Wait for the Visual Studio Installer to finish the installation.
|
||||
|
||||
### Suggestions not displayed in Microsoft Visual Studio
|
||||
|
||||
|
|
|
|||
|
|
@ -50618,6 +50618,9 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|Add regular branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Additional configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|After dismissing the alert, the information will never be shown again."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -50651,6 +50654,9 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|Allow except on these packages"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Allow users to skip pipelines"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Allowed licenses"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -50741,6 +50747,9 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|Configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Configure policies to allow individual users or service accounts to use %{linkStart}skip_ci%{linkEnd} to skip pipelines."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Configure your conditions in the pipeline execution file. %{linkStart}What can pipeline execution do?%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -51538,6 +51547,9 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|default"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|except for:"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|except groups"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
"@gitlab/duo-ui": "^6.0.0",
|
||||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/fonts": "^1.3.0",
|
||||
"@gitlab/query-language-rust": "0.3.0",
|
||||
"@gitlab/query-language-rust": "0.3.1",
|
||||
"@gitlab/svgs": "3.121.0",
|
||||
"@gitlab/ui": "106.0.0",
|
||||
"@gitlab/vue-router-vue3": "npm:vue-router@4.1.6",
|
||||
|
|
|
|||
|
|
@ -8,9 +8,14 @@ module QA
|
|||
|
||||
GITLAB_PROJECT_ID = 278964
|
||||
UPDATE_BRANCH_NAME = "qa-knapsack-master-report-update"
|
||||
DEFAULT_WAIT_BEFORE_MERGE = 120
|
||||
|
||||
def self.run
|
||||
new.update_master_report
|
||||
def self.run(wait_before_merge: DEFAULT_WAIT_BEFORE_MERGE)
|
||||
new(wait_before_merge: wait_before_merge).update_master_report
|
||||
end
|
||||
|
||||
def initialize(wait_before_merge: DEFAULT_WAIT_BEFORE_MERGE)
|
||||
@wait_before_merge = wait_before_merge
|
||||
end
|
||||
|
||||
# Create master_report.json merge request
|
||||
|
|
@ -20,10 +25,17 @@ module QA
|
|||
create_branch
|
||||
create_commit
|
||||
create_mr
|
||||
return unless auto_merge?
|
||||
|
||||
logger.info("Performing auto merge")
|
||||
approve_mr
|
||||
add_mr_to_merge_train
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :wait_before_merge, :mr_iid
|
||||
|
||||
# Knapsack report generator
|
||||
#
|
||||
# @return [QA::Support::KnapsackReport]
|
||||
|
|
@ -31,13 +43,6 @@ module QA
|
|||
@knapsack_reporter = Support::KnapsackReport.new(logger)
|
||||
end
|
||||
|
||||
# Gitlab access token
|
||||
#
|
||||
# @return [String]
|
||||
def gitlab_access_token
|
||||
@gitlab_access_token ||= ENV["GITLAB_ACCESS_TOKEN"] || raise("Missing GITLAB_ACCESS_TOKEN env variable")
|
||||
end
|
||||
|
||||
# Gitlab api url
|
||||
#
|
||||
# @return [String]
|
||||
|
|
@ -45,13 +50,49 @@ module QA
|
|||
@gitlab_api_url ||= ENV["CI_API_V4_URL"] || raise("Missing CI_API_V4_URL env variable")
|
||||
end
|
||||
|
||||
# Api request headers
|
||||
# Gitlab access token
|
||||
#
|
||||
# @return [String]
|
||||
def gitlab_access_token
|
||||
@gitlab_access_token ||= ENV["GITLAB_ACCESS_TOKEN"] || raise("Missing GITLAB_ACCESS_TOKEN env variable")
|
||||
end
|
||||
|
||||
# Knapsack report approver token
|
||||
#
|
||||
# @return [String]
|
||||
def approver_access_token
|
||||
@approver_access_token ||= ENV["QA_KNAPSACK_REPORT_APPROVER_TOKEN"].tap do |token|
|
||||
logger.warn("QA_KNAPSACK_REPORT_APPROVER_TOKEN is not set") unless token
|
||||
end
|
||||
end
|
||||
|
||||
# Update mr approver user id
|
||||
#
|
||||
# @return [Integer]
|
||||
def approver_user_id
|
||||
@approver_user_id ||= approver_access_token.then do |token|
|
||||
next 0 unless token
|
||||
|
||||
resp = get("#{gitlab_api_url}/user", token_header(token))
|
||||
next parse_body(resp)[:id] if success?(resp.code)
|
||||
|
||||
logger.error("Failed to fetch approver user id! Response: #{resp.body}")
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
# Valid approver user is set
|
||||
#
|
||||
# @return [Boolean]
|
||||
def approver_user_valid?
|
||||
approver_user_id != 0
|
||||
end
|
||||
|
||||
# Api request private token header
|
||||
#
|
||||
# @return [Hash]
|
||||
def api_headers
|
||||
@api_headers ||= {
|
||||
headers: { "PRIVATE-TOKEN" => gitlab_access_token }
|
||||
}
|
||||
def token_header(token = gitlab_access_token)
|
||||
{ headers: { "PRIVATE-TOKEN" => token } }
|
||||
end
|
||||
|
||||
# Create branch for knapsack report update
|
||||
|
|
@ -59,16 +100,24 @@ module QA
|
|||
# @return [void]
|
||||
def create_branch
|
||||
logger.info("Creating branch '#{UPDATE_BRANCH_NAME}' branch")
|
||||
api_request(:post, "repository/branches", {
|
||||
branch: UPDATE_BRANCH_NAME,
|
||||
ref: "master"
|
||||
})
|
||||
rescue StandardError => e
|
||||
raise e unless e.message.include?("Branch already exists")
|
||||
retry_attempts = 0
|
||||
|
||||
logger.warn("Branch '#{UPDATE_BRANCH_NAME}' already exists, recreating it.")
|
||||
api_request(:delete, "repository/branches/#{UPDATE_BRANCH_NAME}")
|
||||
retry
|
||||
begin
|
||||
api_request(:post, "repository/branches", {
|
||||
branch: UPDATE_BRANCH_NAME,
|
||||
ref: "master"
|
||||
})
|
||||
rescue StandardError => e
|
||||
raise e if retry_attempts > 2
|
||||
|
||||
if e.message.include?("Branch already exists")
|
||||
logger.warn("Branch '#{UPDATE_BRANCH_NAME}' already exists, recreating it.")
|
||||
api_request(:delete, "repository/branches/#{UPDATE_BRANCH_NAME}")
|
||||
end
|
||||
|
||||
retry_attempts += 1
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
# Create update commit for knapsack report
|
||||
|
|
@ -104,28 +153,69 @@ module QA
|
|||
resp = api_request(:post, "merge_requests", {
|
||||
source_branch: UPDATE_BRANCH_NAME,
|
||||
target_branch: "master",
|
||||
title: "Update master_report.json for E2E tests",
|
||||
title: "Update knapsack runtime data for E2E tests",
|
||||
remove_source_branch: true,
|
||||
squash: true,
|
||||
labels: "Quality,team::Test and Tools Infrastructure,type::maintenance,maintenance::pipelines",
|
||||
description: <<~DESCRIPTION
|
||||
Update fallback knapsack report with latest spec runtime data.
|
||||
reviewer_ids: approver_user_valid? ? [approver_user_id] : nil,
|
||||
labels: "group::development analytics,type::maintenance,maintenance::pipelines",
|
||||
description: "Update fallback knapsack report and example runtime data report.".then do |description|
|
||||
next description if approver_user_valid?
|
||||
|
||||
@gl-dx/qe-maintainers please review and merge.
|
||||
DESCRIPTION
|
||||
})
|
||||
"#{description}\n\ncc: @gl-dx/qe-maintainers"
|
||||
end
|
||||
}.compact)
|
||||
@mr_iid = resp[:iid]
|
||||
|
||||
logger.info("Merge request created: #{resp[:web_url]}")
|
||||
end
|
||||
|
||||
# Approve created merge request
|
||||
#
|
||||
# @return [void]
|
||||
def approve_mr
|
||||
logger.info(" approving merge request")
|
||||
api_request(:post, "merge_requests/#{mr_iid}/approve", {}, token_header(approver_access_token))
|
||||
end
|
||||
|
||||
# Add merge request to merge train
|
||||
#
|
||||
# @return [void]
|
||||
def add_mr_to_merge_train
|
||||
logger.info(" adding merge request to merge train")
|
||||
sleep(wait_before_merge) # gitlab-org/gitlab takes a long time to create pipeline after approval
|
||||
retry_attempts = 0
|
||||
approver_header = token_header(approver_access_token)
|
||||
|
||||
begin
|
||||
api_request(:post, "merge_trains/merge_requests/#{mr_iid}", { when_pipeline_succeeds: true }, approver_header)
|
||||
rescue StandardError => e
|
||||
raise e if retry_attempts > 2
|
||||
|
||||
logger.warn(" failed to add merge request to merge train, retrying...")
|
||||
logger.warn(e.message)
|
||||
retry_attempts += 1
|
||||
sleep(10)
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
# Attempt to automatically merge created mr
|
||||
#
|
||||
# @return [Boolean]
|
||||
def auto_merge?
|
||||
(mr_iid && approver_user_valid?).tap do |auto_merge|
|
||||
logger.warn("Auto merge will not be performed!") unless auto_merge
|
||||
end
|
||||
end
|
||||
|
||||
# Api update request
|
||||
#
|
||||
# @param [String] verb
|
||||
# @param [String] path
|
||||
# @param [Hash] payload
|
||||
# @return [Hash, Array]
|
||||
def api_request(verb, path, payload = nil)
|
||||
args = [verb, "#{gitlab_api_url}/projects/#{GITLAB_PROJECT_ID}/#{path}", payload, api_headers].compact
|
||||
def api_request(verb, path, payload = nil, headers = token_header)
|
||||
args = [verb, "#{gitlab_api_url}/projects/#{GITLAB_PROJECT_ID}/#{path}", payload, headers].compact
|
||||
response = public_send(*args)
|
||||
raise "Api request to #{path} failed! Body: #{response.body}" unless success?(response.code)
|
||||
return {} if response.body.empty?
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe QA::Tools::KnapsackReportUpdater do
|
||||
RSpec.describe QA::Tools::KnapsackReportUpdater, :aggregate_failures do
|
||||
include QA::Support::Helpers::StubEnv
|
||||
|
||||
let(:http_response) { instance_double("HTTPResponse", code: 200, body: {}.to_json) }
|
||||
let(:logger) { instance_double("Logger", info: nil, warn: nil) }
|
||||
subject(:report_updater) { described_class.new(wait_before_merge: 0) }
|
||||
|
||||
let(:approver_id) { 1 }
|
||||
let(:mr_iid) { 1 }
|
||||
let(:approver_token) { nil }
|
||||
let(:merged_runtimes) { { "spec_file[1:1]": 0.0 } }
|
||||
let(:merged_report) { { spec_file: 0.0 } }
|
||||
let(:branch) { "qa-knapsack-master-report-update" }
|
||||
|
||||
let(:http_response) { mock_response(200, { id: approver_id, iid: mr_iid }) }
|
||||
let(:logger) { instance_double(Logger, info: nil, warn: nil, error: nil) }
|
||||
let(:knapsack_reporter) do
|
||||
instance_double(
|
||||
QA::Support::KnapsackReport,
|
||||
|
|
@ -17,26 +22,37 @@ RSpec.describe QA::Tools::KnapsackReportUpdater do
|
|||
)
|
||||
end
|
||||
|
||||
def request_args(verb, path, payload)
|
||||
# Instance double for rest client response
|
||||
#
|
||||
# @param code [Integer]
|
||||
# @param body [Hash]
|
||||
# @return [InstanceDouble]
|
||||
def mock_response(code, body)
|
||||
instance_double(RestClient::Response, code: code, body: body.to_json)
|
||||
end
|
||||
|
||||
# Request args passed to rest client
|
||||
#
|
||||
# @param verb [Symbol]
|
||||
# @param path [String]
|
||||
# @param payload [Hash]
|
||||
# @param token [String]
|
||||
# @return [Hash]
|
||||
def request_args(verb, path, payload, token = "token")
|
||||
{
|
||||
method: verb,
|
||||
url: "https://gitlab.com/api/v4/projects/278964/#{path}",
|
||||
payload: payload,
|
||||
verify_ssl: false,
|
||||
headers: { "PRIVATE-TOKEN" => "token" }
|
||||
headers: { "PRIVATE-TOKEN" => token }
|
||||
}.compact
|
||||
end
|
||||
|
||||
before do
|
||||
allow(RestClient::Request).to receive(:execute).and_return(http_response)
|
||||
allow(Gitlab::QA::TestLogger).to receive(:logger).and_return(logger)
|
||||
allow(QA::Support::KnapsackReport).to receive(:new).with(logger).and_return(knapsack_reporter)
|
||||
|
||||
stub_env("CI_API_V4_URL", "https://gitlab.com/api/v4")
|
||||
stub_env("GITLAB_ACCESS_TOKEN", "token")
|
||||
end
|
||||
|
||||
def expect_mr_created
|
||||
# Expect merge request was created
|
||||
#
|
||||
# @param reviewer_ids [Array, nil]
|
||||
# @return [void]
|
||||
def expect_mr_created(reviewer_ids: nil)
|
||||
expect(knapsack_reporter).to have_received(:create_knapsack_report).with(merged_runtimes)
|
||||
expect(RestClient::Request).to have_received(:execute).with(request_args(:post, "repository/commits", {
|
||||
branch: branch,
|
||||
|
|
@ -57,21 +73,49 @@ RSpec.describe QA::Tools::KnapsackReportUpdater do
|
|||
expect(RestClient::Request).to have_received(:execute).with(request_args(:post, "merge_requests", {
|
||||
source_branch: branch,
|
||||
target_branch: "master",
|
||||
title: "Update master_report.json for E2E tests",
|
||||
title: "Update knapsack runtime data for E2E tests",
|
||||
remove_source_branch: true,
|
||||
squash: true,
|
||||
labels: "Quality,team::Test and Tools Infrastructure,type::maintenance,maintenance::pipelines",
|
||||
description: <<~DESCRIPTION
|
||||
Update fallback knapsack report with latest spec runtime data.
|
||||
reviewer_ids: reviewer_ids,
|
||||
labels: "group::development analytics,type::maintenance,maintenance::pipelines",
|
||||
description: "Update fallback knapsack report and example runtime data report.".then do |description|
|
||||
next description unless reviewer_ids.nil?
|
||||
|
||||
@gl-dx/qe-maintainers please review and merge.
|
||||
DESCRIPTION
|
||||
}))
|
||||
"#{description}\n\ncc: @gl-dx/qe-maintainers"
|
||||
end
|
||||
}.compact))
|
||||
end
|
||||
|
||||
before do
|
||||
allow(RestClient::Request).to receive(:execute).and_return(http_response)
|
||||
allow(Gitlab::QA::TestLogger).to receive(:logger).and_return(logger)
|
||||
allow(QA::Support::KnapsackReport).to receive(:new).and_return(knapsack_reporter)
|
||||
allow(QA::Runtime::Env).to receive(:canary_cookie).and_return({})
|
||||
|
||||
stub_env("CI_API_V4_URL", "https://gitlab.com/api/v4")
|
||||
stub_env("GITLAB_ACCESS_TOKEN", "token")
|
||||
stub_env("QA_KNAPSACK_REPORT_APPROVER_TOKEN", approver_token)
|
||||
end
|
||||
|
||||
context "without approver token" do
|
||||
it "does not attempt auto merge" do
|
||||
report_updater.update_master_report
|
||||
|
||||
expect_mr_created
|
||||
expect(RestClient::Request).not_to have_received(:execute).with(hash_including(
|
||||
method: :post,
|
||||
url: "merge_requests/#{mr_iid}/approve"
|
||||
))
|
||||
expect(RestClient::Request).not_to have_received(:execute).with(hash_including(
|
||||
method: :post,
|
||||
url: "merge_trains/merge_requests/#{mr_iid}"
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
context "without existing branch" do
|
||||
it "creates master report merge request", :aggregate_failures do
|
||||
described_class.run
|
||||
it "creates master report merge request" do
|
||||
report_updater.update_master_report
|
||||
|
||||
expect(RestClient::Request).to have_received(:execute).with(request_args(:post, "repository/branches", {
|
||||
branch: branch,
|
||||
|
|
@ -86,13 +130,13 @@ RSpec.describe QA::Tools::KnapsackReportUpdater do
|
|||
allow(RestClient::Request).to receive(:execute)
|
||||
.with(request_args(:post, "repository/branches", { branch: branch, ref: "master" }))
|
||||
.and_return(
|
||||
instance_double("HTTPResponse", code: 403, body: { message: "Branch already exists" }.to_json),
|
||||
http_response
|
||||
mock_response(403, { message: "Branch already exists" }),
|
||||
mock_response(200, { name: branch })
|
||||
)
|
||||
end
|
||||
|
||||
it "recreates branch and creates master report merge request", :aggregate_failures do
|
||||
described_class.run
|
||||
report_updater.update_master_report
|
||||
|
||||
expect(RestClient::Request).to have_received(:execute).with(
|
||||
request_args(:post, "repository/branches", { branch: branch, ref: "master" })
|
||||
|
|
@ -103,4 +147,47 @@ RSpec.describe QA::Tools::KnapsackReportUpdater do
|
|||
expect_mr_created
|
||||
end
|
||||
end
|
||||
|
||||
context "with approver token" do
|
||||
let(:approver_token) { "approver_token" }
|
||||
|
||||
context "with approver id returned" do
|
||||
it "creates merge request and adds it to merge train" do
|
||||
report_updater.update_master_report
|
||||
|
||||
expect_mr_created(reviewer_ids: [approver_id])
|
||||
expect(RestClient::Request).to have_received(:execute).with(
|
||||
request_args(:post, "merge_requests/#{mr_iid}/approve", {}, approver_token)
|
||||
)
|
||||
expect(RestClient::Request).to have_received(:execute).with(
|
||||
request_args(:post, "merge_trains/merge_requests/#{mr_iid}", { when_pipeline_succeeds: true }, approver_token)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "without approver id returned" do
|
||||
before do
|
||||
allow(RestClient::Request).to receive(:execute).with({
|
||||
method: :get,
|
||||
url: "https://gitlab.com/api/v4/user",
|
||||
verify_ssl: false,
|
||||
headers: { "PRIVATE-TOKEN" => approver_token }
|
||||
}).and_return(mock_response(401, { message: "401 Unauthorized" }))
|
||||
end
|
||||
|
||||
it "does not attempt auto merge" do
|
||||
report_updater.update_master_report
|
||||
|
||||
expect_mr_created(reviewer_ids: nil)
|
||||
expect(RestClient::Request).not_to have_received(:execute).with(hash_including(
|
||||
method: :post,
|
||||
url: "merge_requests/#{mr_iid}/approve"
|
||||
))
|
||||
expect(RestClient::Request).not_to have_received(:execute).with(hash_including(
|
||||
method: :post,
|
||||
url: "merge_trains/merge_requests/#{mr_iid}"
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ ee/spec/frontend/members/components/modals/ldap_override_confirmation_modal_spec
|
|||
ee/spec/frontend/members/components/table/drawer/role_details_drawer_spec.js
|
||||
ee/spec/frontend/members/components/table/drawer/role_updater_spec.js
|
||||
ee/spec/frontend/members/components/table/max_role_spec.js
|
||||
ee/spec/frontend/members/components/table/member_action_buttons_spec.js
|
||||
ee/spec/frontend/members/components/table/members_table_spec.js
|
||||
ee/spec/frontend/metrics/details/filter_bar/groupby_filter_spec.js
|
||||
ee/spec/frontend/metrics/details/metrics_details_spec.js
|
||||
|
|
@ -126,18 +125,11 @@ spec/frontend/branches/components/delete_branch_modal_spec.js
|
|||
spec/frontend/cascading_settings/components/cascading_lock_icon_spec.js
|
||||
spec/frontend/cascading_settings/components/lock_tooltip_spec.js
|
||||
spec/frontend/ci/artifacts/components/artifacts_bulk_delete_spec.js
|
||||
spec/frontend/ci/catalog/index_spec.js
|
||||
spec/frontend/ci/job_details/components/log/line_header_spec.js
|
||||
spec/frontend/ci/job_details/components/log/line_spec.js
|
||||
spec/frontend/ci/job_details/components/log/log_spec.js
|
||||
spec/frontend/ci/job_details/components/sidebar/job_sidebar_retry_button_spec.js
|
||||
spec/frontend/ci/pipeline_details/header/components/header_badges_spec.js
|
||||
spec/frontend/ci/pipeline_editor/components/header/validation_segment_spec.js
|
||||
spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item_spec.js
|
||||
spec/frontend/ci/pipeline_editor/components/lint/ci_lint_warnings_spec.js
|
||||
spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js
|
||||
spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js
|
||||
spec/frontend/ci/pipelines_page/components/pipeline_multi_actions_spec.js
|
||||
spec/frontend/ci/pipelines_page/components/pipelines_artifacts_spec.js
|
||||
spec/frontend/ci/runner/components/runner_form_fields_spec.js
|
||||
spec/frontend/ci_settings_pipeline_triggers/components/edit_trigger_modal_spec.js
|
||||
|
|
@ -273,7 +265,6 @@ spec/frontend/sidebar/components/lock/issuable_lock_form_spec.js
|
|||
spec/frontend/sidebar/components/milestone/milestone_dropdown_spec.js
|
||||
spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js
|
||||
spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js
|
||||
spec/frontend/snippets/components/snippet_header_spec.js
|
||||
spec/frontend/super_sidebar/components/nav_item_router_link_spec.js
|
||||
spec/frontend/super_sidebar/components/organization_switcher_spec.js
|
||||
spec/frontend/super_sidebar/components/sidebar_portal_spec.js
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ RSpec.describe 'Database schema',
|
|||
ci_sources_pipelines: [%w[source_partition_id source_pipeline_id], %w[partition_id pipeline_id]],
|
||||
ci_sources_projects: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient
|
||||
ci_stages: [%w[partition_id pipeline_id]], # the index on pipeline_id is sufficient
|
||||
issues: [%w[correct_work_item_type_id]],
|
||||
notes: %w[namespace_id], # this index is added in an async manner, hence it needs to be ignored in the first phase.
|
||||
p_ci_build_trace_metadata: [%w[partition_id build_id], %w[partition_id trace_artifact_id]], # the index on build_id is enough
|
||||
p_ci_builds: [%w[partition_id stage_id], %w[partition_id execution_config_id], %w[auto_canceled_by_partition_id auto_canceled_by_id], %w[upstream_pipeline_partition_id upstream_pipeline_id], %w[partition_id commit_id]], # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142804#note_1745483081
|
||||
|
|
@ -157,7 +156,7 @@ RSpec.describe 'Database schema',
|
|||
gitlab_subscription_histories: %w[gitlab_subscription_id hosted_plan_id namespace_id],
|
||||
identities: %w[user_id],
|
||||
import_failures: %w[project_id],
|
||||
issues: %w[last_edited_by_id state_id correct_work_item_type_id],
|
||||
issues: %w[last_edited_by_id state_id work_item_type_id],
|
||||
issue_emails: %w[email_message_id],
|
||||
jira_tracker_data: %w[jira_issue_transition_id],
|
||||
keys: %w[user_id],
|
||||
|
|
|
|||
|
|
@ -125,5 +125,14 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
after(:build) do |issue, _|
|
||||
next if issue.attributes['work_item_type_id'].blank?
|
||||
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/499911
|
||||
issue.write_attribute(:work_item_type_id, -issue.attributes['work_item_type_id'])
|
||||
# clearing attribute change to avoid update callbacks on initial save
|
||||
issue.clear_work_item_type_id_change
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -85,6 +85,15 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
after(:build) do |work_item, _|
|
||||
next if work_item.attributes['work_item_type_id'].blank?
|
||||
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/499911
|
||||
work_item.write_attribute(:work_item_type_id, -work_item.attributes['work_item_type_id'])
|
||||
# clearing attribute change to avoid update callbacks on initial save
|
||||
work_item.clear_work_item_type_id_change
|
||||
end
|
||||
|
||||
# Service Desk Ticket
|
||||
factory :ticket do
|
||||
association :work_item_type, :ticket
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import Vue from 'vue';
|
||||
import { initCatalog } from '~/ci/catalog/';
|
||||
import * as Router from '~/ci/catalog/router';
|
||||
import CiResourcesPage from '~/ci/catalog/components/pages/ci_resources_page.vue';
|
||||
|
|
@ -30,7 +29,7 @@ describe('~/ci/catalog/index', () => {
|
|||
});
|
||||
|
||||
it('returns a Vue Instance', () => {
|
||||
expect(component).toBeInstanceOf(Vue);
|
||||
expect(component.$options.name).toBe('GlobalCatalog');
|
||||
});
|
||||
|
||||
it('creates a router with the received base path and component', () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { shallowMount, mount } from '@vue/test-utils';
|
||||
import Line from '~/ci/job_details/components/log/line.vue';
|
||||
import LineNumber from '~/ci/job_details/components/log/line_number.vue';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
|
|
@ -24,8 +24,8 @@ describe('Job Log Line', () => {
|
|||
let wrapper;
|
||||
let data;
|
||||
|
||||
const createComponent = (props) => {
|
||||
wrapper = shallowMount(Line, {
|
||||
const createComponent = (props, mountFn = shallowMount) => {
|
||||
wrapper = mountFn(Line, {
|
||||
propsData: {
|
||||
path: '/',
|
||||
...props,
|
||||
|
|
@ -215,14 +215,17 @@ describe('Job Log Line', () => {
|
|||
const time = '00:00:01Z';
|
||||
const text = 'text';
|
||||
|
||||
createComponent({
|
||||
line: {
|
||||
time,
|
||||
content: [{ text }],
|
||||
lineNumber,
|
||||
createComponent(
|
||||
{
|
||||
line: {
|
||||
time,
|
||||
content: [{ text }],
|
||||
lineNumber,
|
||||
},
|
||||
path: '/',
|
||||
},
|
||||
path: '/',
|
||||
});
|
||||
mount,
|
||||
);
|
||||
|
||||
expect(wrapper.text()).toBe(`${lineNumber}${time}${text}`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ jest.mock('~/lib/utils/common_utils', () => ({
|
|||
describe('Job Log', () => {
|
||||
let wrapper;
|
||||
let actions;
|
||||
let state;
|
||||
let initialState;
|
||||
let store;
|
||||
let toggleCollapsibleLineMock;
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const createComponent = (props) => {
|
||||
store = new Vuex.Store({ actions, state });
|
||||
store = new Vuex.Store({ actions, state: initialState });
|
||||
|
||||
wrapper = mount(Log, {
|
||||
provide: {
|
||||
|
|
@ -49,7 +49,7 @@ describe('Job Log', () => {
|
|||
|
||||
const { lines, sections } = logLinesParser(mockJobLog);
|
||||
|
||||
state = {
|
||||
initialState = {
|
||||
jobLog: lines,
|
||||
jobLogSections: sections,
|
||||
};
|
||||
|
|
@ -102,7 +102,8 @@ describe('Job Log', () => {
|
|||
});
|
||||
|
||||
it('hides duration', () => {
|
||||
state.jobLogSections['resolve-secrets'].hideDuration = true;
|
||||
initialState.jobLogSections['resolve-secrets'].hideDuration = true;
|
||||
|
||||
createComponent();
|
||||
|
||||
expect(findLineHeader().props('duration')).toBe('00:00');
|
||||
|
|
@ -112,7 +113,7 @@ describe('Job Log', () => {
|
|||
|
||||
describe('when a section is collapsed', () => {
|
||||
beforeEach(() => {
|
||||
state.jobLogSections['prepare-executor'].isClosed = true;
|
||||
initialState.jobLogSections['prepare-executor'].isClosed = true;
|
||||
|
||||
createComponent();
|
||||
});
|
||||
|
|
@ -153,20 +154,21 @@ describe('Job Log', () => {
|
|||
|
||||
it('scrolls to line number', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
state.jobLog = logLinesParser(mockJobLog, [], '#L6').lines;
|
||||
wrapper.vm.$store.state.jobLog = logLinesParser(mockJobLog, [], '#L6').lines;
|
||||
await waitForPromises();
|
||||
|
||||
expect(scrollToElement).toHaveBeenCalledTimes(1);
|
||||
|
||||
state.jobLog = logLinesParser(mockJobLog, [], '#L6').lines;
|
||||
wrapper.vm.$store.state.jobLog = logLinesParser(mockJobLog, [], '#L6').lines;
|
||||
await waitForPromises();
|
||||
|
||||
expect(scrollToElement).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('line number within collapsed section is visible', () => {
|
||||
state.jobLog = logLinesParser(mockJobLog, [], '#L6').lines;
|
||||
initialState.jobLog = logLinesParser(mockJobLog, [], '#L6').lines;
|
||||
|
||||
createComponent();
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ describe('Job Sidebar Retry Button', () => {
|
|||
});
|
||||
};
|
||||
|
||||
beforeEach(createWrapper);
|
||||
beforeEach(() => {
|
||||
createWrapper();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[null, false, true],
|
||||
|
|
@ -82,8 +84,9 @@ describe('Job Sidebar Retry Button', () => {
|
|||
store,
|
||||
});
|
||||
};
|
||||
|
||||
it('should not render confirmation modal if confirmation message is null', () => {
|
||||
findRetryLink().vm.$emit('click');
|
||||
findRetryLink().trigger('click');
|
||||
expect(confirmAction).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ describe('Job App', () => {
|
|||
wrapper = shallowMountExtended(JobApp, {
|
||||
propsData: { ...props },
|
||||
store,
|
||||
provide: {
|
||||
glAbilities: { troubleshootJobWithAi: false },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -165,6 +168,9 @@ describe('Job App', () => {
|
|||
details_path: 'path',
|
||||
action: {
|
||||
confirmation_message: null,
|
||||
button_title: 'Retry job',
|
||||
method: 'post',
|
||||
path: '/path',
|
||||
},
|
||||
illustration: {
|
||||
content: 'Run this job again in order to create the necessary resources.',
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { pipelineHeaderSuccess, pipelineHeaderTrigger } from '../../mock_data';
|
|||
describe('Header badges', () => {
|
||||
let wrapper;
|
||||
|
||||
const findAllBadges = () => wrapper.findAllComponents(GlBadge);
|
||||
const findAllBadges = () => wrapper.findAllComponents(GlBadge).wrappers;
|
||||
const findChildPipelineBadge = () =>
|
||||
findAllBadges().filter((badge) => {
|
||||
const sprintf = badge.findComponent(GlSprintf);
|
||||
|
|
@ -62,7 +62,7 @@ describe('Header badges', () => {
|
|||
});
|
||||
|
||||
expect(findAllBadges()).toHaveLength(4);
|
||||
expect(findChildPipelineBadge().exists()).toBe(true);
|
||||
expect(findChildPipelineBadge()).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ describe('Pipeline Multi Actions Dropdown', () => {
|
|||
path: '/new/download/path-three',
|
||||
},
|
||||
];
|
||||
const artifactItemTestId = 'artifact-item';
|
||||
const artifactsEndpointPlaceholder = ':pipeline_artifacts_id';
|
||||
const artifactsEndpoint = `endpoint/${artifactsEndpointPlaceholder}/artifacts.json`;
|
||||
const pipelineId = 108;
|
||||
|
|
@ -77,7 +76,6 @@ describe('Pipeline Multi Actions Dropdown', () => {
|
|||
const findAlert = () => wrapper.findByTestId('artifacts-fetch-error');
|
||||
const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findFirstArtifactItem = () => wrapper.findByTestId(artifactItemTestId);
|
||||
const findAllArtifactItemsData = () =>
|
||||
findDropdown()
|
||||
.props('items')
|
||||
|
|
@ -182,8 +180,7 @@ describe('Pipeline Multi Actions Dropdown', () => {
|
|||
});
|
||||
|
||||
it('should render the correct artifact name and path', () => {
|
||||
expect(findFirstArtifactItem().attributes('href')).toBe(artifacts[0].path);
|
||||
expect(findFirstArtifactItem().text()).toBe(artifacts[0].name);
|
||||
expect(findAllArtifactItemsData()).toEqual(artifacts);
|
||||
});
|
||||
|
||||
describe('when opened again with new artifacts', () => {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ describe('CreateMenu component', () => {
|
|||
const findGlDisclosureDropdownGroups = () => wrapper.findAllComponents(GlDisclosureDropdownGroup);
|
||||
const findGlDisclosureDropdownItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem);
|
||||
const findInviteMembersTrigger = () => wrapper.findComponent(InviteMembersTrigger);
|
||||
const findCreateWorkItemModalTrigger = () =>
|
||||
findGlDisclosureDropdownItems()
|
||||
.filter((item) => item.props('item').text === 'New epic')
|
||||
.at(0);
|
||||
const findCreateWorkItemModal = () => wrapper.findComponent(CreateWorkItemModal);
|
||||
|
||||
const createWrapper = ({ provide = {} } = {}) => {
|
||||
|
|
@ -82,12 +86,34 @@ describe('CreateMenu component', () => {
|
|||
});
|
||||
|
||||
describe('create new work item modal', () => {
|
||||
it('renders the modal', () => {
|
||||
expect(findCreateWorkItemModal().exists()).toBe(true);
|
||||
it('renders work item menu item correctly', () => {
|
||||
expect(findCreateWorkItemModalTrigger().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('sets `isGroup` to `true`', () => {
|
||||
it('does not render the modal by default', () => {
|
||||
expect(findCreateWorkItemModal().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows modal when clicking work item dropdown item', async () => {
|
||||
findCreateWorkItemModalTrigger().vm.$emit('action');
|
||||
await nextTick();
|
||||
|
||||
expect(findCreateWorkItemModal().exists()).toBe(true);
|
||||
expect(findCreateWorkItemModal().props('isGroup')).toBe(true);
|
||||
expect(findCreateWorkItemModal().props('visible')).toBe(true);
|
||||
expect(findCreateWorkItemModal().props('hideButton')).toBe(true);
|
||||
});
|
||||
|
||||
it('hides modal when hideModal event is emitted', async () => {
|
||||
findCreateWorkItemModalTrigger().vm.$emit('action');
|
||||
await nextTick();
|
||||
|
||||
expect(findCreateWorkItemModal().exists()).toBe(true);
|
||||
|
||||
findCreateWorkItemModal().vm.$emit('hideModal');
|
||||
await nextTick();
|
||||
|
||||
expect(findCreateWorkItemModal().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ RSpec.describe Issue, feature_category: :team_planning do
|
|||
let(:type_attributes) { { work_item_type_id: type1.id, correct_work_item_type_id: type2.correct_id } }
|
||||
|
||||
it 'does not overwrite any of the provided values' do
|
||||
expect(issue.work_item_type_id).to eq(type1.id)
|
||||
expect(issue.attributes['work_item_type_id']).to eq(type1.id)
|
||||
expect(issue.correct_work_item_type_id).to eq(type2.correct_id)
|
||||
end
|
||||
end
|
||||
|
|
@ -90,7 +90,7 @@ RSpec.describe Issue, feature_category: :team_planning do
|
|||
expect do
|
||||
issue.update_columns(work_item_type_id: type1.id, correct_work_item_type_id: type2.correct_id)
|
||||
issue.reload
|
||||
end.to change { issue.work_item_type_id }.to(type1.id).and(
|
||||
end.to change { issue.attributes['work_item_type_id'] }.to(type1.id).and(
|
||||
change { issue.correct_work_item_type_id }.to(type2.correct_id)
|
||||
)
|
||||
end
|
||||
|
|
@ -2599,6 +2599,20 @@ RSpec.describe Issue, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#work_item_type_id' do
|
||||
let_it_be(:work_item_type) { create(:work_item_type, :non_default) }
|
||||
let_it_be(:issue) { create(:issue, project: reusable_project) }
|
||||
|
||||
it 'returns the correct work_item_types.id value even if the value in the column is wrong' do
|
||||
issue.update_columns(
|
||||
work_item_type_id: non_existing_record_id,
|
||||
correct_work_item_type_id: work_item_type.correct_id
|
||||
)
|
||||
|
||||
expect(issue.work_item_type_id).to eq(work_item_type.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#work_item_type_id=', :aggregate_failures do
|
||||
let_it_be(:type1) do
|
||||
create(:work_item_type, :non_default).tap do |type|
|
||||
|
|
|
|||
|
|
@ -417,11 +417,11 @@ module TestEnv
|
|||
Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions
|
||||
Gitlab::DatabaseImporters::WorkItems::RelatedLinksRestrictionsImporter.upsert_restrictions
|
||||
|
||||
# We need this temporarily to make sure the app works when work_item_types.id and work_item_types.correct_id
|
||||
# are different (as in some production apps). We can remove when work_item_types.id values are fixed (1 - 9)
|
||||
# Updating old_id to simulate an environment that has gone through the process of cleaning
|
||||
# the issues.work_item_type_id column. old_id is used as a fallback id.
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/499911
|
||||
WorkItems::Type.find_each do |work_item_type|
|
||||
work_item_type.update!(id: -work_item_type.id, old_id: -work_item_type.id)
|
||||
work_item_type.update!(old_id: -work_item_type.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1413,10 +1413,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/noop/-/noop-1.0.0.tgz#b1ecb8ae6b2abf9b2e28927e4fbb05b7a1b2704b"
|
||||
integrity sha512-nOltttik5o2BjBo8LnyeTFzHoLpMY/XcCVOC+lm9ZwU+ivEam8wafacMF0KTbRn1KVrIoHYdo70QnqS+vJiOVw==
|
||||
|
||||
"@gitlab/query-language-rust@0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/query-language-rust/-/query-language-rust-0.3.0.tgz#41ce042886bd0e87e372b1355963acaec6777ab9"
|
||||
integrity sha512-zn43UMaHJKjFjerEMZWMqbWkZ05sX0U9F1D235kSY8zQgFk2qWu2rOE5GwJAmCgMd0vdZSHwaSWhzpsc3lX+cw==
|
||||
"@gitlab/query-language-rust@0.3.1":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/query-language-rust/-/query-language-rust-0.3.1.tgz#eb758c9b21326765a541dc016e8d459ad4f6b8db"
|
||||
integrity sha512-nXmLZHjEMJwiQbdwrTZiTCKkzLbyG7+XOUK/ARrRZGKHg775EhVCY5sj9i6eGGXwUBxnJ0Os2oH12Nc1/s47cQ==
|
||||
|
||||
"@gitlab/stylelint-config@6.2.2":
|
||||
version "6.2.2"
|
||||
|
|
|
|||
Loading…
Reference in New Issue