diff options
32 files changed, 385 insertions, 74 deletions
diff --git a/app/assets/javascripts/diffs/components/collapsed_files_warning.vue b/app/assets/javascripts/diffs/components/collapsed_files_warning.vue index b7eea32e699..ebb6ec1e7c8 100644 --- a/app/assets/javascripts/diffs/components/collapsed_files_warning.vue +++ b/app/assets/javascripts/diffs/components/collapsed_files_warning.vue @@ -55,7 +55,7 @@ export default { {{ __('For a faster browsing experience, some files are collapsed by default.') }} </p> <template #actions> - <gl-button category="secondary" variant="warning" class="gl-alert-action" @click="expand"> + <gl-button class="gl-alert-action" @click="expand"> {{ __('Expand all files') }} </gl-button> </template> diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js index 2a58933cd64..98c24350f09 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js @@ -67,7 +67,7 @@ export const MISSING_MANIFEST_WARNING_TOOLTIP = s__( export const UPDATED_AT = s__('ContainerRegistry|Last updated %{time}'); -export const NOT_AVAILABLE_TEXT = __('N/A'); +export const NOT_AVAILABLE_TEXT = __('Not applicable.'); export const NOT_AVAILABLE_SIZE = __('0 bytes'); export const CLEANUP_UNSCHEDULED_TEXT = s__('ContainerRegistry|Cleanup will run %{time}'); diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/constants/details.js b/app/assets/javascripts/packages_and_registries/harbor_registry/constants/details.js index 2519f6b74a2..b62c51bd208 100644 --- a/app/assets/javascripts/packages_and_registries/harbor_registry/constants/details.js +++ b/app/assets/javascripts/packages_and_registries/harbor_registry/constants/details.js @@ -35,5 +35,5 @@ export const MISSING_MANIFEST_WARNING_TOOLTIP = s__( 'HarborRegistry|Invalid tag: missing manifest digest', ); -export const NOT_AVAILABLE_TEXT = __('N/A'); +export const NOT_AVAILABLE_TEXT = __('Not applicable.'); export const NOT_AVAILABLE_SIZE = __('0 bytes'); diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index fd869014570..3e50cb6ef07 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -40,10 +40,12 @@ export default async function initPipelineDetailsBundle() { } if (gon.features?.pipelineTabsVue) { + const { createAppOptions } = await import('ee_else_ce/pipelines/pipeline_tabs'); const { createPipelineTabs } = await import('./pipeline_tabs'); try { - createPipelineTabs(SELECTORS.PIPELINE_TABS, apolloProvider); + const appOptions = createAppOptions(SELECTORS.PIPELINE_TABS, apolloProvider); + createPipelineTabs(appOptions); } catch { createFlash({ message: __('An error occurred while loading a section of this page.'), diff --git a/app/assets/javascripts/pipelines/pipeline_tabs.js b/app/assets/javascripts/pipelines/pipeline_tabs.js index 3bf23d9d001..6d4f3762aea 100644 --- a/app/assets/javascripts/pipelines/pipeline_tabs.js +++ b/app/assets/javascripts/pipelines/pipeline_tabs.js @@ -8,12 +8,12 @@ import { getPipelineDefaultTab, reportToSentry } from './utils'; Vue.use(VueApollo); -const createPipelineTabs = (selector, apolloProvider) => { +export const createAppOptions = (selector, apolloProvider) => { const el = document.querySelector(selector); - if (!el) return; + if (!el) return null; - const { dataset } = document.querySelector(selector); + const { dataset } = el; const { canGenerateCodequalityReports, codequalityReportDownloadPath, @@ -29,15 +29,8 @@ const createPipelineTabs = (selector, apolloProvider) => { const defaultTabValue = getPipelineDefaultTab(window.location.href); - updateHistory({ - url: removeParams([TAB_QUERY_PARAM]), - title: document.title, - replace: true, - }); - - // eslint-disable-next-line no-new - new Vue({ - el: selector, + return { + el, components: { PipelineTabs, }, @@ -61,7 +54,18 @@ const createPipelineTabs = (selector, apolloProvider) => { render(createElement) { return createElement(PipelineTabs); }, - }); + }; }; -export { createPipelineTabs }; +export const createPipelineTabs = (options) => { + if (!options) return; + + updateHistory({ + url: removeParams([TAB_QUERY_PARAM]), + title: document.title, + replace: true, + }); + + // eslint-disable-next-line no-new + new Vue(options); +}; diff --git a/app/components/pajamas/card_component.html.haml b/app/components/pajamas/card_component.html.haml new file mode 100644 index 00000000000..007229cc69f --- /dev/null +++ b/app/components/pajamas/card_component.html.haml @@ -0,0 +1,9 @@ +.gl-card{ @card_options } + - if header? + .gl-card-header{ @header_options } + = header + .gl-card-body{ @body_options } + = body + - if footer? + .gl-card-footer{ @footer_options } + = footer diff --git a/app/components/pajamas/card_component.rb b/app/components/pajamas/card_component.rb new file mode 100644 index 00000000000..bcc71db1c34 --- /dev/null +++ b/app/components/pajamas/card_component.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# Renders a GlCard root element +module Pajamas + class CardComponent < Pajamas::Component + # @param [Hash] card_options + # @param [Hash] header_options + # @param [Hash] body_options + # @param [Hash] footer_options + def initialize(card_options: {}, header_options: {}, body_options: {}, footer_options: {}) + @card_options = card_options + @header_options = header_options + @body_options = body_options + @footer_options = footer_options + end + + renders_one :header + renders_one :body + renders_one :footer + end +end diff --git a/app/graphql/resolvers/clusters/agent_tokens_resolver.rb b/app/graphql/resolvers/clusters/agent_tokens_resolver.rb index 722fbab3bb7..9740bc6bb6a 100644 --- a/app/graphql/resolvers/clusters/agent_tokens_resolver.rb +++ b/app/graphql/resolvers/clusters/agent_tokens_resolver.rb @@ -16,7 +16,7 @@ module Resolvers def resolve(**args) return ::Clusters::AgentToken.none unless can_read_agent_tokens? - tokens = agent.last_used_agent_tokens + tokens = agent.agent_tokens tokens = tokens.with_status(args[:status]) if args[:status].present? tokens diff --git a/app/graphql/resolvers/clusters/agents_resolver.rb b/app/graphql/resolvers/clusters/agents_resolver.rb index 5ad66ed7cdd..28618bef807 100644 --- a/app/graphql/resolvers/clusters/agents_resolver.rb +++ b/app/graphql/resolvers/clusters/agents_resolver.rb @@ -30,7 +30,7 @@ module Resolvers def preloads { activity_events: { activity_events: [:user, agent_token: :agent] }, - tokens: :last_used_agent_tokens + tokens: :agent_tokens } end end diff --git a/app/models/clusters/agent.rb b/app/models/clusters/agent.rb index 79fc2b58237..c77b57575ff 100644 --- a/app/models/clusters/agent.rb +++ b/app/models/clusters/agent.rb @@ -10,8 +10,7 @@ module Clusters belongs_to :created_by_user, class_name: 'User', optional: true belongs_to :project, class_name: '::Project' # Otherwise, it will load ::Clusters::Project - has_many :agent_tokens, class_name: 'Clusters::AgentToken' - has_many :last_used_agent_tokens, -> { order_last_used_at_desc }, class_name: 'Clusters::AgentToken', inverse_of: :agent + has_many :agent_tokens, -> { order_last_used_at_desc }, class_name: 'Clusters::AgentToken', inverse_of: :agent has_many :group_authorizations, class_name: 'Clusters::Agents::GroupAuthorization' has_many :authorized_groups, class_name: '::Group', through: :group_authorizations, source: :group diff --git a/app/models/work_item.rb b/app/models/work_item.rb index 557694da35a..84646daf59b 100644 --- a/app/models/work_item.rb +++ b/app/models/work_item.rb @@ -4,6 +4,13 @@ class WorkItem < Issue self.table_name = 'issues' self.inheritance_column = :_type_disabled + has_one :parent_link, class_name: '::WorkItems::ParentLink', foreign_key: :work_item_id + has_one :work_item_parent, through: :parent_link, class_name: 'WorkItem' + + has_many :child_links, class_name: '::WorkItems::ParentLink', foreign_key: :work_item_parent_id + has_many :work_item_children, through: :child_links, class_name: 'WorkItem', + foreign_key: :work_item_id, source: :work_item + def noteable_target_type_name 'issue' end diff --git a/app/models/work_items/parent_link.rb b/app/models/work_items/parent_link.rb new file mode 100644 index 00000000000..11367846b36 --- /dev/null +++ b/app/models/work_items/parent_link.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module WorkItems + class ParentLink < ApplicationRecord + self.table_name = 'work_item_parent_links' + + belongs_to :work_item + belongs_to :work_item_parent, class_name: 'WorkItem' + end +end diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 69033d274a2..75613827ed9 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -22,22 +22,24 @@ .admin-dashboard.gl-mt-3 .h3.gl-mb-5.gl-mt-0= _('Instance overview') .row + - component_params = { body_options: { class: 'gl-display-flex gl-justify-content-space-between gl-align-items-center gl-p-6' }, + footer_options: { class: 'gl-bg-transparent'} } .col-md-4.gl-mb-6 - .gl-card - .gl-card-body.d-flex.justify-content-between.align-items-center.gl-p-6 + = render Pajamas::CardComponent.new(**component_params) do |c| + = c.body do %span .d-flex.align-items-center = sprite_icon('project', size: 16, css_class: 'gl-text-gray-700') %h3.gl-m-0.gl-ml-3= approximate_count_with_delimiters(@counts, Project) .gl-mt-3.text-uppercase= s_('AdminArea|Projects') = link_to(s_('AdminArea|New project'), new_project_path, class: "btn gl-button btn-default") - .gl-card-footer.gl-bg-transparent + = c.footer do .d-flex.align-items-center = link_to(s_('AdminArea|View latest projects'), admin_projects_path) = sprite_icon('angle-right', size: 12, css_class: 'gl-text-gray-700 gl-ml-2') .col-md-4.gl-mb-6 - .gl-card - .gl-card-body.d-flex.justify-content-between.align-items-center.gl-p-6 + = render Pajamas::CardComponent.new(**component_params) do |c| + = c.body do %span .d-flex.align-items-center = sprite_icon('users', size: 16, css_class: 'gl-text-gray-700') @@ -54,20 +56,20 @@ = s_('AdminArea|Users') = link_to(s_('AdminArea|Users statistics'), admin_dashboard_stats_path, class: "text-capitalize gl-ml-2") = link_to(s_('AdminArea|New user'), new_admin_user_path, class: "btn gl-button btn-default") - .gl-card-footer.gl-bg-transparent + = c.footer do .d-flex.align-items-center = link_to(s_('AdminArea|View latest users'), admin_users_path) = sprite_icon('angle-right', size: 12, css_class: 'gl-text-gray-700 gl-ml-2') .col-md-4.gl-mb-6 - .gl-card - .gl-card-body.d-flex.justify-content-between.align-items-center.gl-p-6 + = render Pajamas::CardComponent.new(**component_params) do |c| + = c.body do %span .d-flex.align-items-center = sprite_icon('group', size: 16, css_class: 'gl-text-gray-700') %h3.gl-m-0.gl-ml-3= approximate_count_with_delimiters(@counts, Group) .gl-mt-3.text-uppercase= s_('AdminArea|Groups') = link_to(s_('AdminArea|New group'), new_admin_group_path, class: "btn gl-button btn-default") - .gl-card-footer.gl-bg-transparent + = c.footer do .d-flex.align-items-center = link_to(s_('AdminArea|View latest groups'), admin_groups_path) = sprite_icon('angle-right', size: 12, css_class: 'gl-text-gray-700 gl-ml-2') @@ -75,8 +77,8 @@ .col-md-4.gl-mb-6 #js-admin-statistics-container .col-md-4.gl-mb-6 - .gl-card - .gl-card-body + = render Pajamas::CardComponent.new do |c| + = c.body do %h4= s_('AdminArea|Features') = feature_entry(_('Sign up'), href: general_admin_application_settings_path(anchor: 'js-signup-settings'), @@ -114,8 +116,8 @@ href: admin_runners_path, enabled: Gitlab.config.gitlab_ci.shared_runners_enabled) .col-md-4.gl-mb-6 - .gl-card - .gl-card-body + = render Pajamas::CardComponent.new do |c| + = c.body do %h4 = s_('AdminArea|Components') - if show_version_check? @@ -171,8 +173,8 @@ = link_to _("Gitaly Servers"), admin_gitaly_servers_path .row .col-md-4.gl-mb-6 - .gl-card - .gl-card-body + = render Pajamas::CardComponent.new do |c| + = c.body do %h4= s_('AdminArea|Latest projects') - @projects.each do |project| .gl-display-flex.gl-py-3 @@ -181,8 +183,8 @@ %span.gl-white-space-nowrap.gl-text-right #{time_ago_with_tooltip(project.created_at)} .col-md-4.gl-mb-6 - .gl-card - .gl-card-body + = render Pajamas::CardComponent.new do |c| + = c.body do %h4= s_('AdminArea|Latest users') - @users.each do |user| .gl-display-flex.gl-py-3 @@ -192,8 +194,8 @@ %span.gl-white-space-nowrap.gl-text-right #{time_ago_with_tooltip(user.created_at)} .col-md-4.gl-mb-6 - .gl-card - .gl-card-body + = render Pajamas::CardComponent.new do |c| + = c.body do %h4= s_('AdminArea|Latest groups') - @groups.each do |group| .gl-display-flex.gl-py-3 diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml index 8f80c9fdc6c..2c46bf1e074 100644 --- a/app/views/profiles/chat_names/_chat_name.html.haml +++ b/app/views/profiles/chat_names/_chat_name.html.haml @@ -6,7 +6,7 @@ - if can?(current_user, :read_project, project) = link_to project.full_name, project_path(project) - else - .light= _('N/A') + .light= _('Not applicable.') %td %strong - if can?(current_user, :admin_project, project) diff --git a/config/metrics/counts_28d/20210916080405_promoted_issues.yml b/config/metrics/counts_28d/20210916080405_promoted_issues.yml index ec73682b1f3..27c9f01edbe 100644 --- a/config/metrics/counts_28d/20210916080405_promoted_issues.yml +++ b/config/metrics/counts_28d/20210916080405_promoted_issues.yml @@ -7,8 +7,10 @@ product_stage: growth product_group: group::product intelligence product_category: collection value_type: number -status: deprecated +status: removed milestone: "14.3" +milestone_removed: "15.1" +removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88113 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70485 time_frame: 28d data_source: database diff --git a/db/docs/work_item_parent_links.yml b/db/docs/work_item_parent_links.yml new file mode 100644 index 00000000000..f4b5cd20abb --- /dev/null +++ b/db/docs/work_item_parent_links.yml @@ -0,0 +1,10 @@ +--- +table_name: work_item_parent_links +classes: +- WorkItem +- WorkItems::ParentLink +feature_categories: +- team_planning +description: Persists link between work item and its parent. +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87283 +milestone: '15.1' diff --git a/db/migrate/20220511144946_add_work_item_parent_child_table.rb b/db/migrate/20220511144946_add_work_item_parent_child_table.rb new file mode 100644 index 00000000000..160dac78160 --- /dev/null +++ b/db/migrate/20220511144946_add_work_item_parent_child_table.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class AddWorkItemParentChildTable < Gitlab::Database::Migration[2.0] + def up + create_table :work_item_parent_links do |t| + t.references :work_item, + index: false, + unique: true, + foreign_key: { to_table: :issues, on_delete: :cascade }, + null: false + t.references :work_item_parent, + index: true, + foreign_key: { to_table: :issues, on_delete: :cascade }, + null: false + t.integer :relative_position + t.timestamps_with_timezone null: false + + t.index [:work_item_id, :work_item_parent_id], + unique: true, + name: :index_parent_links_on_work_item_id_and_work_item_parent_id + end + end + + def down + drop_table :work_item_parent_links + end +end diff --git a/db/schema_migrations/20220511144946 b/db/schema_migrations/20220511144946 new file mode 100644 index 00000000000..c443848a5fb --- /dev/null +++ b/db/schema_migrations/20220511144946 @@ -0,0 +1 @@ +a11b32eeb9269e70ab0457eea3fbd42520e15fa6c089b349e4f9655190678cff
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 91b8149e38c..f7530e9dbda 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -22308,6 +22308,24 @@ CREATE SEQUENCE wiki_page_slugs_id_seq ALTER SEQUENCE wiki_page_slugs_id_seq OWNED BY wiki_page_slugs.id; +CREATE TABLE work_item_parent_links ( + id bigint NOT NULL, + work_item_id bigint NOT NULL, + work_item_parent_id bigint NOT NULL, + relative_position integer, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL +); + +CREATE SEQUENCE work_item_parent_links_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE work_item_parent_links_id_seq OWNED BY work_item_parent_links.id; + CREATE TABLE work_item_types ( id bigint NOT NULL, base_type smallint DEFAULT 0 NOT NULL, @@ -23409,6 +23427,8 @@ ALTER TABLE ONLY wiki_page_meta ALTER COLUMN id SET DEFAULT nextval('wiki_page_m ALTER TABLE ONLY wiki_page_slugs ALTER COLUMN id SET DEFAULT nextval('wiki_page_slugs_id_seq'::regclass); +ALTER TABLE ONLY work_item_parent_links ALTER COLUMN id SET DEFAULT nextval('work_item_parent_links_id_seq'::regclass); + ALTER TABLE ONLY work_item_types ALTER COLUMN id SET DEFAULT nextval('work_item_types_id_seq'::regclass); ALTER TABLE ONLY x509_certificates ALTER COLUMN id SET DEFAULT nextval('x509_certificates_id_seq'::regclass); @@ -25701,6 +25721,9 @@ ALTER TABLE ONLY wiki_page_meta ALTER TABLE ONLY wiki_page_slugs ADD CONSTRAINT wiki_page_slugs_pkey PRIMARY KEY (id); +ALTER TABLE ONLY work_item_parent_links + ADD CONSTRAINT work_item_parent_links_pkey PRIMARY KEY (id); + ALTER TABLE ONLY work_item_types ADD CONSTRAINT work_item_types_pkey PRIMARY KEY (id); @@ -28803,6 +28826,8 @@ CREATE INDEX index_pages_domains_on_verified_at_and_enabled_until ON pages_domai CREATE INDEX index_pages_domains_on_wildcard ON pages_domains USING btree (wildcard); +CREATE UNIQUE INDEX index_parent_links_on_work_item_id_and_work_item_parent_id ON work_item_parent_links USING btree (work_item_id, work_item_parent_id); + CREATE INDEX index_partial_ci_builds_on_user_id_name_parser_features ON ci_builds USING btree (user_id, name) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('secret_detection'::character varying)::text]))); CREATE INDEX index_pat_on_user_id_and_expires_at ON personal_access_tokens USING btree (user_id, expires_at); @@ -29791,6 +29816,8 @@ CREATE UNIQUE INDEX index_wiki_page_slugs_on_slug_and_wiki_page_meta_id ON wiki_ CREATE INDEX index_wiki_page_slugs_on_wiki_page_meta_id ON wiki_page_slugs USING btree (wiki_page_meta_id); +CREATE INDEX index_work_item_parent_links_on_work_item_parent_id ON work_item_parent_links USING btree (work_item_parent_id); + CREATE INDEX index_x509_certificates_on_subject_key_identifier ON x509_certificates USING btree (subject_key_identifier); CREATE INDEX index_x509_certificates_on_x509_issuer_id ON x509_certificates USING btree (x509_issuer_id); @@ -32355,6 +32382,9 @@ ALTER TABLE ONLY service_desk_settings ALTER TABLE ONLY saml_group_links ADD CONSTRAINT fk_rails_22e312c530 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; +ALTER TABLE ONLY work_item_parent_links + ADD CONSTRAINT fk_rails_231dba8959 FOREIGN KEY (work_item_parent_id) REFERENCES issues(id) ON DELETE CASCADE; + ALTER TABLE ONLY dast_profiles ADD CONSTRAINT fk_rails_23cae5abe1 FOREIGN KEY (dast_scanner_profile_id) REFERENCES dast_scanner_profiles(id) ON DELETE CASCADE; @@ -32727,6 +32757,9 @@ ALTER TABLE ONLY approval_project_rules ALTER TABLE ONLY incident_management_oncall_participants ADD CONSTRAINT fk_rails_5fe86ea341 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; +ALTER TABLE ONLY work_item_parent_links + ADD CONSTRAINT fk_rails_601d5bec3a FOREIGN KEY (work_item_id) REFERENCES issues(id) ON DELETE CASCADE; + ALTER TABLE ONLY user_highest_roles ADD CONSTRAINT fk_rails_60f6c325a6 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; diff --git a/doc/api/environments.md b/doc/api/environments.md index a806d8a50dd..d67808bfd61 100644 --- a/doc/api/environments.md +++ b/doc/api/environments.md @@ -386,7 +386,7 @@ POST /projects/:id/environments/:environment_id/stop |------------------|----------------|----------|----------------------------------------------------------------------------------------------------------------| | `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user | | `environment_id` | integer | yes | The ID of the environment | -| `force` | boolean | no | Force environment to stop even when `on_stop` action fails | +| `force` | boolean | no | Force environment to stop without executing `on_stop` actions | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/environments/1/stop" diff --git a/doc/development/service_ping/implement.md b/doc/development/service_ping/implement.md index 8021b24461c..ad7ea3b9e07 100644 --- a/doc/development/service_ping/implement.md +++ b/doc/development/service_ping/implement.md @@ -652,9 +652,10 @@ We return fallback values in these cases: | Case | Value | |-----------------------------|-------| -| Deprecated Metric | -1000 | +| Deprecated Metric ([Removed with version 14.3](https://gitlab.com/gitlab-org/gitlab/-/issues/335894)) | -1000 | | Timeouts, general failures | -1 | | Standard errors in counters | -2 | +| Histogram metrics failure | { '-1' => -1 } | ## Test counters manually using your Rails console diff --git a/doc/user/usage_quotas.md b/doc/user/usage_quotas.md index fd658c96184..84c98a60917 100644 --- a/doc/user/usage_quotas.md +++ b/doc/user/usage_quotas.md @@ -32,7 +32,7 @@ You can view storage usage for your project or [namespace](../user/group/#namesp The statistics are displayed. Select any title to view details. The information on this page is updated every 90 minutes. -If your namespace shows `N/A`, push a commit to any project in the +If your namespace shows `'Not applicable.'`, push a commit to any project in the namespace to recalculate the storage. ## Storage usage statistics diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml index 48729f8dbd9..8e3801c382e 100644 --- a/lib/gitlab/database/gitlab_schemas.yml +++ b/lib/gitlab/database/gitlab_schemas.yml @@ -559,6 +559,7 @@ web_hook_logs: :gitlab_main web_hooks: :gitlab_main wiki_page_meta: :gitlab_main wiki_page_slugs: :gitlab_main +work_item_parent_links: :gitlab_main work_item_types: :gitlab_main x509_certificates: :gitlab_main x509_commit_signatures: :gitlab_main diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 7a17288e5e5..c64a6508d34 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -18,7 +18,6 @@ module Gitlab class UsageData - DEPRECATED_VALUE = -1000 MAX_GENERATION_TIME_FOR_SAAS = 40.hours CE_MEMOIZED_VALUES = %i( @@ -193,8 +192,7 @@ module Gitlab packages: count(::Packages::Package.where(monthly_time_range_db_params)), personal_snippets: count(PersonalSnippet.where(monthly_time_range_db_params)), project_snippets: count(ProjectSnippet.where(monthly_time_range_db_params)), - projects_with_alerts_created: distinct_count(::AlertManagement::Alert.where(monthly_time_range_db_params), :project_id), - promoted_issues: DEPRECATED_VALUE + projects_with_alerts_created: distinct_count(::AlertManagement::Alert.where(monthly_time_range_db_params), :project_id) }.tap do |data| data[:snippets] = add(data[:personal_snippets], data[:project_snippets]) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 94e8c4ff946..1928fb830f6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16508,7 +16508,7 @@ msgid_plural "%d shards selected" msgstr[0] "" msgstr[1] "" -msgid "Geo|%{boldStart}N/A%{boldEnd}: Geo does not verify this component yet. See the %{linkStart}data types we plan to support%{linkEnd}." +msgid "Geo|%{boldStart}Not applicable%{boldEnd}: Geo does not verify this component yet. See the %{linkStart}data types we plan to support%{linkEnd}." msgstr "" msgid "Geo|%{component} synced" @@ -25027,9 +25027,6 @@ msgstr "" msgid "My-Reaction" msgstr "" -msgid "N/A" -msgstr "" - msgid "Name" msgstr "" @@ -25813,6 +25810,9 @@ msgstr "" msgid "Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited." msgstr "" +msgid "Not applicable." +msgstr "" + msgid "Not available" msgstr "" diff --git a/spec/components/pajamas/card_component_spec.rb b/spec/components/pajamas/card_component_spec.rb new file mode 100644 index 00000000000..65522a9023f --- /dev/null +++ b/spec/components/pajamas/card_component_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true +require "spec_helper" + +RSpec.describe Pajamas::CardComponent, :aggregate_failures, type: :component do + let(:header) { 'Card header' } + let(:body) { 'Card body' } + let(:footer) { 'Card footer' } + + context 'slots' do + before do + render_inline described_class.new do |c| + c.header { header } + c.body { body } + c.footer { footer } + end + end + + it 'renders card header' do + expect(rendered_component).to have_content(header) + end + + it 'renders card body' do + expect(rendered_component).to have_content(body) + end + + it 'renders footer' do + expect(rendered_component).to have_content(footer) + end + end + + context 'with defaults' do + before do + render_inline described_class.new + end + + it 'does not have a header or footer' do + expect(rendered_component).not_to have_selector('.gl-card-header') + expect(rendered_component).not_to have_selector('.gl-card-footer') + end + + it 'renders the card and body' do + expect(rendered_component).to have_selector('.gl-card') + expect(rendered_component).to have_selector('.gl-card-body') + end + end + + context 'with custom options' do + before do + render_inline described_class.new( + card_options: { class: '_card_class_', data: { testid: '_card_testid_' } }, + header_options: { class: '_header_class_', data: { testid: '_header_testid_' } }, + body_options: { class: '_body_class_', data: { testid: '_body_testid_' } }, + footer_options: { class: '_footer_class_', data: { testid: '_footer_testid_' } }) do |c| + c.header { header } + c.body { body } + c.footer { footer } + end + end + + it 'renders card options' do + expect(rendered_component).to have_selector('._card_class_') + expect(rendered_component).to have_selector('[data-testid="_card_testid_"]') + end + + it 'renders header options' do + expect(rendered_component).to have_selector('._header_class_') + expect(rendered_component).to have_selector('[data-testid="_header_testid_"]') + end + + it 'renders body options' do + expect(rendered_component).to have_selector('._body_class_') + expect(rendered_component).to have_selector('[data-testid="_body_testid_"]') + end + + it 'renders footer options' do + expect(rendered_component).to have_selector('._footer_class_') + expect(rendered_component).to have_selector('[data-testid="_footer_testid_"]') + end + end +end diff --git a/spec/features/projects/container_registry_spec.rb b/spec/features/projects/container_registry_spec.rb index 17eb421191f..54685441300 100644 --- a/spec/features/projects/container_registry_spec.rb +++ b/spec/features/projects/container_registry_spec.rb @@ -122,7 +122,7 @@ RSpec.describe 'Container Registry', :js do it 'renders the tags list correctly' do expect(page).to have_content('latest') expect(page).to have_content('stable') - expect(page).to have_content('Digest: N/A') + expect(page).to have_content('Digest: Not applicable.') end end diff --git a/spec/frontend/pipelines/pipeline_tabs_spec.js b/spec/frontend/pipelines/pipeline_tabs_spec.js new file mode 100644 index 00000000000..2c58780aa1c --- /dev/null +++ b/spec/frontend/pipelines/pipeline_tabs_spec.js @@ -0,0 +1,91 @@ +import { createAppOptions, createPipelineTabs } from '~/pipelines/pipeline_tabs'; +import { updateHistory } from '~/lib/utils/url_utility'; + +jest.mock('~/lib/utils/url_utility', () => ({ + removeParams: () => 'gitlab.com', + updateHistory: jest.fn(), + joinPaths: () => {}, + setUrlFragment: () => {}, +})); + +jest.mock('~/pipelines/utils', () => ({ + getPipelineDefaultTab: () => '', +})); + +describe('~/pipelines/pipeline_tabs.js', () => { + describe('createAppOptions', () => { + const SELECTOR = 'SELECTOR'; + + let el; + + const createElement = () => { + el = document.createElement('div'); + el.id = SELECTOR; + el.dataset.canGenerateCodequalityReports = 'true'; + el.dataset.codequalityReportDownloadPath = 'codequalityReportDownloadPath'; + el.dataset.downloadablePathForReportType = 'downloadablePathForReportType'; + el.dataset.exposeSecurityDashboard = 'true'; + el.dataset.exposeLicenseScanningData = 'true'; + el.dataset.graphqlResourceEtag = 'graphqlResourceEtag'; + el.dataset.pipelineIid = '123'; + el.dataset.pipelineProjectPath = 'pipelineProjectPath'; + + document.body.appendChild(el); + }; + + afterEach(() => { + el = null; + }); + + it("extracts the properties from the element's dataset", () => { + createElement(); + const options = createAppOptions(`#${SELECTOR}`, null); + + expect(options).toMatchObject({ + el, + provide: { + canGenerateCodequalityReports: true, + codequalityReportDownloadPath: 'codequalityReportDownloadPath', + downloadablePathForReportType: 'downloadablePathForReportType', + exposeSecurityDashboard: true, + exposeLicenseScanningData: true, + graphqlResourceEtag: 'graphqlResourceEtag', + pipelineIid: '123', + pipelineProjectPath: 'pipelineProjectPath', + }, + }); + }); + + it('returns `null` if el does not exist', () => { + expect(createAppOptions('foo', null)).toBe(null); + }); + }); + + describe('createPipelineTabs', () => { + const title = 'Pipeline Tabs'; + + beforeAll(() => { + document.title = title; + }); + + afterAll(() => { + document.title = ''; + }); + + it('calls `updateHistory` with correct params', () => { + createPipelineTabs({}); + + expect(updateHistory).toHaveBeenCalledWith({ + title, + url: 'gitlab.com', + replace: true, + }); + }); + + it("returns early if options aren't provided", () => { + createPipelineTabs(); + + expect(updateHistory).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 7edec6d13f4..cf65f1e7d30 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -723,7 +723,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do expect(counts_monthly[:projects_with_alerts_created]).to eq(1) expect(counts_monthly[:projects]).to eq(1) expect(counts_monthly[:packages]).to eq(1) - expect(counts_monthly[:promoted_issues]).to eq(Gitlab::UsageData::DEPRECATED_VALUE) end end diff --git a/spec/models/clusters/agent_spec.rb b/spec/models/clusters/agent_spec.rb index f10e0cc8fa7..f97e89912c6 100644 --- a/spec/models/clusters/agent_spec.rb +++ b/spec/models/clusters/agent_spec.rb @@ -7,8 +7,7 @@ RSpec.describe Clusters::Agent do it { is_expected.to belong_to(:created_by_user).class_name('User').optional } it { is_expected.to belong_to(:project).class_name('::Project') } - it { is_expected.to have_many(:agent_tokens).class_name('Clusters::AgentToken') } - it { is_expected.to have_many(:last_used_agent_tokens).class_name('Clusters::AgentToken') } + it { is_expected.to have_many(:agent_tokens).class_name('Clusters::AgentToken').order(Clusters::AgentToken.arel_table[:last_used_at].desc.nulls_last) } it { is_expected.to have_many(:group_authorizations).class_name('Clusters::Agents::GroupAuthorization') } it { is_expected.to have_many(:authorized_groups).through(:group_authorizations) } it { is_expected.to have_many(:project_authorizations).class_name('Clusters::Agents::ProjectAuthorization') } @@ -117,23 +116,6 @@ RSpec.describe Clusters::Agent do end end - describe '#last_used_agent_tokens' do - let_it_be(:agent) { create(:cluster_agent) } - - subject { agent.last_used_agent_tokens } - - context 'agent has no tokens' do - it { is_expected.to be_empty } - end - - context 'agent has active and inactive tokens' do - let!(:active_token) { create(:cluster_agent_token, agent: agent, last_used_at: 1.minute.ago) } - let!(:inactive_token) { create(:cluster_agent_token, agent: agent, last_used_at: 2.hours.ago) } - - it { is_expected.to contain_exactly(active_token, inactive_token) } - end - end - describe '#activity_event_deletion_cutoff' do let_it_be(:agent) { create(:cluster_agent) } let_it_be(:event1) { create(:agent_activity_event, agent: agent, recorded_at: 1.hour.ago) } diff --git a/spec/models/work_item_spec.rb b/spec/models/work_item_spec.rb index e92ae746911..d126e81f1cd 100644 --- a/spec/models/work_item_spec.rb +++ b/spec/models/work_item_spec.rb @@ -3,6 +3,28 @@ require 'spec_helper' RSpec.describe WorkItem do + describe 'associations' do + it { is_expected.to have_one(:work_item_parent).class_name('WorkItem') } + + it 'has one `parent_link`' do + is_expected.to have_one(:parent_link) + .class_name('::WorkItems::ParentLink') + .with_foreign_key('work_item_id') + end + + it 'has many `work_item_children`' do + is_expected.to have_many(:work_item_children) + .class_name('WorkItem') + .with_foreign_key('work_item_id') + end + + it 'has many `child_links`' do + is_expected.to have_many(:child_links) + .class_name('::WorkItems::ParentLink') + .with_foreign_key('work_item_parent_id') + end + end + describe '#noteable_target_type_name' do it 'returns `issue` as the target name' do work_item = build(:work_item) diff --git a/spec/models/work_items/parent_link_spec.rb b/spec/models/work_items/parent_link_spec.rb new file mode 100644 index 00000000000..a66f6e54734 --- /dev/null +++ b/spec/models/work_items/parent_link_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::ParentLink do + describe 'associations' do + it { is_expected.to belong_to(:work_item) } + it { is_expected.to belong_to(:work_item_parent).class_name('WorkItem') } + end +end |