diff --git a/.gitlab/issue_templates/UX design brief.md b/.gitlab/issue_templates/UX design brief.md new file mode 100644 index 00000000000..151c17d3694 --- /dev/null +++ b/.gitlab/issue_templates/UX design brief.md @@ -0,0 +1,138 @@ + + +## Problem and scope + + + +
+See details + +### What is the problem to solve? + +`{ Add a brief description about the problem to solve for }` + +### Who is the design solution for? + +`{ Add persona and/or job performer }` + +### What is the [Job](https://handbook.gitlab.com/handbook/product/ux/jobs-to-be-done/#main-job-what-is-the-job-performer-trying-to-get-done) this user is trying to achieve? + +- ... + +### What [outcomes](https://handbook.gitlab.com/handbook/product/ux/jobs-to-be-done/outcome-driven-innovation-pilot/topics-and-definitions/#outcomes) is this design solution helping them achieve? + +If you have measured Outcome data, put that in the table. If not, delete the table and add the Outcomes to be designed for in a bulleted list. + +| Outcome Statement | Importance | Satisfaction | Overall Score | +|-------------------|------------|--------------|---------------| +| `{ e.g. Minimize the time it takes for users to navigate to the desired section of the application. }` | `5` | `1` | `9` | + + +### What are the requirements necessary to solve for this problem and Outcomes? + +`{ Create a bulleted list of everything that will need to be addressed in the holistic Design Vision in order to fully solve for the problem and Outcomes. Work with your counterparts to define this list. Keep it solution agnostic and try to understand any technical constraints that each requirement may imply. Only remove a requirement due to a technical constraint if it's not technically feasible to build within a reasonable amount of time (1-3 milestones is reasonable, anything longer than that you'll have to decide as a team how important it is to keep it in scope or address it in parallel in later iterations). }` + +* ... +* ... +* ... + +### What supporting research or customer validation do we have? + +- `{ Add links to any supporting research and related problem validation issues }` + +### What is the timeline? + +`{ Add milestone or link to planning issue that clarifies when the design must be ready for the Build phase by }` + +### What are the technical constraints? + +`{ `:warning:` This is to understand initial constraints in which the design solution needs to work within, NOT whether the solution can be implemented in a given milestone.` + +``Once the Product Designer has come up with a holistic Design Vision, or an ideal state for solving the problem, they should collaborate with their team members and engineers to continue the technical feasibility discussion during ``~"workflow::planning breakdown"``. }`` + +- `{ e.g. All visualization must use [eCharts](https://echarts.apache.org/examples/en/index.html#chart-type-line) }` +- `{ e.g. Data prior to 2024-10-10 will not be available }` +- `{ e.g. Solution will only be visible to Maintainer roles }` + +### In what parts of GitLab will this solution be available? + +Plans: + +* [ ] Free +* [ ] Premium +* [ ] Ultimate + +Instances: + +* [ ] Self-managed +* [ ] Dedicated +* [ ] GitLab.com + +Levels: + +* [ ] Instance +* [ ] Group +* [ ] Project + +### How will we know if the solution is successful? + +`{ If you don't have measured Outcome data to measure against, what other success metrics can we use? Otherwise you can reference the Outcomes table above (your goal is to beat the previous Outcome measurements Satisfaction numbers with this new design). }` + +
+ +## Ready for design + +
+See checklist + + + +* [ ] The problem has been defined and is well understood +* [ ] Who the design solution is for has been defined +* [ ] User goals and outcomes have been defined +* [ ] Supporting research has been reviewed and linked +* [ ] The product requirements have been defined, and the scope has been agreed upon +* [ ] Success metrics have been defined and agreed upon +* [ ] I, as the Product Designer, believe I have all the information I need to begin creating a design solution +* [ ] Move this issue to \~"workflow::ready for design" or ~"workflow::design" :tada: +* [ ] (Optional) Help improve this issue template, [view feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/519682) + +
+ +## Proposal + +
+See checklist reminder + +* [ ] Follow the [Product design process](https://handbook.gitlab.com/handbook/product/ux/product-designer/#ideate-and-iterate) +* [ ] [Start with a long-term vision](https://handbook.gitlab.com/handbook/values/#start-with-a-long-term-vision) +* [ ] Remember to link your video walkthrough, prototypes, Figma project + +
+ +- :tv: Walkthrough +- :frame_photo: Design Solution Proposal +- ❖ Figma project → + +## Design breakdown + +Once the proposal is agreed upon, work with your team to break it down into buildable parts (MVC, Iteration 1, Iteration 2, etc... until fully built). + +
+See checklist + +* [ ] Design Vision broken down into MVC and follow-up iterations based on their ability to stand alone and provide value to the user +* [ ] Create MVC and other necessary Iteration 1, Iteration 2... issues and add them as Linked items to this issue + * [ ] Include all necessary requirements, and specs needed to create the designs for each broken down issue + +
+ +/label ~"UX" ~"UX Template Applied" ~"workflow::start" \ No newline at end of file diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml index c6dc02a8151..f9842efc6e0 100644 --- a/.rubocop_todo/rspec/feature_category.yml +++ b/.rubocop_todo/rspec/feature_category.yml @@ -674,7 +674,6 @@ RSpec/FeatureCategory: - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_ci_builds_metric_spec.rb' - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_users_deployment_approvals_spec.rb' - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/epics_deepest_relationship_level_metric_spec.rb' - - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/license_management_jobs_metric_spec.rb' - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/protected_environment_approval_rules_required_approvals_average_metric_spec.rb' - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/protected_environments_required_approvals_average_metric_spec.rb' - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/user_cap_setting_enabled_metric_spec.rb' diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION index 5af311270d3..fb5104a0a59 100644 --- a/GITLAB_KAS_VERSION +++ b/GITLAB_KAS_VERSION @@ -1 +1 @@ -efced52dc4e4f9f202e32dc6239573d4aceb4d4e +e947561ab2240a27ca13c4339e2cdd70115fc3ff diff --git a/app/assets/javascripts/import/github/import_from_github_app.vue b/app/assets/javascripts/import/github/import_from_github_app.vue new file mode 100644 index 00000000000..c18b31daafc --- /dev/null +++ b/app/assets/javascripts/import/github/import_from_github_app.vue @@ -0,0 +1,204 @@ + + + diff --git a/app/assets/javascripts/import/github/index.js b/app/assets/javascripts/import/github/index.js new file mode 100644 index 00000000000..6ddeb359bfc --- /dev/null +++ b/app/assets/javascripts/import/github/index.js @@ -0,0 +1,23 @@ +import Vue from 'vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import ImportFromGithubApp from './import_from_github_app.vue'; + +export function initGitHubImportProjectForm() { + const el = document.getElementById('js-vue-import-github-project-app'); + + if (!el) { + return null; + } + + const { viewModel } = el.dataset; + const provide = JSON.parse(viewModel); + + return new Vue({ + el, + name: 'ImportFromGitHubRoot', + provide: convertObjectPropsToCamelCase(provide), + render(createElement) { + return createElement(ImportFromGithubApp); + }, + }); +} diff --git a/app/assets/javascripts/pages/import/github/new/index.js b/app/assets/javascripts/pages/import/github/new/index.js index bacd891336a..59aef0810f1 100644 --- a/app/assets/javascripts/pages/import/github/new/index.js +++ b/app/assets/javascripts/pages/import/github/new/index.js @@ -1,3 +1,8 @@ +import { initGitHubImportProjectForm } from '~/import/github'; import { initPersonalAccessTokenFormValidation } from './init_personal_access_token_form_validation'; -initPersonalAccessTokenFormValidation(); +if (gon.features.newProjectCreationForm) { + initGitHubImportProjectForm(); +} else { + initPersonalAccessTokenFormValidation(); +} diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index f35449a52af..9da9fdf3fbc 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -183,7 +183,6 @@ module Ci scope :with_live_trace, -> { where_exists(Ci::BuildTraceChunk.scoped_build) } scope :with_stale_live_trace, -> { with_live_trace.finished_before(12.hours.ago) } scope :finished_before, ->(date) { finished.where('finished_at < ?', date) } - scope :license_management_jobs, -> { where(name: %i[license_management license_scanning]) } # handle license rename https://gitlab.com/gitlab-org/gitlab/issues/8911 # WARNING: This scope could lead to performance implications for large size of tables `ci_builds` and ci_runners`. # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123131 scope :with_runner_type, ->(runner_type) { joins(:runner).where(runner: { runner_type: runner_type }) } @@ -1343,7 +1342,14 @@ module Ci end def track_ci_build_created_event - Gitlab::InternalEvents.track_event('create_ci_build', project: project, user: user) + Gitlab::InternalEvents.track_event( + 'create_ci_build', + project: project, + user: user, + additional_properties: { + property: name + } + ) end def partition_id_prefix_in_16_bit_encode diff --git a/app/models/concerns/has_user_type.rb b/app/models/concerns/has_user_type.rb index 2aba5d156e1..204dbda8013 100644 --- a/app/models/concerns/has_user_type.rb +++ b/app/models/concerns/has_user_type.rb @@ -21,8 +21,7 @@ module HasUserType llm_bot: 14, placeholder: 15, duo_code_review_bot: 16, - import_user: 17, - ci_pipeline_bot: 18 + import_user: 17 }.with_indifferent_access.freeze BOT_USER_TYPES = %w[ @@ -39,7 +38,6 @@ module HasUserType service_account llm_bot duo_code_review_bot - ci_pipeline_bot ].freeze # `service_account` allows instance/namespaces to configure a user for external integrations/automations diff --git a/app/models/repository.rb b/app/models/repository.rb index 4663902be13..4fc8493be5a 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1260,7 +1260,7 @@ class Repository def get_patch_id(old_revision, new_revision) raw_repository.get_patch_id(old_revision, new_revision) - rescue Gitlab::Git::CommandError, Gitlab::Git::Repository::NoRepository => e + rescue Gitlab::Git::CommandError, Gitlab::Git::Repository::NoRepository, Gitlab::Git::CommandTimedOut => e # This is expected when there are no differences between the old_revision and the new_revision. # It's not ideal, but is simpler to handle this here than making breaking changes to gitaly. return if e.message.match?(/no difference between old and new revision./) diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml index a2e49cf9218..0412ec85603 100644 --- a/app/views/import/github/new.html.haml +++ b/app/views/import/github/new.html.haml @@ -3,50 +3,61 @@ - header_title _("New project"), new_project_path - add_to_breadcrumbs s_('ProjectsNew|Import project'), new_project_path(anchor: 'import_project') -= render ::Layouts::PageHeadingComponent.new('') do |c| - - c.with_heading do - .gl-flex.gl-gap-3.gl-items-center - = sprite_icon('github', size: 32) - = title - - c.with_description do - = import_github_authorize_message - - if !has_ci_cd_only_params? - .gl-mt-5 - - if github_import_configured? - = render Pajamas::ButtonComponent.new(variant: :confirm, - href: status_import_github_path(namespace_id: params[:namespace_id]), - icon: 'github') do - = title - - else - = render Pajamas::AlertComponent.new(variant: :info, dismissible: false) do |c| - - c.with_body do - = import_configure_github_admin_message +- if Feature.enabled?(:new_project_creation_form, @user) + #js-vue-import-github-project-app{ data: { view_model: Gitlab::Json.generate({ + back_button_path: new_project_path(anchor: 'import_project'), + namespace_id: namespace_id_from(params), + message_admin: import_configure_github_admin_message, + is_ci_cd_only: has_ci_cd_only_params?, + is_configured: github_import_configured?, + button_auth_href: status_import_github_path(namespace_id: params[:namespace_id]), + form_path: personal_access_token_import_github_path + }) } } +- else + = render ::Layouts::PageHeadingComponent.new('') do |c| + - c.with_heading do + .gl-flex.gl-gap-3.gl-items-center + = sprite_icon('github', size: 32) + = title + - c.with_description do + = import_github_authorize_message + - if !has_ci_cd_only_params? + .gl-mt-5 + - if github_import_configured? + = render Pajamas::ButtonComponent.new(variant: :confirm, + href: status_import_github_path(namespace_id: params[:namespace_id]), + icon: 'github') do + = title + - else + = render Pajamas::AlertComponent.new(variant: :info, dismissible: false) do |c| + - c.with_body do + = import_configure_github_admin_message -= form_tag personal_access_token_import_github_path, method: :post, class: 'gl-mt-3' do - .form-group.gl-form-group - %label.col-form-label{ for: 'personal_access_token' }= _('Personal access token') - = hidden_field_tag(:namespace_id, params[:namespace_id]) - = password_field_tag :personal_access_token, '', class: 'form-control gl-form-input js-import-github-pat-field', placeholder: _('e.g. %{token}') % { token: '8d3f016698e...' }, data: { testid: 'personal-access-token-field' } - %p.invalid-feedback.js-import-github-pat-validation - = _('Personal access token is required.') - %span.form-text.gl-text-subtle - - code_pair = tag_pair(tag.code, :code_start, :code_end) - - github_link_tag_pair = tag_pair(link_to('', 'https://github.com/settings/tokens', target: '_blank', rel: 'noopener noreferrer'), :link_start, :link_end) - = safe_format(s_('GithubImport|Create and provide your GitHub %{link_start}personal access token%{link_end}.'), github_link_tag_pair) - %br - = safe_format(s_('GithubImport|Use a classic GitHub personal access token with the following scopes:')) - %ul - - if has_ci_cd_only_params? - %li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to connect to.'), code_pair) - - else - %li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to import from.'), code_pair) - %li= safe_format(s_('GithubImporter|%{code_start}read:org%{code_end} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files.'), code_pair) - - docs_link = link_to('', help_page_path('user/project/import/github.md', anchor: 'use-a-github-personal-access-token'), target: '_blank', rel: 'noopener noreferrer') - - docs_link_tag_pair = tag_pair(docs_link, :link_start, :link_end) - = safe_format(s_('GithubImport|%{link_start}Learn more%{link_end}.'), docs_link_tag_pair) + = form_tag personal_access_token_import_github_path, method: :post, class: 'gl-mt-3' do + .form-group.gl-form-group + %label.col-form-label{ for: 'personal_access_token' }= _('Personal access token') + = hidden_field_tag(:namespace_id, params[:namespace_id]) + = password_field_tag :personal_access_token, '', class: 'form-control gl-form-input js-import-github-pat-field', placeholder: _('e.g. %{token}') % { token: '8d3f016698e...' }, data: { testid: 'personal-access-token-field' } + %p.invalid-feedback.js-import-github-pat-validation + = _('Personal access token is required.') + %span.form-text.gl-text-subtle + - code_pair = tag_pair(tag.code, :code_start, :code_end) + - github_link_tag_pair = tag_pair(link_to('', 'https://github.com/settings/tokens', target: '_blank', rel: 'noopener noreferrer'), :link_start, :link_end) + = safe_format(s_('GithubImport|Create and provide your GitHub %{link_start}personal access token%{link_end}.'), github_link_tag_pair) + %br + = safe_format(s_('GithubImport|Use a classic GitHub personal access token with the following scopes:')) + %ul + - if has_ci_cd_only_params? + %li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to connect to.'), code_pair) + - else + %li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to import from.'), code_pair) + %li= safe_format(s_('GithubImporter|%{code_start}read:org%{code_end} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files.'), code_pair) + - docs_link = link_to('', help_page_path('user/project/import/github.md', anchor: 'use-a-github-personal-access-token'), target: '_blank', rel: 'noopener noreferrer') + - docs_link_tag_pair = tag_pair(docs_link, :link_start, :link_end) + = safe_format(s_('GithubImport|%{link_start}Learn more%{link_end}.'), docs_link_tag_pair) - .gl-mt-5.gl-flex.gl-gap-3 - = render Pajamas::ButtonComponent.new(variant: :confirm, type: :submit, button_options: { class: 'js-import-github-pat-authenticate', data: { testid: 'authenticate-button' } }) do - = _('Authenticate') - = render Pajamas::ButtonComponent.new(href: new_project_path) do - = _('Cancel') + .gl-mt-5.gl-flex.gl-gap-3 + = render Pajamas::ButtonComponent.new(variant: :confirm, type: :submit, button_options: { class: 'js-import-github-pat-authenticate', data: { testid: 'authenticate-button' } }) do + = _('Authenticate') + = render Pajamas::ButtonComponent.new(href: new_project_path) do + = _('Cancel') diff --git a/config/events/create_ci_build.yml b/config/events/create_ci_build.yml index fe0f78adba5..6239cc2b1a1 100644 --- a/config/events/create_ci_build.yml +++ b/config/events/create_ci_build.yml @@ -13,3 +13,6 @@ tiers: - free - premium - ultimate +additional_properties: + property: + description: the name of the build diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index 440387b5829..0bb1b7d9b45 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -43626,7 +43626,6 @@ Possible types of user. | `ADMIN_BOT` | Admin bot. | | `ALERT_BOT` | Alert bot. | | `AUTOMATION_BOT` | Automation bot. | -| `CI_PIPELINE_BOT` | Ci pipeline bot. | | `DUO_CODE_REVIEW_BOT` | Duo code review bot. | | `GHOST` | Ghost. | | `HUMAN` | Human. | diff --git a/doc/development/gemfile.md b/doc/development/gemfile.md index 95ef4b6a21e..4c48db47b62 100644 --- a/doc/development/gemfile.md +++ b/doc/development/gemfile.md @@ -70,11 +70,13 @@ to be available in the RubyGems index. We want to minimize external build dependencies and build times. It's enforced by the RuboCop rule [`Cop/GemFetcher`](https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/blob/master/lib/rubocop/cop/gem_fetcher.rb). -## No gems for importers or integrations +## No gems that make HTTP calls for importers or integrations -Do not add gems for use in importers or integrations. For more information, see: +Do not add gems that make HTTP calls in importers or integrations. +In general, other gems are also strongly discouraged from these domains. +For more information, see: -- [Integration developer guide](integrations/_index.md#do-not-add-ruby-gems) +- [Integration development guidelines](integrations/_index.md#no-ruby-gems-that-make-http-calls) - [Principles of importer design](import/principles_of_importer_design.md#security) ## Review the new dependency for quality diff --git a/doc/development/import/principles_of_importer_design.md b/doc/development/import/principles_of_importer_design.md index 6a6e4630812..64bb03d6a3d 100644 --- a/doc/development/import/principles_of_importer_design.md +++ b/doc/development/import/principles_of_importer_design.md @@ -10,9 +10,9 @@ title: Principles of Importer Design - Uploaded files must be validated. Examples: - [`BulkImports::FileDownloadService`](https://gitlab.com/gitlab-org/gitlab/-/blob/cd4a880cbb2bc56b3a55f14c1d8370f4385319db/app/services/bulk_imports/file_download_service.rb#L38-46) - [`ImportExport::CommandLineUtil`](https://gitlab.com/gitlab-org/gitlab/blob/139690b3aeac69675119ce70f17f70bc1753de48/lib/gitlab/import_export/command_line_util.rb#L134) -- Importers must not add third-party Ruby gems to our `Gemfile`. - For more information, see the - [reasons](../integrations/_index.md#do-not-add-ruby-gems) given for integrations. +- Importers must not add third-party Ruby gems that make HTTP calls. + Importers use the same + [Ruby gem policy as for integrations](../integrations/_index.md#no-ruby-gems-that-make-http-calls), for more information about Ruby gem use for importers see that page. - All HTTP calls must use `Gitlab::HTTP`. `Gitlab::HTTP` ensures that [network settings](../../security/webhooks.md) of the instance are enforced and has other [security hardening](../../security/webhooks.md#enforce-dns-rebinding-attack-protection) measures. diff --git a/doc/development/integrations/_index.md b/doc/development/integrations/_index.md index c0fc935ef28..ad293424d31 100644 --- a/doc/development/integrations/_index.md +++ b/doc/development/integrations/_index.md @@ -185,21 +185,6 @@ attribute :wiki_page_events, default: false If an event attribute for an existing integration changes to `true`, this requires a data migration to back-fill the attribute value for old records. -#### Do not add Ruby gems - -GitLab integrations must not add Ruby gems to our `Gemfile`. -Instead, any required HTTP clients or small abstractions must be implemented in GitLab. - -Gems that wrap interactions with third-party services may look convenient at first glance, -but they offer minimal benefit compared to the costs involved: - -- They increase the potential surface area of security problems and the effort required to fix them. -- Often these gems make HTTP calls on your behalf. As integrations can make HTTP calls to remote - servers configured by users, it is critical that we - [fully control the network calls](#all-http-calls-must-use-gitlabhttp). -- There is a maintenance cost of managing gem upgrades. -- They can block us from using newer features. - ### Security requirements #### All HTTP calls must use `Gitlab::HTTP` @@ -225,6 +210,23 @@ def mask_configurable_channels? end ``` +## No Ruby gems that make HTTP calls + +GitLab integrations must not add Ruby gems that make HTTP calls. +Other gems that add small abstractions should also not be added. + +Certain utility-like gems from official sources, like `atlassian-jwt` gem can be used if required. + +Gems that wrap interactions with third-party services may look convenient at first glance, +but they offer minimal benefit compared to the costs involved: + +- They increase the potential surface area of security problems and the effort required to fix them. +- Often these gems make HTTP calls on your behalf. As integrations can make HTTP calls to remote + servers configured by users, it is critical that we + [fully control the network calls](#all-http-calls-must-use-gitlabhttp). +- There is a maintenance cost of managing gem upgrades. +- They can block us from using newer features. + ## Define configuration test Optionally, you can define a configuration test of an integration's settings. The test is executed from the integration form's **Test** button, and results are returned to the user. diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 8a7dcdaa6ea..e4b49bc10e7 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -85,14 +85,15 @@ module API agent = ::Clusters::AgentsFinder.new(user_project, current_user).execute.find_by_id(params[:cluster_agent_id]) bad_request!("cluster agent doesn't exist or cannot be associated with this environment") unless agent + params[:cluster_agent] = agent end - environment = user_project.environments.create(params) + response = ::Environments::CreateService.new(user_project, current_user, params).execute - if environment.persisted? - present environment, with: Entities::Environment, current_user: current_user + if response.success? + present response.payload[:environment], with: Entities::Environment, current_user: current_user else - render_validation_error!(environment) + render_api_error!(response.message, 400) end end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 1ff4e1692fb..d16ee796bea 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -283,7 +283,7 @@ module Gitlab end def bot_user_can_read_project?(user, project) - (user.project_bot? || user.ci_pipeline_bot? || user.service_account? || user.security_policy_bot?) && can_read_project?(user, project) + (user.project_bot? || user.service_account? || user.security_policy_bot?) && can_read_project?(user, project) end def valid_oauth_token?(token) diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index fdc5c97c5ba..fa1a34702b5 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -91,6 +91,7 @@ module Gitlab push_frontend_feature_flag(:work_items_view_preference, current_user) push_frontend_feature_flag(:search_button_top_right, current_user) push_frontend_feature_flag(:merge_request_dashboard, current_user, type: :wip) + push_frontend_feature_flag(:new_project_creation_form, current_user, type: :wip) end # Exposes the state of a feature flag to the frontend code. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4bc0d48a5c0..de73f3e295a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -26668,6 +26668,15 @@ msgstr "" msgid "Gitea import" msgstr "" +msgid "GithubImporter|%{codeStart}read:org%{codeEnd} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files." +msgstr "" + +msgid "GithubImporter|%{codeStart}repo%{codeEnd}: Used to display a list of your public and private repositories that are available to connect to." +msgstr "" + +msgid "GithubImporter|%{codeStart}repo%{codeEnd}: Used to display a list of your public and private repositories that are available to import from." +msgstr "" + msgid "GithubImporter|%{code_start}read:org%{code_end} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files." msgstr "" @@ -26755,9 +26764,15 @@ msgstr "" msgid "GithubImport|\"%{repository_name}\" size (%{repository_size}) is larger than the limit of %{limit}." msgstr "" +msgid "GithubImport|%{linkStart}Learn more%{linkEnd}." +msgstr "" + msgid "GithubImport|%{link_start}Learn more%{link_end}." msgstr "" +msgid "GithubImport|Create and provide your GitHub %{linkStart}personal access token%{linkEnd}." +msgstr "" + msgid "GithubImport|Create and provide your GitHub %{link_start}personal access token%{link_end}." msgstr "" @@ -46236,6 +46251,12 @@ msgstr "" msgid "ProjectsNew|Analyze your source code for known security vulnerabilities." msgstr "" +msgid "ProjectsNew|Authenticate through GitHub" +msgstr "" + +msgid "ProjectsNew|Authenticate with GitHub" +msgstr "" + msgid "ProjectsNew|Available only for projects within groups" msgstr "" @@ -52853,7 +52874,7 @@ msgstr "" msgid "SecurityOrchestration|This %{namespaceType} does not contain any security policies." msgstr "" -msgid "SecurityOrchestration|This %{namespaceType} is not linked to a security policy project" +msgid "SecurityOrchestration|This %{namespaceType} is not linked to a security policy project. Either link it to an existing project or create a new policy, which will create a new project that you can use as a security policy project. For help, see %{linkStart}policies%{linkEnd}." msgstr "" msgid "SecurityOrchestration|This applies to following compliance frameworks:" @@ -62650,6 +62671,9 @@ msgstr "" msgid "Use one line per URI" msgstr "" +msgid "Use personal access token" +msgstr "" + msgid "Use primary email (%{email})" msgstr "" diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 0a736c933ca..4f42f2ba31d 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -136,10 +136,6 @@ FactoryBot.define do user_type { :llm_bot } end - trait :ci_pipeline_bot do - user_type { :ci_pipeline_bot } - end - trait :duo_code_review_bot do user_type { :duo_code_review_bot } end diff --git a/spec/frontend/import/github/import_from_github_app_spec.js b/spec/frontend/import/github/import_from_github_app_spec.js new file mode 100644 index 00000000000..effa88f8d70 --- /dev/null +++ b/spec/frontend/import/github/import_from_github_app_spec.js @@ -0,0 +1,58 @@ +import { GlAlert } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ImportFromGithubApp from '~/import/github/import_from_github_app.vue'; +import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue'; + +describe('Import from GitHub app', () => { + let wrapper; + + const createComponent = (provide = {}) => { + wrapper = shallowMountExtended(ImportFromGithubApp, { + provide: { + backButtonPath: 'https://gitlab.com', + namespaceId: '1', + messageAdmin: 'This is an admin alert.', + isCiCdOnly: false, + isConfigured: true, + buttonAuthHref: 'https://gitlab.com/submit', + formPath: 'https://gitlab.com/submit', + ...provide, + }, + }); + }; + + const findMultiStepForm = () => wrapper.findComponent(MultiStepFormTemplate); + const findGithubAuthButton = () => wrapper.findByTestId('github-auth-button'); + const findAlert = () => wrapper.findComponent(GlAlert); + + it('renders a form', () => { + createComponent(); + + expect(findMultiStepForm().exists()).toBe(true); + }); + + describe('not a ci/cd project', () => { + it('renders a button if github is configured', () => { + createComponent(); + + expect(findGithubAuthButton().exists()).toBe(true); + expect(findAlert().exists()).toBe(false); + }); + + it('renders an alert if github is not configured', () => { + createComponent({ isConfigured: false }); + + expect(findGithubAuthButton().exists()).toBe(false); + expect(findAlert().exists()).toBe(true); + }); + }); + + describe('is a ci/cd project', () => { + it('does not render a github auth button or alert', () => { + createComponent({ isCiCdOnly: true }); + + expect(findGithubAuthButton().exists()).toBe(false); + expect(findAlert().exists()).toBe(false); + }); + }); +}); diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index f05d2f4ca40..3b4be1732cd 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -158,14 +158,19 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def end context 'when running after_commit callbacks' do - it 'tracks creation event' do - expect(Gitlab::InternalEvents).to receive(:track_event).with( - 'create_ci_build', - project: project, - user: user - ) + let(:name) { 'test123' } - create(:ci_build, user: user, project: project) + subject(:create_ci_build) { create(:ci_build, user: user, project: project, name: name) } + + it 'tracks creation event' do + expect { create_ci_build } + .to trigger_internal_events('create_ci_build') + .with( + category: 'InternalEventTracking', + user: user, + project: project, + additional_properties: { property: name } + ) end end end @@ -360,26 +365,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def end end - describe '.license_management_jobs' do - subject { described_class.license_management_jobs } - - let!(:management_build) { create(:ci_build, :success, name: :license_management, pipeline: pipeline) } - let!(:scanning_build) { create(:ci_build, :success, name: :license_scanning, pipeline: pipeline) } - let!(:another_build) { create(:ci_build, :success, name: :another_type, pipeline: pipeline) } - - it 'returns license_scanning jobs' do - is_expected.to include(scanning_build) - end - - it 'returns license_management jobs' do - is_expected.to include(management_build) - end - - it 'doesnt return filtered out jobs' do - is_expected.not_to include(another_build) - end - end - describe '.finished_before' do subject { described_class.finished_before(date) } diff --git a/spec/models/concerns/has_user_type_spec.rb b/spec/models/concerns/has_user_type_spec.rb index fc619485d0a..8275929ad9f 100644 --- a/spec/models/concerns/has_user_type_spec.rb +++ b/spec/models/concerns/has_user_type_spec.rb @@ -14,7 +14,7 @@ RSpec.describe User, feature_category: :system_access do expect(described_class::USER_TYPES.keys) .to match_array(%w[human ghost alert_bot project_bot support_bot service_user security_bot visual_review_bot migration_bot automation_bot security_policy_bot admin_bot suggested_reviewers_bot - service_account llm_bot placeholder duo_code_review_bot import_user ci_pipeline_bot]) + service_account llm_bot placeholder duo_code_review_bot import_user]) expect(described_class::USER_TYPES).to include(*described_class::BOT_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::NON_INTERNAL_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::INTERNAL_USER_TYPES) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 893179dc801..24583107f13 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -4075,6 +4075,30 @@ RSpec.describe Repository, feature_category: :source_code_management do end end + context 'when a Gitlab::Git::CommandTimedOut is raised' do + before do + expect(repository.raw_repository) + .to receive(:get_patch_id).and_raise(Gitlab::Git::CommandTimedOut) + end + + it 'returns nil' do + expect(repository.get_patch_id('HEAD', 'f' * 40)).to be_nil + end + + it 'reports the exception' do + expect(Gitlab::ErrorTracking) + .to receive(:track_exception) + .with( + instance_of(Gitlab::Git::CommandTimedOut), + project_id: repository.project.id, + old_revision: 'HEAD', + new_revision: 'HEAD' + ) + + repository.get_patch_id('HEAD', 'HEAD') + end + end + context 'when a Gitlab::Git::Repository::NoRepository is raised' do before do expect(repository.raw_repository) diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index cbbe67defaf..6d88aeef259 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -158,73 +158,86 @@ RSpec.describe API::Environments, feature_category: :continuous_delivery do let_it_be(:cluster_agent) { create(:cluster_agent, project: project) } let_it_be(:foreign_cluster_agent) { create(:cluster_agent) } - it 'creates an environment with associated cluster agent' do - post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id } + context 'when user access authorization exists' do + let_it_be(:user_access_auth) { create(:agent_user_access_project_authorization, project: project, agent: cluster_agent) } - expect(response).to have_gitlab_http_status(:created) - expect(response).to match_response_schema('public_api/v4/environment') - expect(json_response['cluster_agent']).to be_present + it 'creates an environment with associated cluster agent' do + post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id } + + expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('public_api/v4/environment') + expect(json_response['cluster_agent']).to be_present + end + + it 'creates an environment with associated kubernetes namespace' do + post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id, kubernetes_namespace: 'flux-system' } + + expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('public_api/v4/environment') + expect(json_response['cluster_agent']).to be_present + expect(json_response['kubernetes_namespace']).to eq('flux-system') + end + + it 'creates an environment with associated flux resource path' do + post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id, kubernetes_namespace: 'flux-system', flux_resource_path: 'HelmRelease/flux-system' } + + expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('public_api/v4/environment') + expect(json_response['cluster_agent']).to be_present + expect(json_response['kubernetes_namespace']).to eq('flux-system') + expect(json_response['flux_resource_path']).to eq('HelmRelease/flux-system') + end + + it 'fails to create environment with kubernetes namespace but no cluster agent' do + post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", kubernetes_namespace: 'flux-system' } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq(['Kubernetes namespace cannot be set without a cluster agent']) + end + + it 'fails to create environment with flux resource path but no cluster agent and kubernetes namespace' do + post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", flux_resource_path: 'HelmRelease/flux-system' } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq(['Flux resource path cannot be set without a kubernetes namespace']) + end + + it 'fails to create environment with flux resource path but no cluster agent' do + post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", kubernetes_namespace: 'flux-system', flux_resource_path: 'HelmRelease/flux-system' } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq(['Kubernetes namespace cannot be set without a cluster agent']) + end + + it 'fails to create environment with cluster agent and flux resource path but no kubernetes namespace' do + post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent: cluster_agent.id, flux_resource_path: 'HelmRelease/flux-system' } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq(['Flux resource path cannot be set without a kubernetes namespace']) + end + + it 'fails to create environment with a non existing cluster agent' do + post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: non_existing_record_id } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq("400 Bad request - cluster agent doesn't exist or cannot be associated with this environment") + end + + it 'fails to create environment with a foreign cluster agent' do + post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: foreign_cluster_agent.id } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq("400 Bad request - cluster agent doesn't exist or cannot be associated with this environment") + end end - it 'creates an environment with associated kubernetes namespace' do - post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id, kubernetes_namespace: 'flux-system' } + context 'when user access authorization does not exist' do + it 'fails to create environment with associated cluster agent' do + post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id } - expect(response).to have_gitlab_http_status(:created) - expect(response).to match_response_schema('public_api/v4/environment') - expect(json_response['cluster_agent']).to be_present - expect(json_response['kubernetes_namespace']).to eq('flux-system') - end - - it 'creates an environment with associated flux resource path' do - post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id, kubernetes_namespace: 'flux-system', flux_resource_path: 'HelmRelease/flux-system' } - - expect(response).to have_gitlab_http_status(:created) - expect(response).to match_response_schema('public_api/v4/environment') - expect(json_response['cluster_agent']).to be_present - expect(json_response['kubernetes_namespace']).to eq('flux-system') - expect(json_response['flux_resource_path']).to eq('HelmRelease/flux-system') - end - - it 'fails to create environment with kubernetes namespace but no cluster agent' do - post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", kubernetes_namespace: 'flux-system' } - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']['kubernetes_namespace']).to eq(['cannot be set without a cluster agent']) - end - - it 'fails to create environment with flux resource path but no cluster agent and kubernetes namespace' do - post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", flux_resource_path: 'HelmRelease/flux-system' } - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']['flux_resource_path']).to eq(['cannot be set without a kubernetes namespace']) - end - - it 'fails to create environment with flux resource path but no cluster agent' do - post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", kubernetes_namespace: 'flux-system', flux_resource_path: 'HelmRelease/flux-system' } - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']['kubernetes_namespace']).to eq(['cannot be set without a cluster agent']) - end - - it 'fails to create environment with cluster agent and flux resource path but no kubernetes namespace' do - post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent: cluster_agent.id, flux_resource_path: 'HelmRelease/flux-system' } - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']['flux_resource_path']).to eq(['cannot be set without a kubernetes namespace']) - end - - it 'fails to create environment with a non existing cluster agent' do - post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: non_existing_record_id } - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq("400 Bad request - cluster agent doesn't exist or cannot be associated with this environment") - end - - it 'fails to create environment with a foreign cluster agent' do - post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: foreign_cluster_agent.id } - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq("400 Bad request - cluster agent doesn't exist or cannot be associated with this environment") + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq('Unauthorized to access the cluster agent in this project') + end end end diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index f363f8fee77..a9ea96f47df 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -1097,7 +1097,6 @@ - './ee/spec/lib/gitlab/usage/metrics/instrumentations/count_users_deployment_approvals_spec.rb' - './ee/spec/lib/gitlab/usage/metrics/instrumentations/historical_max_users_metrics_spec.rb' - './ee/spec/lib/gitlab/usage/metrics/instrumentations/licensee_metrics_spec.rb' -- './ee/spec/lib/gitlab/usage/metrics/instrumentations/license_management_jobs_metric_spec.rb' - './ee/spec/lib/gitlab/usage/metrics/instrumentations/license_metric_spec.rb' - './ee/spec/lib/gitlab/usage/metrics/instrumentations/protected_environment_approval_rules_required_approvals_average_metric_spec.rb' - './ee/spec/lib/gitlab/usage/metrics/instrumentations/protected_environments_required_approvals_average_metric_spec.rb'