From da1268042d6486fefe4344e5fb5929c88cdf2ee2 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 7 Nov 2024 12:14:08 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/CODEOWNERS | 2 +- .../User Acceptance Test Task.md | 147 ++++++++++++++++++ .../User Acceptance Testing.md | 117 ++++++++++++++ .../layout/empty_line_after_magic_comment.yml | 1 - .rubocop_todo/style/if_unless_modifier.yml | 1 - .../components/content_editor.vue | 2 +- .../extensions/code_block_highlight.js | 10 +- .../analytics/cycle_analytics/aggregation.rb | 2 +- .../cycle_analytics/stage_aggregation.rb | 4 + app/models/member.rb | 10 +- app/models/namespace/detail.rb | 2 + app/services/groups/destroy_service.rb | 2 - doc/api/graphql/assign_gitlab_duo_seats.md | 93 +++++++++++ doc/development/ai_features/index.md | 79 +++++++++- .../event_definition_guide.md | 2 +- .../quick_start.md | 9 +- doc/topics/git/troubleshooting_git.md | 1 + doc/user/project/repository/web_editor.md | 2 +- locale/gitlab.pot | 2 +- .../code_block_bubble_menu_spec.js | 2 +- .../components/content_editor_spec.js | 2 +- .../extensions/code_block_highlight_spec.js | 17 ++ .../cycle_analytics/aggregation_spec.rb | 4 +- .../cycle_analytics/stage_aggregation_spec.rb | 16 +- spec/models/member_spec.rb | 79 ++++++---- spec/models/namespace_spec.rb | 2 - spec/scripts/internal_events/server_spec.rb | 4 +- spec/services/groups/destroy_service_spec.rb | 2 - .../helpers/cycle_analytics_helpers.rb | 2 +- .../value_streams_dashboard_helpers.rb | 2 +- 30 files changed, 553 insertions(+), 67 deletions(-) create mode 100644 .gitlab/issue_templates/User Acceptance Test Task.md create mode 100644 .gitlab/issue_templates/User Acceptance Testing.md create mode 100644 doc/api/graphql/assign_gitlab_duo_seats.md diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index cc3001bbe5e..5433b50511a 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -1145,7 +1145,7 @@ lib/gitlab/checks/** /doc/user/project/repository/code_suggestions/ @jglassman1 /doc/user/project/repository/files/index.md @ashrafkhamis /doc/user/project/repository/monorepos/ @eread -/doc/user/project/repository/web_editor.md @ashrafkhamis +/doc/user/project/repository/web_editor.md @brendan777 /doc/user/project/requirements/ @msedlakjakubowski /doc/user/project/service_desk/ @msedlakjakubowski /doc/user/project/settings/import_export.md @ashrafkhamis diff --git a/.gitlab/issue_templates/User Acceptance Test Task.md b/.gitlab/issue_templates/User Acceptance Test Task.md new file mode 100644 index 00000000000..5060095b95e --- /dev/null +++ b/.gitlab/issue_templates/User Acceptance Test Task.md @@ -0,0 +1,147 @@ +# Test case name: TC-XX-## + +## Test case scenario + +_Describe the test scenario this task corresponds to_ + + + + + + + + + + + + + + + + + + + + + + + + +
Deal Type (select everything that applies)Product Type (select everything that applies)Product TierDeal Term (months)Ramped segmentsInitial Purchase Discounted?Purchase MethodPurchase Path
+
    +
  • - [ ] New
  • +
  • - [ ] Add-on (Storage, CI minutes)
  • +
  • - [ ] Add seats
  • +
  • - [ ] Manual renewal
  • +
  • - [ ] Auto-renewal
  • +
  • - [ ] Upgrade
  • +
  • - [ ] Cancellation
  • +
  • - [ ] First order
  • +
+
+
    +
  • - [ ] SaaS
  • +
  • - [ ] Self-managed
  • +
  • - [ ] True-up
  • +
  • - [ ] Add-on (Storage, CI minutes)
  • +
  • - [ ] Extra seats
  • +
  • - [ ] Professional services only
  • +
+
+
    +
  • - [ ] Premium
  • +
  • - [ ] Ultimate
  • +
  • - [ ] Premium > Ultimate (upgrade)
  • +
  • - [ ] Legacy Premium
  • +
  • - [ ] Legacy Ultimate
  • +
+
+
    +
  • - [ ] 12
  • +
  • - [ ] 24
  • +
  • - [ ] 36
  • +
+
+
    +
  • - [ ] Non-Ramp
  • +
  • - [ ] 2
  • +
  • - [ ] 3
  • +
+
+
    +
  • - [ ] Yes
  • +
  • - [ ] No
  • +
+
+
    +
  • - [ ] Web store
  • +
  • - [ ] Sales-assisted
  • +
  • - [ ] Automated
  • +
+
+
    +
  • - [ ] Direct
  • +
  • - [ ] Partner
  • +
+
+ +## Artifacts + +_Add various artifacts for this test scenario for reference_ + +1. Account (SFDC): +1. Opportunity (SFDC): +1. Quote (SFDC): +1. Order form (SFDC): +1. Subscription (Zuora): +1. Test case dependency (if any): + +## Test steps + +_List the steps to be performed to verify this scenario_ + + + +## Expected result + +_Describe the expected outcome of this test scenario_ + +### Screenshots + +| Step | Screenshot | +|---|---| +| | | + + + +/confidential +/label ~"devops::fulfillment" ~"section::fulfillment" + + diff --git a/.gitlab/issue_templates/User Acceptance Testing.md b/.gitlab/issue_templates/User Acceptance Testing.md new file mode 100644 index 00000000000..bf50d02b65a --- /dev/null +++ b/.gitlab/issue_templates/User Acceptance Testing.md @@ -0,0 +1,117 @@ + + +# User Acceptance Testing + +# Summary + +Manual testing for the feature. + +# Pre-requisite + +_Add any steps to be performed before end to end testing can begin_ + +- [ ] Set a milestone for the test session issue to inform all the DRIs about the upcoming testing session. +- [ ] System leaders identify a DRI to participate in the testing session. +- [ ] Completion of Staging Rollout issue ([example](https://gitlab.com/gitlab-org/customers-gitlab-com/-/issues/6202)). +- [ ] Ensure all testers have the right access/permissions in all our Staging applications (Zuora, Salesforce, CDot, etc) for testing purposes. +- [ ] Ensure all system DRIs have reviewed the new test scenarios and approved the changes. + - [ ] Sales Systems: `@handle`. + - [ ] Sales Ops: `@handle`. + - [ ] Enterprise Apps: `@handle`. + - [ ] Data: `@handle`. + - [ ] Billing: `@handle`. + - [ ] Revenue: `@handle`. + - [ ] Fulfillment: `@pm-handle` and `@em-handle`. +- [ ] Communicate an estimated time for the testing session to all the DRIs. +- [ ] **{-Enable feature flag:}** {feature-flag-name} on {environment-name}: [Feature flag rollout issue](). +- [ ] Clear any caches. + +# Useful Links + +_Add any links that are helpful to carry out testing such as links to the flows involved, etc._ + + + +1. ... + +# How to + +1. Create a [**Task**](User%20Acceptance%20Test%20Task.md) for each scenario mentioned in the list. + - The Task holds each relevant Test Case for the scenario in its own section. + - Each section holds that case's testing outcome and artifacts. (E.g. screenshots, screen recordings, or text notes.) +1. Create a [**Test Case**](https://gitlab.com/gitlab-org/customers-gitlab-com/-/quality/test_cases) for each variation of the scenario, if one does not already exist. (E.g. testing across product tiers or user roles.) + - The Test Case outlines the testing scenario, the test steps involved, and the expected result. + - **Make sure to set the Test Case to be confidential if applicable when it's created**, as it might not be confidential by default. +1. Before testing a Task, assign yourself to it to avoid multiple people testing the same scenario. +1. **\[Optional\]** assign a tested Task to a PM for review. + - Use the `Task` comments section to discuss any unexpected behaviour and create follow-up Issue(s). +1. **\[Optional\]** A PM should sign-off the test scenario if everything looks good. The, close the Task, and mark it as complete in this issue. +1. Add [Bug(s)](#identified-bugs) and/or [Question(s)](#open-questions) to the corresponding sections below. + +# Test Cases + + + + + + + + + + + + + + + + + + + + +
Scenario #TaskTest CaseScenarioExpected OutcomeProduct Sign-offUX Sign-off
+ + + + + + +
+ +# Identified Bugs + +| Bugs | Testing type (Automated/ Manual) | Resolution | MR | DRI | +|------|----------------------------------|------------|-----|-----| +| | | | | | + +| Question | Related Test Case | Answer | DRI | +| -------- | ----------------- | -------| --- | +| _Summary of question here (can have link to discussion from comments)_ | _Test case link_ | _Final answer / resolution_ | _Person responsible for answering question_ | + +## Sign-offs + +_Once all scenarios have passed validation, stakeholders will provide final sign-off below_ + +- [ ] Sales Systems: `@handle` +- [ ] Sales Ops: `@handle` +- [ ] Enterprise Apps: `@handle` +- [ ] Data: `@handle` +- [ ] Billing: `@handle` +- [ ] Revenue: `@handle` +- [ ] Fulfillment: `@pm-handle` and `@em-handle` + +/label ~"devops::fulfillment" ~"section::fulfillment" diff --git a/.rubocop_todo/layout/empty_line_after_magic_comment.yml b/.rubocop_todo/layout/empty_line_after_magic_comment.yml index 3d9d6d24baf..d8df41e728f 100644 --- a/.rubocop_todo/layout/empty_line_after_magic_comment.yml +++ b/.rubocop_todo/layout/empty_line_after_magic_comment.yml @@ -151,7 +151,6 @@ Layout/EmptyLineAfterMagicComment: - 'ee/app/serializers/ee/issue_entity.rb' - 'ee/app/serializers/license_compliance/collapsed_comparer_entity.rb' - 'ee/app/serializers/license_compliance/comparer_serializer.rb' - - 'ee/app/services/analytics/cycle_analytics/aggregator_service.rb' - 'ee/app/services/arkose/blocked_users_report_service.rb' - 'ee/app/services/audit_events/streaming/headers/base.rb' - 'ee/app/services/audit_events/streaming/headers/destroy_service.rb' diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml index 0b9f598d444..00efbd0c2f0 100644 --- a/.rubocop_todo/style/if_unless_modifier.yml +++ b/.rubocop_todo/style/if_unless_modifier.yml @@ -287,7 +287,6 @@ Style/IfUnlessModifier: - 'ee/app/serializers/ee/blob_entity.rb' - 'ee/app/serializers/linked_epic_issue_entity.rb' - 'ee/app/serializers/vulnerabilities/finding_serializer.rb' - - 'ee/app/services/analytics/cycle_analytics/aggregator_service.rb' - 'ee/app/services/analytics/cycle_analytics/validations.rb' - 'ee/app/services/app_sec/dast/pipelines/find_latest_service.rb' - 'ee/app/services/app_sec/dast/profiles/build_config_service.rb' diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue index e75c8b63733..361493c7838 100644 --- a/app/assets/javascripts/content_editor/components/content_editor.vue +++ b/app/assets/javascripts/content_editor/components/content_editor.vue @@ -199,7 +199,7 @@ export default { this.contentEditor.setEditable(false); this.contentEditor.eventHub.$emit(ALERT_EVENT, { message: __( - 'An error occurred while trying to render the content editor. Please try again.', + 'An error occurred while trying to render the rich text editor. Please try again.', ), variant: VARIANT_DANGER, actionLabel: __('Retry'), diff --git a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js index e75178789da..555716f1c4d 100644 --- a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js +++ b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js @@ -1,5 +1,6 @@ import { lowlight } from 'lowlight/lib/core'; import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'; +import { Fragment } from '@tiptap/pm/model'; import { mergeAttributes, textblockTypeInputRule } from '@tiptap/core'; import { VueNodeViewRenderer } from '@tiptap/vue-2'; import languageLoader from '../services/code_block_language_loader'; @@ -61,16 +62,17 @@ export default CodeBlockLowlight.extend({ tag: 'div.markdown-code-block', skip: true, }, - { - tag: 'pre.js-syntax-highlight', - preserveWhitespace: 'full', - }, // Matches HTML generated by Banzai::Filter::SyntaxHighlightFilter, // Banzai::Filter::MathFilter, Banzai::Filter::MermaidFilter, // or Banzai::Filter::SuggestionFilter { tag: 'pre.code.highlight', preserveWhitespace: 'full', + getContent(element, schema) { + return element.textContent + ? Fragment.from(schema.text(element.textContent)) + : Fragment.empty; + }, }, // Matches HTML generated by Banzai::Filter::MathFilter, // after being transformed by ~/behaviors/markdown/render_math.js diff --git a/app/models/analytics/cycle_analytics/aggregation.rb b/app/models/analytics/cycle_analytics/aggregation.rb index 8d83ca7ce5f..9c4308118aa 100644 --- a/app/models/analytics/cycle_analytics/aggregation.rb +++ b/app/models/analytics/cycle_analytics/aggregation.rb @@ -36,7 +36,7 @@ class Analytics::CycleAnalytics::Aggregation < ApplicationRecord self["last_#{mode}_run_at"] = Time.current end - def reset_full_run_cursors + def complete self.last_full_issues_id = nil self.last_full_issues_updated_at = nil self.last_full_merge_requests_id = nil diff --git a/app/models/analytics/cycle_analytics/stage_aggregation.rb b/app/models/analytics/cycle_analytics/stage_aggregation.rb index 88fd8dcb5fd..6a94601f299 100644 --- a/app/models/analytics/cycle_analytics/stage_aggregation.rb +++ b/app/models/analytics/cycle_analytics/stage_aggregation.rb @@ -33,6 +33,10 @@ module Analytics self.runtimes_in_seconds = (runtimes_in_seconds + [runtime]).last(STATS_SIZE_LIMIT) self.processed_records = (self.processed_records + [processed_records]).last(STATS_SIZE_LIMIT) end + + def complete + self.last_completed_at = Time.current + end end end end diff --git a/app/models/member.rb b/app/models/member.rb index 3f3835bde24..52740d91ea5 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -323,7 +323,7 @@ class Member < ApplicationRecord after_create :update_two_factor_requirement, unless: :invite? after_create :create_organization_user_record after_update :post_update_hook, unless: [:pending?, :importing?], if: :hook_prerequisites_met? - after_update :create_organization_user_record, if: :saved_change_to_user_id? # only occurs on invite acceptance + after_update :create_organization_user_record, if: :accepted_invite_or_request? after_destroy :destroy_notification_setting after_destroy :post_destroy_member_hook, unless: :pending?, if: :hook_prerequisites_met? after_destroy :post_destroy_access_request_hook, if: [:request?, :hook_prerequisites_met?] @@ -777,11 +777,17 @@ class Member < ApplicationRecord end def create_organization_user_record - return if invite? + return if pending? return if source.organization.blank? Organizations::OrganizationUser.create_organization_record_for(user_id, source.organization_id) end + + def accepted_invite_or_request? + # `user_id` is nil for member invited through email and will be set once the user has created an account. + # `requested_at` is defined only while the membership access request is still pending. + saved_change_to_user_id? || saved_change_to_requested_at? + end end Member.prepend_mod_with('Member') diff --git a/app/models/namespace/detail.rb b/app/models/namespace/detail.rb index e43b7d20c97..4e1ce5d9023 100644 --- a/app/models/namespace/detail.rb +++ b/app/models/namespace/detail.rb @@ -7,6 +7,8 @@ class Namespace::Detail < ApplicationRecord validates :namespace, presence: true validates :description, length: { maximum: 255 } + ignore_column :pending_delete, remove_with: '17.7', remove_after: '2024-11-22' + self.primary_key = :namespace_id # This method should not be called directly. Instead, it is available on the namespace via delegation and should diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb index 9c7caa7368c..e868d2a2fa9 100644 --- a/app/services/groups/destroy_service.rb +++ b/app/services/groups/destroy_service.rb @@ -59,12 +59,10 @@ module Groups private def mark_deleted - group.update_attribute(:pending_delete, true) group.update_attribute(:deleted_at, Time.current) end def unmark_deleted - group.update_attribute(:pending_delete, false) group.update_attribute(:deleted_at, nil) end diff --git a/doc/api/graphql/assign_gitlab_duo_seats.md b/doc/api/graphql/assign_gitlab_duo_seats.md new file mode 100644 index 00000000000..b850d495e5c --- /dev/null +++ b/doc/api/graphql/assign_gitlab_duo_seats.md @@ -0,0 +1,93 @@ +--- +stage: Fulfillment +group: Provision +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Assign GitLab Duo seats by using GraphQL + +DETAILS: +**Tier:** Premium, Ultimate +**Offering:** GitLab.com + +Use the GraphQL API to assign GitLab Duo seats to users. + +## Prerequisites + +- You must have the Owner role for the group you want to assign seats to. +- You must have a personal access token with the `api` scope. + +## Get the add-on purchase ID + +To start, retrieve the purchase ID for the GitLab Duo add-on: + +```graphql +query { + addOnPurchases (namespaceId: "gid://gitlab/Group/YOUR_NAMESPACE_ID") + { + name + purchasedQuantity + assignedQuantity + id + } +} +``` + +## Assign a GitLab Duo seat to specific users + +Then assign seats to specific users: + +```graphql +mutation { + userAddOnAssignmentBulkCreate(input: { + addOnPurchaseId: "gid://gitlab/GitlabSubscriptions::AddOnPurchase/YOUR_ADDON_PURCHASE_ID", + userIds: [ + "gid://gitlab/User/USER_ID_1", + "gid://gitlab/User/USER_ID_2", + "gid://gitlab/User/USER_ID_3" + ] + }) { + addOnPurchase { + id + name + assignedQuantity + purchasedQuantity + } + users { + nodes { + id + username + } + } + errors + } +} +``` + +## Use GraphiQL + +You can use [GraphiQL](https://gitlab.com/-/graphql-explorer) to assign seats to users. + +1. Copy the add-on purchase ID code excerpt. +1. Open GraphiQL. +1. In the left window, enter the query: + + ```graphql + query { + addOnPurchases (namespaceId: "gid://gitlab/Group/YOUR_NAMESPACE_ID") + { + name + purchasedQuantity + assignedQuantity + id + } + } + ``` + +1. Select **Play**. +1. Repeat to assign a GitLab Duo seat to specific users. + +## Related topics + +- [GraphQL API Resources](reference/index.md) +- [GraphQL specific entities, like fragments and interfaces](https://graphql.org/learn/) diff --git a/doc/development/ai_features/index.md b/doc/development/ai_features/index.md index 29df02b9fc6..216f05f19f6 100644 --- a/doc/development/ai_features/index.md +++ b/doc/development/ai_features/index.md @@ -811,6 +811,44 @@ LLM models are constantly evolving, and GitLab needs to regularly update our AI Provide a comprehensive guide for migrating AI models within GitLab. +#### Expected Duration + +Model migrations typically follow these general timelines: + +- **Simple Model Updates (Same Provider):** 2-3 weeks + - Example: Upgrading from Claude Sonnet 3.5 to 3.6 + - Involves model validation, testing, and staged rollout + - Primary focus on maintaining stability and performance + - Can sometimes be expedited when urgent, but 2 weeks is standard + +- **Complex Migrations:** 1-2 months (full milestone or longer) + - Example: Adding support for a new provider like AWS Bedrock + - Example: Major version upgrades with breaking changes (e.g., Claude 2 to 3) + - Requires significant API integration work + - May need infrastructure changes + - Extensive testing and validation required + +#### Timeline Factors + +Several factors can impact migration timelines: + +- Current system stability and recent incidents +- Resource availability and competing priorities +- Complexity of behavioral changes in new model +- Scale of testing required +- Feature flag rollout strategy + +#### Best Practices + +- Always err on the side of caution with initial timeline estimates +- Use feature flags for gradual rollouts to minimize risk +- Plan for buffer time to handle unexpected issues +- Communicate conservative timelines externally while working to deliver faster +- Prioritize system stability over speed of deployment + +NOTE: +While some migrations can technically be completed quickly, we typically plan for longer timelines to ensure proper testing and staged rollouts. This approach helps maintain system stability and reliability. + ### Scope Applicable to all AI model-related teams at GitLab. We currently only support using Anthropic and Google Vertex models, with plans to support AWS Bedrock models in the [future](https://gitlab.com/gitlab-org/gitlab/-/issues/498119). @@ -819,11 +857,39 @@ Applicable to all AI model-related teams at GitLab. We currently only support us Before starting a model migration: -- Verify the new model is supported in our current AI-Gateway API specification -- Document any known behavioral changes or improvements in the new model +- Create an issue under the [AI Model Version Migration Initiative epic](https://gitlab.com/groups/gitlab-org/-/epics/15650) with the following: + - Label with `group::ai framework` + - Document any known behavioral changes or improvements in the new model + - Include any breaking changes or compatibility issues + - Reference any model provider documentation about the changes + +- Verify the new model is supported in our current AI-Gateway API specification by: + + - Check model definitions in AI Gateway: + - For LiteLLM models: `ai_gateway/models/v2/container.py` + - For Anthropic models: `ai_gateway/models/anthropic.py` + - For new providers: Create a new model definition file in `ai_gateway/models/` + - Verify model configurations: + - Model enum definitions + - Stop tokens + - Timeout settings + - Completion type (text or chat) + - Max token limits + - Testing the model locally in AI Gateway: + - Set up the [AI Gateway development environment](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist#how-to-run-the-server-locally) + - Configure the necessary API keys in your `.env` file + - Test the model using the Swagger UI at `http://localhost:5052/docs` + - If the model isn't supported, create an issue in the [AI Gateway repository](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist) to add support + - Review the provider's API documentation for any breaking changes: + - [Anthropic API Documentation](https://docs.anthropic.com/claude/reference/versions) + - [Google Vertex AI Documentation](https://cloud.google.com/vertex-ai/docs/reference) + - Ensure you have access to testing environments and monitoring tools - Complete model evaluation using the [Prompt Library](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/blob/main/doc/how-to/run_duo_chat_eval.md) +NOTE: +Documentation of model changes is crucial for tracking the impact of migrations and helping with future troubleshooting. Always create an issue to track these changes before beginning the migration process. + ### Migration Tasks #### Migration Tasks for Anthropic Model @@ -858,11 +924,18 @@ The model selection logic should be implemented in: #### Rollout Strategy - Enable the feature flag for a small percentage of users/groups initially -- Monitor performance metrics and error rates +- Monitor performance metrics and error rates using: + - [Sidekiq Service dashboard](https://dashboards.gitlab.net/d/sidekiq-main/sidekiq-overview) for error ratios and response latency + - [AI Gateway metrics dashboard](https://dashboards.gitlab.net/d/ai-gateway-main/ai-gateway3a-overview?orgId=1) for gateway-specific metrics + - [AI Gateway logs](https://log.gprd.gitlab.net/app/r/s/zKEel) for detailed error investigation + - [Feature usage dashboard](https://log.gprd.gitlab.net/app/r/s/egybF) for adoption metrics + - [Periscope dashboard](https://app.periscopedata.com/app/gitlab/1137231/Ai-Features) for token usage and feature statistics - Gradually increase the rollout percentage - If issues arise, quickly disable the feature flag to rollback to the previous model - Once stability is confirmed, remove the feature flag and make the migration permanent +For more details on monitoring during migrations, see the [Monitoring and Metrics](#monitoring-and-metrics) section below. + ### Scope of Work #### AI Features to Migrate diff --git a/doc/development/internal_analytics/internal_event_instrumentation/event_definition_guide.md b/doc/development/internal_analytics/internal_event_instrumentation/event_definition_guide.md index 24de9622aa8..510f5820b81 100644 --- a/doc/development/internal_analytics/internal_event_instrumentation/event_definition_guide.md +++ b/doc/development/internal_analytics/internal_event_instrumentation/event_definition_guide.md @@ -34,7 +34,7 @@ Each event is defined in a separate YAML file consisting of the following fields | `introduced_by_url` | no | The URL to the merge request that introduced the event. | | `distributions` | yes | The [distributions](https://handbook.gitlab.com/handbook/marketing/brand-and-product-marketing/product-and-solution-marketing/tiers/#definitions) where the tracked feature is available. Can be set to one or more of `ce` or `ee`. | | `tiers` | yes | The [tiers](https://handbook.gitlab.com/handbook/marketing/brand-and-product-marketing/product-and-solution-marketing/tiers/) where the tracked feature is available. Can be set to one or more of `free`, `premium`, or `ultimate`. | -| `additional_properties` | no | A list of additional properties that are sent with the event. Each record must have `description` field. Built-in properties are: `label` (string), `property` (string) and `value` (numeric). [Custom](quick_start.md#additional-properties) properties can be added if the built-in options are not sufficient. | +| `additional_properties` | no | A list of additional properties that are sent with the event. Each additional property must have a record entry with a `description` field. It is required to add all the additional properties that would be sent with the event in the event definition file. Built-in properties are: `label` (string), `property` (string) and `value` (numeric). [Custom](quick_start.md#additional-properties) properties can be added if the built-in options are not sufficient. | ### Example event definition diff --git a/doc/development/internal_analytics/internal_event_instrumentation/quick_start.md b/doc/development/internal_analytics/internal_event_instrumentation/quick_start.md index 27463adbfe1..983f39d2ccb 100644 --- a/doc/development/internal_analytics/internal_event_instrumentation/quick_start.md +++ b/doc/development/internal_analytics/internal_event_instrumentation/quick_start.md @@ -109,15 +109,18 @@ track_internal_event( ) ``` -If you need to pass more than three additional properties, you can use the `additional_properties` hash with your custom keys: +If you need to pass more than the three built-in additional properties, you can use the `additional_properties` hash with your custom keys: ```ruby track_internal_event( "code_suggestion_accepted", user: user, + additional_properties: { + # Built-in properties label: editor_name, property: suggestion_type, - value: suggestion_shown_duration + value: suggestion_shown_duration, + # Your custom properties lang: 'ruby', custom_key: 'custom_value' } @@ -369,7 +372,7 @@ Sometimes we want to send internal events when the component is rendered or load #### Additional properties -Additional properties can be passed when tracking events. They can be used to save additional data related to given event. It is possible to send a maximum of three additional properties with keys `label` (string), `property` (string) and `value`(numeric). +You can include additional properties with events to save additional data. When included you must define each additional property in the `additional_properties` field. It is possible to send the three built-in additional properties with keys `label` (string), `property` (string) and `value`(numeric) and [custom additional properties](quick_start.md#additional-properties) if the built-in properties are not sufficient. NOTE: Do not pass the page URL or page path as an additional property because we already track the pseudonymized page URL for each event. diff --git a/doc/topics/git/troubleshooting_git.md b/doc/topics/git/troubleshooting_git.md index cc9c2da89be..1f1b9f0a4f2 100644 --- a/doc/topics/git/troubleshooting_git.md +++ b/doc/topics/git/troubleshooting_git.md @@ -42,6 +42,7 @@ Git includes a complete set of [traces for debugging Git commands](https://git-s - `GIT_TRACE_PERFORMANCE=1`: enables tracing of performance data, showing how long each particular `git` invocation takes. - `GIT_TRACE_SETUP=1`: enables tracing of what `git` is discovering about the repository and environment it's interacting with. - `GIT_TRACE_PACKET=1`: enables packet-level tracing for network operations. +- `GIT_CURL_VERBOSE=1`: enables `curl`'s verbose output, which [may include credentials](https://curl.se/docs/manpage.html#-v). ## Broken pipe errors on `git push` diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md index 070bfdc8500..3bdd677d265 100644 --- a/doc/user/project/repository/web_editor.md +++ b/doc/user/project/repository/web_editor.md @@ -1,6 +1,6 @@ --- stage: Create -group: Remote Development +group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments description: "Use the Web Editor to create, upload, and edit text files directly in the GitLab UI." --- diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f93e2010250..2674f811869 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6187,7 +6187,7 @@ msgstr "" msgid "An error occurred while trying to follow this user, please try again." msgstr "" -msgid "An error occurred while trying to render the content editor. Please try again." +msgid "An error occurred while trying to render the rich text editor. Please try again." msgstr "" msgid "An error occurred while trying to run a new pipeline for this merge request." diff --git a/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js b/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js index 877ad3c9681..c61a74670b4 100644 --- a/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js +++ b/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js @@ -48,7 +48,7 @@ describe('content_editor/components/bubble_menus/code_block_bubble_menu', () => const preTag = ({ language, content = 'test' } = {}) => { const languageAttr = language ? ` data-canonical-lang="${language}"` : ''; - return `
${content}
`; + return `
${content}
`; }; const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); diff --git a/spec/frontend/content_editor/components/content_editor_spec.js b/spec/frontend/content_editor/components/content_editor_spec.js index 16a2f0ab1e1..52a920d938e 100644 --- a/spec/frontend/content_editor/components/content_editor_spec.js +++ b/spec/frontend/content_editor/components/content_editor_spec.js @@ -184,7 +184,7 @@ describe('ContentEditor', () => { it('displays error alert indicating that the content editor failed to load', () => { expect(findContentEditorAlert().text()).toContain( - 'An error occurred while trying to render the content editor. Please try again.', + 'An error occurred while trying to render the rich text editor. Please try again.', ); }); diff --git a/spec/frontend/content_editor/extensions/code_block_highlight_spec.js b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js index e12d87885be..3beee0918d6 100644 --- a/spec/frontend/content_editor/extensions/code_block_highlight_spec.js +++ b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js @@ -4,6 +4,7 @@ import languageLoader from '~/content_editor/services/code_block_language_loader import { createTestEditor, triggerNodeInputRule } from '../test_utils'; const CODE_BLOCK_HTML = `
console.log('hello world')
`; +const EMPTY_CODE_BLOCK_HTML = `
`; jest.mock('~/content_editor/services/code_block_language_loader'); @@ -52,6 +53,22 @@ describe('content_editor/extensions/code_block_highlight', () => { }); }); + it('correctly parses HTML with empty code block', () => { + tiptapEditor.commands.setContent(EMPTY_CODE_BLOCK_HTML); + + expect(tiptapEditor.getJSON()).toEqual( + doc( + codeBlock( + { + language: 'js', + class: 'code highlight js-syntax-highlight language-javascript', + }, + '', + ), + ).toJSON(), + ); + }); + describe.each` inputRule ${'```'} diff --git a/spec/models/analytics/cycle_analytics/aggregation_spec.rb b/spec/models/analytics/cycle_analytics/aggregation_spec.rb index 0c7efa6a861..94d99dd3375 100644 --- a/spec/models/analytics/cycle_analytics/aggregation_spec.rb +++ b/spec/models/analytics/cycle_analytics/aggregation_spec.rb @@ -84,14 +84,14 @@ RSpec.describe Analytics::CycleAnalytics::Aggregation, type: :model, feature_cat end end - describe '#reset_full_run_cursors' do + describe '#complete' do it 'resets all full run cursors to nil' do aggregation.last_full_issues_id = 111 aggregation.last_full_issues_updated_at = Time.current aggregation.last_full_merge_requests_id = 111 aggregation.last_full_merge_requests_updated_at = Time.current - aggregation.reset_full_run_cursors + aggregation.complete expect(aggregation).to have_attributes( last_full_issues_id: nil, diff --git a/spec/models/analytics/cycle_analytics/stage_aggregation_spec.rb b/spec/models/analytics/cycle_analytics/stage_aggregation_spec.rb index ea187c953e2..c6ada646dc9 100644 --- a/spec/models/analytics/cycle_analytics/stage_aggregation_spec.rb +++ b/spec/models/analytics/cycle_analytics/stage_aggregation_spec.rb @@ -64,13 +64,9 @@ RSpec.describe Analytics::CycleAnalytics::StageAggregation, type: :model, featur it_behaves_like 'has cursor fields', Issue it_behaves_like 'has cursor fields', MergeRequest - describe '#refresh_last_run' do - it 'updates the run_at column' do - freeze_time do - aggregation.refresh_last_run - - expect(aggregation.last_run_at).to eq(Time.current) - end + describe '#refresh_last_run', :freeze_time do + it 'updates last_run_at column' do + expect { aggregation.refresh_last_run }.to change { aggregation.last_run_at }.to(Time.current) end end @@ -85,5 +81,11 @@ RSpec.describe Analytics::CycleAnalytics::StageAggregation, type: :model, featur ) end end + + describe '#complete', :freeze_time do + it 'updates last_completed_at column' do + expect { aggregation.complete }.to change { aggregation.last_completed_at }.to(Time.current) + end + end end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index d1cfa579bee..33d66d1c2b6 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -1419,44 +1419,71 @@ RSpec.describe Member, feature_category: :groups_and_projects do it_behaves_like 'does not create an organization_user entry' end + + context 'when member is an access request' do + let(:member) { create(:group_member, :access_request, source: group, user: user) } + + it_behaves_like 'does not create an organization_user entry' + end end context 'when updating' do - context 'when member is an invite' do - let_it_be(:member, reload: true) { create(:group_member, :invited, source: group) } + shared_examples 'an action that creates an organization record after commit' do + it 'inserts new record on member creation' do + expect { commit_member }.to change { Organizations::OrganizationUser.count }.by(1) + expect(group.organization.user?(user)).to be(true) + end - context 'when accepting the invite' do - let_it_be(:user) { create(:user) } + context 'when organization does not exist' do + let_it_be(:member) { create(:group_member) } - subject(:commit_member) { member.accept_invite!(user) } + it_behaves_like 'does not create an organization_user entry' + end + end - it 'inserts new record on member creation' do - expect { commit_member }.to change { Organizations::OrganizationUser.count }.by(1) - expect(group.organization.user?(user)).to be(true) + context 'when member accept invite' do + let_it_be_with_reload(:member, reload: true) { create(:group_member, :invited, source: group) } + + subject(:commit_member) { member.accept_invite!(user) } + + it_behaves_like 'an action that creates an organization record after commit' + + context 'when updating the organization_users is not successful' do + before do + allow(Organizations::OrganizationUser) + .to receive(:create_organization_record_for).once.and_raise(ActiveRecord::StatementTimeout) end - context 'when updating the organization_users is not successful' do - before do - allow(Organizations::OrganizationUser) - .to receive(:create_organization_record_for).once.and_raise(ActiveRecord::StatementTimeout) - end - - it 'rolls back the member creation' do - expect { commit_member }.to raise_error(ActiveRecord::StatementTimeout) - expect(group.organization.user?(user)).to be(false) - expect(member.reset.user).to be_nil - end - end - - context 'when organization does not exist' do - let_it_be(:member) { create(:group_member) } - - it_behaves_like 'does not create an organization_user entry' + it 'rolls back the member creation', :aggregate_failures do + expect { commit_member }.to raise_error(ActiveRecord::StatementTimeout) + expect(group.organization.user?(user)).to be(false) + expect(member.reset.user).to be_nil end end end - context 'when updating a non user_id attribute' do + context "when member's access request is approved" do + let_it_be_with_reload(:member) { create(:group_member, :access_request, source: group, user: user) } + + subject(:commit_member) { member.accept_request(@owner_user) } + + it_behaves_like 'an action that creates an organization record after commit' + + context 'when updating the organization_users is not successful' do + before do + allow(Organizations::OrganizationUser) + .to receive(:create_organization_record_for).once.and_raise(ActiveRecord::StatementTimeout) + end + + it 'rolls back the member creation', :aggregate_failures do + expect { commit_member }.to raise_error(ActiveRecord::StatementTimeout) + expect(group.organization.user?(user)).to be(false) + expect(member.reset.requested_at).not_to be_nil + end + end + end + + context 'when updating a non user_id/requested_at attribute' do let_it_be(:member) { create(:group_member, :reporter, source: group) } subject(:commit_member) { member.update!(access_level: GroupMember::DEVELOPER) } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index aec42f4448c..288d0ccf583 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -671,8 +671,6 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do it { is_expected.to delegate_method(:token_expiry_notify_inherited).to(:namespace_settings) } it { is_expected.to delegate_method(:token_expiry_notify_inherited=).to(:namespace_settings).with_arguments(:args) } it { is_expected.to delegate_method(:add_creator).to(:namespace_details) } - it { is_expected.to delegate_method(:pending_delete).to(:namespace_details) } - it { is_expected.to delegate_method(:pending_delete=).to(:namespace_details).with_arguments(:args) } it { is_expected.to delegate_method(:deleted_at).to(:namespace_details) } it { is_expected.to delegate_method(:deleted_at=).to(:namespace_details).with_arguments(:args) } diff --git a/spec/scripts/internal_events/server_spec.rb b/spec/scripts/internal_events/server_spec.rb index 172749c0565..8b0761dc859 100644 --- a/spec/scripts/internal_events/server_spec.rb +++ b/spec/scripts/internal_events/server_spec.rb @@ -183,7 +183,7 @@ RSpec.describe Server, feature_category: :service_ping do await { Net::HTTP.new('localhost', port).options('/com.snowplowanalytics.snowplow/tp2') } end - it 'applies the correct headers' do + it 'applies the correct headers', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/498779' do expect(response.code).to eq('200') expect(response.header['Access-Control-Allow-Credentials']).to eq('true') expect(response.header['Access-Control-Allow-Headers']).to eq('Content-Type') @@ -194,7 +194,7 @@ RSpec.describe Server, feature_category: :service_ping do describe 'GET /micro/good -> list tracked structured events' do subject(:response) { await { Net::HTTP.get_response url_for("/micro/good") } } - it 'successfully returns tracked events' do + it 'successfully returns tracked events', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/498777' do expect(response.code).to eq('200') expect(response.body).to eq("[]") end diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index 7972bc4058e..6ad2cf55c8a 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -88,7 +88,6 @@ RSpec.describe Groups::DestroyService, feature_category: :groups_and_projects do shared_examples 'marks the group as delete' do |async| it 'marks the group as deleted', :freeze_time do - expect(group).to receive(:update_attribute).with(:pending_delete, true) expect(group).to receive(:update_attribute).with(:deleted_at, Time.current) destroy_group(group, user, async) @@ -125,7 +124,6 @@ RSpec.describe Groups::DestroyService, feature_category: :groups_and_projects do it 'unmarks the group as delete' do expect { destroy_group(group, user, false) }.to raise_error(StandardError) - expect(group.pending_delete).to be_falsey expect(group.deleted_at).to be_nil end end diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index 274eaac0674..683ca86d9da 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -102,7 +102,7 @@ module CycleAnalyticsHelpers def create_value_stream_aggregation(namespace) aggregation = Analytics::CycleAnalytics::Aggregation.safe_create_for_namespace(namespace) - Analytics::CycleAnalytics::AggregatorService.new(aggregation: aggregation).execute + Analytics::CycleAnalytics::NamespaceAggregatorService.new(aggregation: aggregation).execute end def select_group_and_custom_value_stream(group, custom_value_stream_name) diff --git a/spec/support/helpers/value_streams_dashboard_helpers.rb b/spec/support/helpers/value_streams_dashboard_helpers.rb index 5c88d5dfdf5..c1d61a8dfb7 100644 --- a/spec/support/helpers/value_streams_dashboard_helpers.rb +++ b/spec/support/helpers/value_streams_dashboard_helpers.rb @@ -186,7 +186,7 @@ module ValueStreamsDashboardHelpers end end - Analytics::CycleAnalytics::DataLoaderService.new(group: project.group, model: Issue).execute + Analytics::CycleAnalytics::DataLoaderService.new(namespace: project.group, model: Issue).execute end def create_mock_merge_request_metrics(project)