Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-03-01 21:11:54 +00:00
parent e69d400913
commit 7564f3f38f
85 changed files with 766 additions and 165 deletions

View File

@ -15,7 +15,7 @@ include:
gitlab_auth_token_variable_name: "PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE"
allure_job_name: "${QA_RUN_TYPE}"
- project: gitlab-org/quality/pipeline-common
ref: 8.4.3
ref: 8.4.4
file:
- /ci/base.gitlab-ci.yml
- /ci/knapsack-report.yml

View File

@ -4025,7 +4025,6 @@ RSpec/FeatureCategory:
- 'spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/search_counter_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/service_usage_data_counter_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter_spec.rb'

View File

@ -516,7 +516,6 @@ Style/ClassAndModuleChildren:
- 'lib/gitlab/usage_data_counters/designs_counter.rb'
- 'lib/gitlab/usage_data_counters/note_counter.rb'
- 'lib/gitlab/usage_data_counters/productivity_analytics_counter.rb'
- 'lib/gitlab/usage_data_counters/service_usage_data_counter.rb'
- 'lib/gitlab/usage_data_counters/snippet_counter.rb'
- 'lib/gitlab/usage_data_counters/source_code_counter.rb'
- 'lib/gitlab/usage_data_counters/wiki_page_counter.rb'

View File

@ -0,0 +1,92 @@
<script>
import { GlButton } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
import { DEFAULT_PER_PAGE } from '~/api';
import { createAlert } from '~/alert';
import organizationsQuery from '../graphql/queries/organizations.query.graphql';
export default {
name: 'AdminOrganizationsIndexApp',
i18n: {
pageTitle: __('Organizations'),
newOrganization: s__('Organization|New organization'),
errorMessage: s__(
'Organization|An error occurred loading organizations. Please refresh the page to try again.',
),
},
components: { GlButton, OrganizationsView },
inject: ['newOrganizationUrl'],
data() {
return {
organizations: {},
pagination: {
first: DEFAULT_PER_PAGE,
after: null,
last: null,
before: null,
},
};
},
apollo: {
organizations: {
query: organizationsQuery,
variables() {
return this.pagination;
},
update(data) {
return data.organizations;
},
error(error) {
createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
},
},
},
computed: {
showHeader() {
return this.loading || this.organizations.nodes?.length;
},
loading() {
return this.$apollo.queries.organizations.loading;
},
},
methods: {
onNext(endCursor) {
this.pagination = {
first: DEFAULT_PER_PAGE,
after: endCursor,
last: null,
before: null,
};
},
onPrev(startCursor) {
this.pagination = {
first: null,
after: null,
last: DEFAULT_PER_PAGE,
before: startCursor,
};
},
},
};
</script>
<template>
<div class="gl-py-6">
<div
v-if="showHeader"
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-mb-5"
>
<h1 class="gl-m-0 gl-font-size-h-display">{{ $options.i18n.pageTitle }}</h1>
<gl-button :href="newOrganizationUrl" variant="confirm">{{
$options.i18n.newOrganization
}}</gl-button>
</div>
<organizations-view
:organizations="organizations"
:loading="loading"
@next="onNext"
@prev="onPrev"
/>
</div>
</template>

View File

@ -0,0 +1,13 @@
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "~/organizations/shared/graphql/fragments/organization.fragment.graphql"
query getOrganizations($first: Int, $last: Int, $before: String, $after: String) {
organizations(first: $first, last: $last, before: $before, after: $after) {
nodes {
...Organization
}
pageInfo {
...PageInfo
}
}
}

View File

@ -0,0 +1,35 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import createDefaultClient from '~/lib/graphql';
import App from './components/app.vue';
export const initAdminOrganizationsIndex = () => {
const el = document.getElementById('js-admin-organizations-index');
if (!el) return false;
const {
dataset: { appData },
} = el;
const { newOrganizationUrl, organizationsEmptyStateSvgPath } = convertObjectPropsToCamelCase(
JSON.parse(appData),
);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
return new Vue({
el,
name: 'AdminOrganizationIndexRoot',
apolloProvider,
provide: {
newOrganizationUrl,
organizationsEmptyStateSvgPath,
},
render(createElement) {
return createElement(App);
},
});
};

View File

@ -31,6 +31,16 @@ export default {
headingDescription: s__(
'Runners|After you complete the steps below, an autoscaling fleet of runners is available to execute your CI/CD jobs in Google Cloud. Based on demand, a runner manager automatically creates temporary runners.',
),
beforeHeading: s__('Runners|Before you begin'),
permissionsText: s__(
'Runners|Ensure you have the %{linkStart}Owner%{linkEnd} IAM role on your Google Cloud project.',
),
billingLinkText: s__(
'Runners|Ensure that %{linkStart}billing is enabled for your Google Cloud project%{linkEnd}.',
),
preInstallText: s__(
'Runners|To follow the setup instructions, %{gcloudLinkStart}install the Google Cloud CLI%{gcloudLinkEnd} and %{terraformLinkStart}install Terraform%{terraformLinkEnd}.',
),
stepOneHeading: s__('Runners|Step 1: Specify environment'),
stepOneDescription: s__(
'Runners|Environment in Google Cloud where runners execute CI/CD jobs. Runners are created in temporary virtual machines based on demand.',
@ -86,8 +96,14 @@ export default {
},
alertBody: s__('Runners|To view the setup instructions, complete the previous form'),
invalidFormButton: s__('Runners|Go to first invalid form field'),
externalLink: __('(external link)'),
},
links: {
permissionsLink: 'https://cloud.google.com/iam/docs/understanding-roles#owner',
billingLink:
'https://cloud.google.com/billing/docs/how-to/verify-billing-enabled#confirm_billing_is_enabled_on_a_project',
gcloudLink: 'https://cloud.google.com/sdk/docs/install',
terraformLink: 'https://developer.hashicorp.com/terraform/install',
projectIdLink:
'https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects',
regionAndZonesLink: 'https://cloud.google.com/compute/docs/regions-zones',
@ -309,6 +325,51 @@ export default {
</div>
<hr />
<!-- start: before you begin -->
<div>
<h2 class="gl-font-lg">{{ $options.i18n.beforeHeading }}</h2>
<ul>
<li>
<gl-sprintf :message="$options.i18n.permissionsText">
<template #link="{ content }">
<gl-link :href="$options.links.permissionsLink" target="_blank">
{{ content }}
<gl-icon name="external-link" :aria-label="$options.i18n.externalLink" />
</gl-link>
</template>
</gl-sprintf>
</li>
<li>
<gl-sprintf :message="$options.i18n.billingLinkText">
<template #link="{ content }">
<gl-link :href="$options.links.billingLink" target="_blank">
{{ content }}
<gl-icon name="external-link" :aria-label="$options.i18n.externalLink" />
</gl-link>
</template>
</gl-sprintf>
</li>
<li>
<gl-sprintf :message="$options.i18n.preInstallText">
<template #gcloudLink="{ content }">
<gl-link :href="$options.links.gcloudLink" target="_blank">
{{ content }}
<gl-icon name="external-link" :aria-label="$options.i18n.externalLink" />
</gl-link>
</template>
<template #terraformLink="{ content }">
<gl-link :href="$options.links.terraformLink" target="_blank">
{{ content }}
<gl-icon name="external-link" :aria-label="$options.i18n.externalLink" />
</gl-link>
</template>
</gl-sprintf>
</li>
</ul>
</div>
<hr />
<!-- end: before you begin -->
<!-- start: step one -->
<div class="gl-pb-4">
<h2 class="gl-font-lg">{{ $options.i18n.stepOneHeading }}</h2>

View File

@ -4,7 +4,7 @@ import { EditorContent as TiptapEditorContent } from '@tiptap/vue-2';
import { __ } from '~/locale';
import { VARIANT_DANGER } from '~/alert';
import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue';
import { CONTENT_EDITOR_READY_EVENT } from '~/vue_shared/constants';
import { CONTENT_EDITOR_READY_EVENT, CONTENT_EDITOR_PASTE } from '~/vue_shared/constants';
import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import { createContentEditor } from '../services/create_content_editor';
@ -169,11 +169,16 @@ export default {
this.$emit('initialized');
await this.setSerializedContent(this.markdown);
markdownEditorEventHub.$emit(CONTENT_EDITOR_READY_EVENT);
markdownEditorEventHub.$on(CONTENT_EDITOR_PASTE, this.pasteContent);
},
beforeDestroy() {
markdownEditorEventHub.$off(CONTENT_EDITOR_PASTE, this.pasteContent);
this.contentEditor.dispose();
},
methods: {
pasteContent(content) {
this.contentEditor.tiptapEditor.chain().focus().pasteContent(content).run();
},
async setSerializedContent(markdown) {
this.notifyLoading();

View File

@ -3,8 +3,8 @@ import { GlButton } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { createAlert } from '~/alert';
import { DEFAULT_PER_PAGE } from '~/api';
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
import organizationsQuery from '../../shared/graphql/queries/organizations.query.graphql';
import OrganizationsView from './organizations_view.vue';
export default {
name: 'OrganizationsIndexApp',

View File

@ -1,10 +1,16 @@
<script>
import { GlAvatarLabeled, GlTruncateText } from '@gitlab/ui';
import { __ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import SafeHtml from '~/vue_shared/directives/safe_html';
export default {
name: 'OrganizationsListItem',
i18n: {
showMore: __('Show more'),
showLess: __('Show less'),
},
truncateTextToggleButtonProps: { class: 'gl-font-sm!' },
components: {
GlAvatarLabeled,
GlTruncateText,
@ -21,17 +27,20 @@ export default {
required: true,
},
},
avatarSize: { default: 32, md: 48 },
getIdFromGraphQLId,
methods: {
getIdFromGraphQLId,
},
};
</script>
<template>
<li class="organization-row gl-py-3 gl-border-b gl-display-flex gl-align-items-flex-start">
<li
class="organization-row gl-py-5 gl-px-5 gl-border-b gl-display-flex gl-align-items-flex-start"
>
<gl-avatar-labeled
:size="$options.avatarSize"
:size="48"
:src="organization.avatarUrl"
:entity-id="$options.getIdFromGraphQLId(organization.id)"
:entity-id="getIdFromGraphQLId(organization.id)"
:entity-name="organization.name"
:label="organization.name"
:label-link="organization.webUrl"
@ -41,12 +50,15 @@ export default {
v-if="organization.descriptionHtml"
:lines="2"
:mobile-lines="2"
class="gl-mt-2"
:show-more-text="$options.i18n.showMore"
:show-less-text="$options.i18n.showLess"
:toggle-button-props="$options.truncateTextToggleButtonProps"
class="gl-mt-2 gl-max-w-88"
>
<div
v-safe-html:[$options.safeHtmlConfig]="organization.descriptionHtml"
data-testid="organization-description-html"
class="organization-description gl-text-secondary gl-font-sm"
class="gl-text-secondary gl-font-sm md"
></div>
</gl-truncate-text>
</gl-avatar-labeled>

View File

@ -1,7 +1,7 @@
<script>
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import { s__ } from '~/locale';
import OrganizationsList from './organizations_list.vue';
import OrganizationsList from './list/organizations_list.vue';
export default {
name: 'OrganizationsView',

View File

@ -1,3 +1,4 @@
import initUsageQuotas from '~/usage_quotas';
import { GROUP_VIEW_TYPE } from '~/usage_quotas/constants';
initUsageQuotas();
initUsageQuotas(GROUP_VIEW_TYPE);

View File

@ -38,6 +38,7 @@ export default {
:key="tab.hash"
:title="tab.title"
:active="isActive(tab.hash)"
:data-testid="tab.hash"
@click="updateActiveTab(tab.hash)"
>
<component :is="tab.component" :data-testid="`${tab.hash}-app`" />

View File

@ -1,4 +1,5 @@
export const GROUP_VIEW_TYPE = 'group_view';
export const PROJECT_VIEW_TYPE = 'project_view';
export const PROFILE_VIEW_TYPE = 'profile_view';
export const STORAGE_TAB_METADATA_EL_SELECTOR = '#js-namespace-storage-app';

View File

@ -1,3 +1,3 @@
import { getStorageTabMetadata } from './storage/tab_metadata';
export const usageQuotasTabsMetadata = [getStorageTabMetadata()];
export const usageQuotasTabsMetadata = [getStorageTabMetadata()].filter(Boolean);

View File

@ -1,26 +1,32 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { usageQuotasTabsMetadata } from 'ee_else_ce/usage_quotas/group_view_metadata';
import { usageQuotasTabsMetadata as groupViewTabsMetadata } from 'ee_else_ce/usage_quotas/group_view_metadata';
import { usageQuotasTabsMetadata as profileViewTabsMetadata } from 'ee_else_ce/usage_quotas/profile_view_metadata';
import UsageQuotasApp from './components/usage_quotas_app.vue';
import { GROUP_VIEW_TYPE, PROFILE_VIEW_TYPE } from './constants';
Vue.use(VueApollo);
const getViewTabs = (viewType) => {
if (viewType === GROUP_VIEW_TYPE) {
return groupViewTabsMetadata;
}
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
if (viewType === PROFILE_VIEW_TYPE) {
return profileViewTabsMetadata;
}
export default () => {
return false;
};
export default (viewType) => {
const el = document.querySelector('#js-usage-quotas-view');
const tabs = getViewTabs(viewType);
if (!el) return false;
if (!el || !tabs) return false;
return new Vue({
el,
name: 'UsageQuotasView',
apolloProvider,
provide: {
tabs: usageQuotasTabsMetadata.filter(Boolean),
tabs,
},
render(createElement) {
return createElement(UsageQuotasApp);

View File

@ -0,0 +1,2 @@
// To Be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/385653
export const usageQuotasTabsMetadata = [];

View File

@ -100,4 +100,5 @@ export const EDITING_MODE_CONTENT_EDITOR = 'contentEditor';
export const CLEAR_AUTOSAVE_ENTRY_EVENT = 'markdown_clear_autosave_entry';
export const CONTENT_EDITOR_READY_EVENT = 'content_editor_ready';
export const CONTENT_EDITOR_PASTE = 'content_editor_paste';
export const MARKDOWN_EDITOR_READY_EVENT = 'markdown_editor_ready';

View File

@ -1,10 +1,5 @@
@import 'mixins_and_variables_and_functions';
// Modeled after projects.scss and groups.scss
.organization-row .organization-description p {
margin-bottom: 0;
}
.organization-root-path {
max-width: 40vw;
}

View File

@ -73,7 +73,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
format.json do
Gitlab::UsageDataCounters::ServiceUsageDataCounter.count(:download_payload_click)
Gitlab::InternalEvents.track_event('usage_data_download_payload_clicked', user: current_user)
render json: Gitlab::Json.dump(prerecorded_service_ping_data)
end

View File

@ -34,10 +34,7 @@ module Organizations
end
def organization_index_app_data
{
new_organization_url: new_organization_path,
organizations_empty_state_svg_path: image_path('illustrations/empty-state/empty-organizations-md.svg')
}
shared_organization_index_app_data
end
def organization_user_app_data(organization)
@ -66,6 +63,10 @@ module Organizations
}.to_json
end
def admin_organizations_index_app_data
shared_organization_index_app_data.to_json
end
private
def shared_groups_and_projects_app_data(organization)
@ -89,6 +90,13 @@ module Organizations
}
end
def shared_organization_index_app_data
{
new_organization_url: new_organization_path,
organizations_empty_state_svg_path: image_path('illustrations/empty-state/empty-organizations-md.svg')
}
end
# See UsersHelper#admin_users_paths for inspiration to this method
def organizations_users_paths
{

View File

@ -1 +1,3 @@
- page_title _('Organizations')
#js-admin-organizations-index{ data: { app_data: admin_organizations_index_app_data } }

View File

@ -5,13 +5,13 @@
.row
.col-sm-6{ data: { testid: 'group-usage-message' } }
%p.text-secondary
%p.gl-text-secondary
= safe_format(s_('UsageQuota|Usage of group resources across the projects in the %{strong_start}%{group_name}%{strong_end} group'),
{ group_name: @group.name },
tag_pair(tag.strong, :strong_start, :strong_end))
#js-usage-quotas-view
.gl-font-lg.gl--flex-center
%span.mr-1= s_('UsageQuota|Loading Usage Quotas tabs')
%span.gl-mr-1= s_('UsageQuota|Loading Usage Quotas tabs')
= render Pajamas::SpinnerComponent.new(inline: true, size: :md)
#js-namespace-storage-app{ data: storage_usage_app_data(@group) }

View File

@ -0,0 +1,18 @@
---
description: Counts Download Payload button clicks
category: InternalEventTracking
action: usage_data_download_payload_clicked
identifiers:
- user
product_section: analytics
product_stage: monitor
product_group: analytics_instrumentation
milestone: '16.10'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146118
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -8,11 +8,9 @@ product_group: analytics_instrumentation
value_type: number
status: active
time_frame: all
data_source: redis
instrumentation_class: RedisMetric
options:
prefix: service_usage_data
event: download_payload_click
data_source: internal_events
events:
- name: usage_data_download_payload_clicked
distribution:
- ce
- ee

View File

@ -7,4 +7,4 @@ feature_categories:
description: User mentions in commit messages
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19009
milestone: '12.6'
gitlab_schema: gitlab_main
gitlab_schema: gitlab_main_cell

View File

@ -7,4 +7,4 @@ feature_categories:
description: Stores diff notes positions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28113
milestone: '13.0'
gitlab_schema: gitlab_main
gitlab_schema: gitlab_main_cell

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddOrganizationIdToDependencyListExports < Gitlab::Database::Migration[2.2]
milestone '16.10'
def change
add_column :dependency_list_exports, :organization_id, :bigint
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class IndexOrganizationIdOnDependencyListExports < Gitlab::Database::Migration[2.2]
INDEX_NAME = 'index_dependency_list_exports_on_organization_id'
disable_ddl_transaction!
milestone '16.10'
def up
add_concurrent_index :dependency_list_exports, :organization_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :dependency_list_exports, INDEX_NAME
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddForeignKeyToOrganizationIdOnDependencyListExports < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '16.10'
def up
add_concurrent_foreign_key :dependency_list_exports, :organizations,
column: :organization_id,
on_delete: :cascade,
reverse_lock_order: true
end
def down
remove_foreign_key_if_exists :dependency_list_exports, column: :organization_id
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class UnscheduleOpenAiClearConvosCron < Gitlab::Database::Migration[2.2]
milestone '16.10'
disable_ddl_transaction!
def up
# This is to clean up the cron schedule for OpenAi::ClearConversationsWorker
# which was removed in
# https://gitlab.com/gitlab-org/gitlab/-/commit/8c24e145c14d64c62a5b4f6fe72726140457d9f1#be4e3233708096a83c31a905040cb84cc105703d_780_780
Sidekiq::Cron::Job.destroy('open_ai_clear_conversations')
sidekiq_remove_jobs(job_klasses: %w[OpenAi::ClearConversationsWorker])
end
def down
# No-op
end
end

View File

@ -0,0 +1 @@
ba88b86b5331d02fa7cfa4416fc2de5dbd451dc1fc03f5c6550f981b91d7ed96

View File

@ -0,0 +1 @@
d1c427131b7cddcab2891069a03af3c615e6126f3d7a3a5a8e6b30b7f1875d5e

View File

@ -0,0 +1 @@
0e53c0f6004aad8fee7323e5652dbabc9823561dd73ed017c7bfa34678ff9527

View File

@ -0,0 +1 @@
f16288a4771fe31dd8e10e3441facc9e79918a4f46f0b8910f5924fd0819b472

View File

@ -7759,6 +7759,7 @@ CREATE TABLE dependency_list_exports (
group_id bigint,
pipeline_id bigint,
export_type smallint DEFAULT 0 NOT NULL,
organization_id bigint,
CONSTRAINT check_fff6fc9b2f CHECK ((char_length(file) <= 255))
);
@ -24751,6 +24752,8 @@ CREATE UNIQUE INDEX index_dep_prox_manifests_on_group_id_file_name_and_status ON
CREATE INDEX index_dependency_list_exports_on_group_id ON dependency_list_exports USING btree (group_id);
CREATE INDEX index_dependency_list_exports_on_organization_id ON dependency_list_exports USING btree (organization_id);
CREATE INDEX index_dependency_list_exports_on_pipeline_id ON dependency_list_exports USING btree (pipeline_id);
CREATE INDEX index_dependency_list_exports_on_project_id ON dependency_list_exports USING btree (project_id);
@ -30189,6 +30192,9 @@ ALTER TABLE ONLY sbom_occurrences
ALTER TABLE ONLY issues
ADD CONSTRAINT fk_c34dd2b036 FOREIGN KEY (tmp_epic_id) REFERENCES epics(id) ON DELETE CASCADE;
ALTER TABLE ONLY dependency_list_exports
ADD CONSTRAINT fk_c348f16f10 FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ALTER TABLE ONLY user_group_callouts
ADD CONSTRAINT fk_c366e12ec3 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;

View File

@ -6,7 +6,7 @@
extends: existence
message: "Offerings should be comma-separated, without `and`, and must be capitalized. Example: `GitLab.com, Self-managed, GitLab Dedicated`."
link: https://docs.gitlab.com/ee/development/documentation/styleguide/#available-product-tier-badges
level: suggestion
level: error
scope: raw
raw:
- (?<=\n\*\*Offering:\*\* )(Dedicated|[^\n]*(SaaS|self-managed|Self-Managed|GitLab dedicated|and|GitLab Dedicated,|, GitLab\.com|, Dedicated))

View File

@ -20,6 +20,22 @@ self-managed instances. If you are an administrator, to access the Admin Area:
NOTE:
Only administrators can access the Admin Area.
## Administering organizations
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/419540) in GitLab 16.10 [with a flag](feature_flags.md) named `ui_for_organizations`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](feature_flags.md) named `ui_for_organizations`.
On GitLab.com and GitLab Dedicated, this feature is not available.
This feature is not ready for production use.
You can administer all organizations in the GitLab instance from the Admin Area's Organizations page.
To access the Organizations page:
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Organizations**.
## Administering projects
You can administer all projects in the GitLab instance from the Admin Area's Projects page.

View File

@ -43,6 +43,12 @@ NOTE:
Audit event types with the `-` scope are limited to either project, group, or instance and user audit events, but the
[information on the scope](https://gitlab.com/gitlab-org/gitlab/-/issues/438620) is still being added to the documentation.
### Ai framework
| Name | Description | Saved to database | Streamed | Introduced in | Scope |
|:------------|:------------|:------------------|:---------|:--------------|:--------------|
| [`duo_features_enabled_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145509) | GitLab Duo Features enabled setting on group or project changed| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.10](https://gitlab.com/gitlab-org/gitlab/-/issues/442485) | Group, Project |
### Audit events
| Name | Description | Saved to database | Streamed | Introduced in | Scope |

View File

@ -15126,7 +15126,6 @@ Represents the approval policy.
| <a id="approvalpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. |
| <a id="approvalpolicygroupapprovers"></a>`groupApprovers` **{warning-solid}** | [`[Group!]`](#group) | **Deprecated** in GitLab 16.5. Use `allGroupApprovers`. |
| <a id="approvalpolicyname"></a>`name` | [`String!`](#string) | Name of the policy. |
| <a id="approvalpolicypolicyscope"></a>`policyScope` | [`PolicyScope`](#policyscope) | Scope of the policy. |
| <a id="approvalpolicyroleapprovers"></a>`roleApprovers` | [`[MemberAccessLevelName!]`](#memberaccesslevelname) | Approvers of the role type. Users belonging to these role(s) alone will be approvers. |
| <a id="approvalpolicysource"></a>`source` | [`SecurityPolicySource!`](#securitypolicysource) | Source of the policy. Its fields depend on the source type. |
| <a id="approvalpolicyupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the policy YAML was last updated. |
@ -25211,16 +25210,6 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="policyapprovalgroupid"></a>`id` | [`ID!`](#id) | ID of the namespace. |
| <a id="policyapprovalgroupweburl"></a>`webUrl` | [`String!`](#string) | Web URL of the group. |
### `PolicyScope`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="policyscopecomplianceframeworks"></a>`complianceFrameworks` | [`ComplianceFrameworkConnection!`](#complianceframeworkconnection) | Compliance Frameworks linked to the policy. (see [Connections](#connections)) |
| <a id="policyscopeexcludingprojects"></a>`excludingProjects` | [`ProjectConnection!`](#projectconnection) | Projects to which the policy should not be applied to. (see [Connections](#connections)) |
| <a id="policyscopeincludingprojects"></a>`includingProjects` | [`ProjectConnection!`](#projectconnection) | Projects to which the policy should be applied to. (see [Connections](#connections)) |
### `PreviewBillableUserChange`
#### Fields
@ -27942,7 +27931,6 @@ Represents the scan execution policy.
| <a id="scanexecutionpolicyeditpath"></a>`editPath` | [`String!`](#string) | URL of policy edit page. |
| <a id="scanexecutionpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. |
| <a id="scanexecutionpolicyname"></a>`name` | [`String!`](#string) | Name of the policy. |
| <a id="scanexecutionpolicypolicyscope"></a>`policyScope` | [`PolicyScope`](#policyscope) | Scope of the policy. |
| <a id="scanexecutionpolicysource"></a>`source` | [`SecurityPolicySource!`](#securitypolicysource) | Source of the policy. Its fields depend on the source type. |
| <a id="scanexecutionpolicyupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the policy YAML was last updated. |
| <a id="scanexecutionpolicyyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. |
@ -27961,7 +27949,6 @@ Represents the scan result policy.
| <a id="scanresultpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. |
| <a id="scanresultpolicygroupapprovers"></a>`groupApprovers` **{warning-solid}** | [`[Group!]`](#group) | **Deprecated** in GitLab 16.5. Use `allGroupApprovers`. |
| <a id="scanresultpolicyname"></a>`name` | [`String!`](#string) | Name of the policy. |
| <a id="scanresultpolicypolicyscope"></a>`policyScope` | [`PolicyScope`](#policyscope) | Scope of the policy. |
| <a id="scanresultpolicyroleapprovers"></a>`roleApprovers` | [`[MemberAccessLevelName!]`](#memberaccesslevelname) | Approvers of the role type. Users belonging to these role(s) alone will be approvers. |
| <a id="scanresultpolicysource"></a>`source` | [`SecurityPolicySource!`](#securitypolicysource) | Source of the policy. Its fields depend on the source type. |
| <a id="scanresultpolicyupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the policy YAML was last updated. |
@ -34783,7 +34770,6 @@ Implementations:
| <a id="orchestrationpolicyeditpath"></a>`editPath` | [`String!`](#string) | URL of policy edit page. |
| <a id="orchestrationpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. |
| <a id="orchestrationpolicyname"></a>`name` | [`String!`](#string) | Name of the policy. |
| <a id="orchestrationpolicypolicyscope"></a>`policyScope` | [`PolicyScope`](#policyscope) | Scope of the policy. |
| <a id="orchestrationpolicyupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the policy YAML was last updated. |
| <a id="orchestrationpolicyyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. |

View File

@ -39,7 +39,7 @@ You must have at least the Maintainer role for the project.
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > Access Tokens**.
1. Create an access token with the following scopes: `read_api`, `read_observability`, `write_observability`. Be sure to save the access token value for later.
1. Select **Monitor > Tracing**, and then select **Enable**.
1. Select **Monitor > Metrics**, and then select **Enable**.
1. To configure your application to send GitLab metrics, set the following environment variables:
```shell

View File

@ -7,10 +7,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Use CI/CD to build your application
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
Use CI/CD to generate your application.
- [Getting started](../ci/index.md)

View File

@ -7,10 +7,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Manage your code
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
Store your source files in a repository and create merge requests. Write, debug, and collaborate on code.
- [Repositories](../user/project/repository/index.md)

View File

@ -7,10 +7,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Plan and track work
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
Plan your work by creating requirements, issues, and epics. Schedule work
with milestones and track your team's time. Learn how to save time with
quick actions, see how GitLab renders Markdown text, and learn how to

View File

@ -7,10 +7,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Deploy and release your application
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
Deployment is the step of the software delivery process when your
application gets deployed to its final, target infrastructure.

View File

@ -7,10 +7,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Set up your organization
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
Configure your organization and its users. Determine user roles
and give everyone access to the projects they need.

View File

@ -7,10 +7,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Analyze GitLab usage
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
GitLab provides different types of analytics insights at the instance, group, and project level.
These insights appear on the left sidebar, under [**Analyze**](../project/settings/project_features_permissions.md#disable-project-analytics).

View File

@ -7,10 +7,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Secure your application
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
GitLab can check your applications for security vulnerabilities.
- [Get started](get-started-security.md)

View File

@ -158,11 +158,11 @@ To see the full list of customizations available, see the Helm chart's [README](
##### Use the agent when KAS is behind a self-signed certificate
When [KAS](../../../../administration/clusters/kas.md) is behind a self-signed certificate,
you can set the value of `config.caCert` to the certificate. For example:
you can set the value of `config.kasCaCert` to the certificate. For example:
```shell
helm upgrade --install gitlab-agent gitlab/gitlab-agent \
--set-file config.caCert=my-custom-ca.pem
--set-file config.kasCaCert=my-custom-ca.pem
```
In this example, `my-custom-ca.pem` is the path to a local file that contains

View File

@ -12,23 +12,33 @@ DETAILS:
In GitLab, you use groups to manage one or more related projects at the same time.
You can use groups to manage permissions for your projects. If someone has access to
the group, they get access to all the projects in the group.
You can use groups to communicate with all group members and manage permissions for your projects.
If someone has access to the group, they get access to all the projects in the group.
You can also view all of the issues and merge requests for the projects in the group,
and view analytics that show the group's activity.
You can use groups to communicate with all of the members of the group at once.
and analytics about the group's activity.
For larger organizations, you can also create [subgroups](subgroups/index.md).
For more information about creating and managing your groups, see [Manage groups](manage.md).
## Group structure
The way to set up a group depends on your use cases, team size, and access requirements.
The following table describes the most common models of structuring groups.
| Model | Structure | Use cases |
| ----- | --------- | --------- |
| Simple | One group for all your projects. | Work in a small team or on specific solutions (for example, a marketing website) that require seamless collaboration and access to resources. |
| Team | Different groups or subgroups for different types of teams (for example, product and engineering). | Work in a large organization where some teams work autonomously or require centralized resources and limited access from external team members. |
| Client | One group for each client. | Provide custom solutions for multiple clients that require different resources and access levels. |
| Functionality | One group or subgroup for one type of functionality (for example, AI/ML). | Develop complex products where one functionality requires specific resources and collaboration of subject-matter experts. |
NOTE:
For self-managed customers it could be beneficial to create one single top-level group, so you can see an overview of
your entire organization. For more information about efforts to create an
organization view of all groups, [see epic 9266](https://gitlab.com/groups/gitlab-org/-/epics/9266).
A single top-level group provides insights in your entire organization via a complete
On self-managed GitLab, if you want to see an overview of your entire organization, you should create one top-level group.
For more information about efforts to create an organization view of all groups,
[see epic 9266](https://gitlab.com/groups/gitlab-org/-/epics/9266).
A single top-level group provides insights in your entire organization through a complete
[Security Dashboard and Center](../application_security/security_dashboard/index.md),
[Vulnerability](../application_security/vulnerability_report/index.md#vulnerability-report) and
[Compliance center](../compliance/compliance_center/index.md), and

View File

@ -9,9 +9,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/409913) in GitLab 16.1 [with a flag](../../administration/feature_flags.md) named `ui_for_organizations`. Disabled by default.
FLAG:
This feature is not ready for production use.
On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../../administration/feature_flags.md) named `ui_for_organizations`.
On GitLab.com and GitLab Dedicated, this feature is not available.
This feature is not ready for production use.
DISCLAIMER:
This page contains information related to upcoming products, features, and functionality.

View File

@ -7,10 +7,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Organize work with projects
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
In GitLab, you can create projects to host
your codebase. You can also use projects to track issues, plan work,
collaborate on code, and continuously build, test, and use

View File

@ -1,5 +1,5 @@
variables:
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.85.0'
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.86.0'
.dast-auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${DAST_AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -1,5 +1,5 @@
variables:
AUTO_DEPLOY_IMAGE_VERSION: 'v2.85.0'
AUTO_DEPLOY_IMAGE_VERSION: 'v2.86.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -1,5 +1,5 @@
variables:
AUTO_DEPLOY_IMAGE_VERSION: 'v2.85.0'
AUTO_DEPLOY_IMAGE_VERSION: 'v2.86.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -58,6 +58,7 @@ module Gitlab
state :finished, value: 3
state :failed, value: 4
state :finalizing, value: 5
state :finalized, value: 6
event :pause do
transition [:active, :paused] => :paused
@ -79,6 +80,10 @@ module Gitlab
transition any => :finalizing
end
event :confirm_finalize do
transition [:finalized, :finished] => :finalized
end
before_transition any => :finished do |migration|
migration.finished_at = Time.current if migration.respond_to?(:finished_at)
end

View File

@ -216,11 +216,19 @@ module Gitlab
return Gitlab::AppLogger.warn "Could not find batched background migration for the given configuration: #{configuration}" if migration.nil?
return if migration.finished?
return if migration.finalized?
if migration.finished?
migration.confirm_finalize!
return
end
finalize_batched_background_migration(job_class_name: job_class_name, table_name: table_name, column_name: column_name, job_arguments: job_arguments) if finalize
return if migration.reload.finished? # rubocop:disable Cop/ActiveRecordAssociationReload
if migration.reload.finished? # rubocop:disable Cop/ActiveRecordAssociationReload -- TODO: ensure that we have latest version of the batched migration
migration.confirm_finalize!
return
end
raise "Expected batched background migration for the given configuration to be marked as 'finished', " \
"but it is '#{migration.status_name}':" \

View File

@ -10,7 +10,6 @@ module Gitlab
KubernetesAgentCounter,
NoteCounter,
SearchCounter,
ServiceUsageDataCounter,
WebIdeCounter,
WikiPageCounter,
SnippetCounter,

View File

@ -1,8 +0,0 @@
# frozen_string_literal: true
module Gitlab::UsageDataCounters
class ServiceUsageDataCounter < BaseCounter
KNOWN_EVENTS = %w[download_payload_click].freeze
PREFIX = 'service_usage_data'
end
end

View File

@ -7,4 +7,5 @@
# NEW_KEY_2: LEGACY_KEY_2
# ...
#
'{event_counters}_usage_data_download_payload_clicked': USAGE_SERVICE_USAGE_DATA_DOWNLOAD_PAYLOAD_CLICK
'{event_counters}_web_ide_viewed': WEB_IDE_VIEWS_COUNT

View File

@ -34800,6 +34800,9 @@ msgstr ""
msgid "Organization|An error occurred deleting the project. Please refresh the page to try again."
msgstr ""
msgid "Organization|An error occurred loading organizations. Please refresh the page to try again."
msgstr ""
msgid "Organization|An error occurred loading the groups. Please refresh the page to try again."
msgstr ""
@ -42752,6 +42755,9 @@ msgstr ""
msgid "Runners|Available to all projects and subgroups in the group"
msgstr ""
msgid "Runners|Before you begin"
msgstr ""
msgid "Runners|Can run untagged jobs"
msgstr ""
@ -42868,6 +42874,12 @@ msgstr ""
msgid "Runners|Enable stale runner cleanup?"
msgstr ""
msgid "Runners|Ensure that %{linkStart}billing is enabled for your Google Cloud project%{linkEnd}."
msgstr ""
msgid "Runners|Ensure you have the %{linkStart}Owner%{linkEnd} IAM role on your Google Cloud project."
msgstr ""
msgid "Runners|Enter the job timeout in seconds. Must be a minimum of 600 seconds."
msgstr ""
@ -43464,6 +43476,9 @@ msgstr ""
msgid "Runners|This step creates the required infrastructure in Google Cloud, installs GitLab Runner, and registers it to this GitLab project. "
msgstr ""
msgid "Runners|To follow the setup instructions, %{gcloudLinkStart}install the Google Cloud CLI%{gcloudLinkEnd} and %{terraformLinkStart}install Terraform%{terraformLinkEnd}."
msgstr ""
msgid "Runners|To improve security, use a dedicated project for CI/CD, separate from resources and identity management projects. %{linkStart}Wheres my project ID in Google Cloud?%{linkEnd}"
msgstr ""

View File

@ -59,7 +59,7 @@
"@gitlab/cluster-client": "^2.1.0",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.3.0",
"@gitlab/svgs": "3.86.0",
"@gitlab/svgs": "3.88.0",
"@gitlab/ui": "77.1.0",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "^0.0.1-dev-20240226152102",

View File

@ -6,7 +6,7 @@ gem 'gitlab-qa', '~> 14', '>= 14.2.1', require: 'gitlab/qa'
gem 'gitlab_quality-test_tooling', '~> 1.11.0', require: false
gem 'gitlab-utils', path: '../gems/gitlab-utils'
gem 'activesupport', '~> 7.0.8.1' # This should stay in sync with the root's Gemfile
gem 'allure-rspec', '~> 2.24.0'
gem 'allure-rspec', '~> 2.24.1'
gem 'capybara', '~> 3.40.0'
gem 'capybara-screenshot', '~> 1.0.26'
gem 'rake', '~> 13', '>= 13.1.0'

View File

@ -29,10 +29,10 @@ GEM
rack-test (>= 1.1.0, < 2.0)
rest-client (>= 2.0.2, < 3.0)
rspec (~> 3.8)
allure-rspec (2.24.0)
allure-ruby-commons (= 2.24.0)
allure-rspec (2.24.1)
allure-ruby-commons (= 2.24.1)
rspec-core (>= 3.8, < 4)
allure-ruby-commons (2.24.0)
allure-ruby-commons (2.24.1)
mime-types (>= 3.3, < 4)
require_all (>= 2, < 4)
rspec-expectations (~> 3.12)
@ -347,7 +347,7 @@ PLATFORMS
DEPENDENCIES
activesupport (~> 7.0.8.1)
airborne (~> 0.3.7)
allure-rspec (~> 2.24.0)
allure-rspec (~> 2.24.1)
capybara (~> 3.40.0)
capybara-screenshot (~> 1.0.26)
chemlab (~> 0.11, >= 0.11.1)

View File

@ -120,14 +120,21 @@ RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_set
end
describe 'usage data counter' do
let(:counter) { Gitlab::UsageDataCounters::ServiceUsageDataCounter }
it_behaves_like 'internal event tracking' do
let(:event) { 'usage_data_download_payload_clicked' }
let(:user) { admin }
let(:project) { nil }
let(:namespace) { nil }
it 'incremented when json generated' do
expect { get :usage_data, format: :json }.to change { counter.read(:download_payload_click) }.by(1)
subject(:track_event) { get :usage_data, format: :json }
end
it 'not incremented when html format requested' do
expect { get :usage_data }.not_to change { counter.read(:download_payload_click) }
context 'with html format requested' do
it 'not incremented when html format requested' do
expect(Gitlab::InternalEvents).not_to receive(:track_event)
get :usage_data, format: :html
end
end
end
end

View File

@ -0,0 +1,214 @@
import { GlButton } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import { DEFAULT_PER_PAGE } from '~/api';
import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data';
import organizationsQuery from '~/admin/organizations/index/graphql/queries/organizations.query.graphql';
import OrganizationsIndexApp from '~/admin/organizations/index/components/app.vue';
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
import { MOCK_NEW_ORG_URL } from 'jest/organizations/shared/mock_data';
jest.mock('~/alert');
Vue.use(VueApollo);
describe('AdminOrganizationsIndexApp', () => {
let wrapper;
let mockApollo;
const organizations = {
nodes,
pageInfo,
};
const organizationEmpty = {
nodes: [],
pageInfo: pageInfoEmpty,
};
const successHandler = jest.fn().mockResolvedValue({
data: {
organizations,
},
});
const createComponent = (handler = successHandler) => {
mockApollo = createMockApollo([[organizationsQuery, handler]]);
wrapper = shallowMountExtended(OrganizationsIndexApp, {
apolloProvider: mockApollo,
provide: {
newOrganizationUrl: MOCK_NEW_ORG_URL,
},
});
};
afterEach(() => {
mockApollo = null;
});
// Finders
const findOrganizationHeaderText = () => wrapper.findByRole('heading', { name: 'Organizations' });
const findNewOrganizationButton = () => wrapper.findComponent(GlButton);
const findOrganizationsView = () => wrapper.findComponent(OrganizationsView);
// Assertions
const itRendersHeaderText = () => {
it('renders the header text', () => {
expect(findOrganizationHeaderText().exists()).toBe(true);
});
};
const itRendersNewOrganizationButton = () => {
it('render new organization button with correct link', () => {
expect(findNewOrganizationButton().attributes('href')).toBe(MOCK_NEW_ORG_URL);
});
};
const itDoesNotRenderErrorMessage = () => {
it('does not render an error message', () => {
expect(createAlert).not.toHaveBeenCalled();
});
};
const itDoesNotRenderHeaderText = () => {
it('does not render the header text', () => {
expect(findOrganizationHeaderText().exists()).toBe(false);
});
};
const itDoesNotRenderNewOrganizationButton = () => {
it('does not render new organization button', () => {
expect(findNewOrganizationButton().exists()).toBe(false);
});
};
describe('when API call is loading', () => {
beforeEach(() => {
createComponent(jest.fn().mockReturnValue(new Promise(() => {})));
});
itRendersHeaderText();
itRendersNewOrganizationButton();
itDoesNotRenderErrorMessage();
it('renders the organizations view with loading prop set to true', () => {
expect(findOrganizationsView().props('loading')).toBe(true);
});
});
describe('when API call is successful', () => {
beforeEach(async () => {
createComponent();
await waitForPromises();
});
itRendersHeaderText();
itRendersNewOrganizationButton();
itDoesNotRenderErrorMessage();
it('passes organizations to view component', () => {
expect(findOrganizationsView().props()).toMatchObject({
loading: false,
organizations,
});
});
});
describe('when API call is successful and returns no organizations', () => {
beforeEach(async () => {
createComponent(
jest.fn().mockResolvedValue({
data: {
organizations: organizationEmpty,
},
}),
);
await waitForPromises();
});
itDoesNotRenderHeaderText();
itDoesNotRenderNewOrganizationButton();
itDoesNotRenderErrorMessage();
it('renders view component with correct organizations and loading props', () => {
expect(findOrganizationsView().props()).toMatchObject({
loading: false,
organizations: organizationEmpty,
});
});
});
describe('when API call is not successful', () => {
const error = new Error();
beforeEach(async () => {
createComponent(jest.fn().mockRejectedValue(error));
await waitForPromises();
});
itDoesNotRenderHeaderText();
itDoesNotRenderNewOrganizationButton();
it('renders view component with correct organizations and loading props', () => {
expect(findOrganizationsView().props()).toMatchObject({
loading: false,
organizations: {},
});
});
it('renders error message', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'An error occurred loading organizations. Please refresh the page to try again.',
error,
captureError: true,
});
});
});
describe('when view component emits `next` event', () => {
const endCursor = 'mockEndCursor';
beforeEach(async () => {
createComponent();
await waitForPromises();
});
it('calls GraphQL query with correct pageInfo variables', async () => {
findOrganizationsView().vm.$emit('next', endCursor);
await waitForPromises();
expect(successHandler).toHaveBeenCalledWith({
first: DEFAULT_PER_PAGE,
after: endCursor,
last: null,
before: null,
});
});
});
describe('when view component emits `prev` event', () => {
const startCursor = 'mockStartCursor';
beforeEach(async () => {
createComponent();
await waitForPromises();
});
it('calls GraphQL query with correct pageInfo variables', async () => {
findOrganizationsView().vm.$emit('prev', startCursor);
await waitForPromises();
expect(successHandler).toHaveBeenCalledWith({
first: null,
after: null,
last: DEFAULT_PER_PAGE,
before: startCursor,
});
});
});
});

View File

@ -346,7 +346,7 @@ export const runnerInstallHelpPage = 'https://docs.example.com/runner/install/';
export const googleCloudRunnerProvisionResponse = {
__typename: 'Project',
id: 'gid://gitlab/Project/23',
id: 'gid://gitlab/Project/1',
runnerCloudProvisioning: {
__typename: 'CiRunnerGoogleCloudProvisioning',
projectSetupShellScript: '#!/bin/bash echo "hello world!"',
@ -355,15 +355,13 @@ export const googleCloudRunnerProvisionResponse = {
__typename: 'CiRunnerCloudProvisioningStep',
title: 'Save the Terraform script to a file',
languageIdentifier: 'terraform',
instructions:
'terraform {\n required_version = "~> 1.5"\n\n required_providers {\n google = {\n source = "hashicorp/google"\n version = "~> 5.12"\n }\n\n tls = {\n source = "hashicorp/tls"\n version = "~> 4.0"\n }\n }\n}\n\nlocals {\n google_project = "dev-gcp-s3c-integrati-9abafed1"\n google_region = "us-central1"\n google_zone = "us-central1-a"\n}\n\nprovider "google" {\n project = local.google_project\n region = local.google_region\n zone = local.google_zone\n}\n\nvariable "runner_token" {\n type = string\n sensitive = true\n}\n\n# Added available customisation\nmodule "runner-deployment" {\n source = "git::https://gitlab.com/gitlab-org/ci-cd/runner-tools/grit.git//scenarios/google/linux/docker-autoscaler-default"\n\n google_project = local.google_project\n google_region = local.google_region\n google_zone = local.google_zone\n\n name = "grit-BQQkbs5X_"\n\n gitlab_url = "http://gdk.test:3000"\n\n runner_token = var.runner_token\n\n ephemeral_runner = {\n # disk_type = "pd-ssd"\n # disk_size = 50\n machine_type = "n2d-standard-2"\n # source_image = "projects/cos-cloud/global/images/family/cos-stable"\n }\n}\n\noutput "runner-manager-external-ip" {\n value = module.runner-deployment.runner_manager_external_ip\n}\n',
instructions: 'terraform...',
},
{
__typename: 'CiRunnerCloudProvisioningStep',
title: 'Apply the Terraform script',
languageIdentifier: 'shell',
instructions:
'#!/bin/bash\n\nterraform plan -var gitlab_runner="glrt-BQQkbs5X_f-ys6wLEy2V" -out plan.out\nterraform apply plan.out\n',
instructions: '#!/bin/bash\n\nterraform plan...',
},
],
},

View File

@ -4,6 +4,8 @@ import { nextTick } from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { CONTENT_EDITOR_PASTE } from '~/vue_shared/constants';
import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub';
import ContentEditor from '~/content_editor/components/content_editor.vue';
import ContentEditorAlert from '~/content_editor/components/content_editor_alert.vue';
import ContentEditorProvider from '~/content_editor/components/content_editor_provider.vue';
@ -17,6 +19,7 @@ import LoadingIndicator from '~/content_editor/components/loading_indicator.vue'
import waitForPromises from 'helpers/wait_for_promises';
import { KEYDOWN_EVENT } from '~/content_editor/constants';
import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue';
import { mockChainedCommands } from '../test_utils';
describe('ContentEditor', () => {
let wrapper;
@ -290,4 +293,29 @@ describe('ContentEditor', () => {
expect(wrapper.findComponent(EditorModeSwitcher).exists()).toBe(true);
});
it('pastes content when CONTENT_EDITOR_READY_PASTE event is emitted', async () => {
const markdown = 'hello world';
createWrapper({ markdown });
renderMarkdown.mockResolvedValueOnce(markdown);
await waitForPromises();
const editorContent = findEditorContent();
const commands = mockChainedCommands(editorContent.props('editor'), [
'focus',
'pasteContent',
'run',
]);
markdownEditorEventHub.$emit(CONTENT_EDITOR_PASTE, 'Paste content');
await waitForPromises();
expect(commands.focus).toHaveBeenCalled();
expect(commands.pasteContent).toHaveBeenCalledWith('Paste content');
expect(commands.run).toHaveBeenCalled();
});
});

View File

@ -9,8 +9,8 @@ import { DEFAULT_PER_PAGE } from '~/api';
import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data';
import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
import OrganizationsIndexApp from '~/organizations/index/components/app.vue';
import OrganizationsView from '~/organizations/index/components/organizations_view.vue';
import { MOCK_NEW_ORG_URL } from '../mock_data';
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
import { MOCK_NEW_ORG_URL } from '../../shared/mock_data';
jest.mock('~/alert');

View File

@ -1,7 +1,7 @@
import { GlAvatarLabeled } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import OrganizationsListItem from '~/organizations/index/components/organizations_list_item.vue';
import OrganizationsListItem from '~/organizations/shared/components/list/organizations_list_item.vue';
import { organizations } from '~/organizations/mock_data';
const MOCK_ORGANIZATION = organizations[0];

View File

@ -1,8 +1,8 @@
import { GlKeysetPagination } from '@gitlab/ui';
import { omit } from 'lodash';
import { shallowMount } from '@vue/test-utils';
import OrganizationsList from '~/organizations/index/components/organizations_list.vue';
import OrganizationsListItem from '~/organizations/index/components/organizations_list_item.vue';
import OrganizationsList from '~/organizations/shared/components/list/organizations_list.vue';
import OrganizationsListItem from '~/organizations/shared/components/list/organizations_list_item.vue';
import { organizations as nodes, pageInfo, pageInfoOnePage } from '~/organizations/mock_data';
describe('OrganizationsList', () => {

View File

@ -1,8 +1,8 @@
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { organizations } from '~/organizations/mock_data';
import OrganizationsView from '~/organizations/index/components/organizations_view.vue';
import OrganizationsList from '~/organizations/index/components/organizations_list.vue';
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
import OrganizationsList from '~/organizations/shared/components/list/organizations_list.vue';
import { MOCK_NEW_ORG_URL, MOCK_ORG_EMPTY_STATE_SVG } from '../mock_data';
describe('OrganizationsView', () => {

View File

@ -302,4 +302,15 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
)
end
end
describe '#admin_organizations_index_app_data' do
it 'returns expected json' do
expect(Gitlab::Json.parse(helper.admin_organizations_index_app_data)).to eq(
{
'new_organization_url' => new_organization_path,
'organizations_empty_state_svg_path' => organizations_empty_state_svg_path
}
)
end
end
end

View File

@ -152,7 +152,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
describe '.valid_status' do
valid_status = [:paused, :active, :finished, :failed, :finalizing]
valid_status = [:paused, :active, :finished, :failed, :finalizing, :finalized]
it 'returns valid status' do
expect(described_class.valid_status).to eq(valid_status)

View File

@ -533,6 +533,25 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers,
end
end
context 'when the migration is `finished`' do
let(:finished_status) { 3 }
let(:finalized_status) { 6 }
let(:migration_record) { create(:batched_background_migration, :finished) }
let(:configuration) do
{
job_class_name: migration_record.job_class_name,
table_name: migration_record.table_name,
column_name: migration_record.column_name,
job_arguments: migration_record.job_arguments
}
end
it 'updates the status to `finalized`' do
expect { ensure_batched_background_migration_is_finished }.to change { migration_record.reload.status }.from(finished_status).to(finalized_status)
end
end
context 'when within transaction' do
before do
allow(migration).to receive(:transaction_open?).and_return(true)

View File

@ -14,11 +14,14 @@ RSpec.describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shar
end.to change { subject.total_count(redis_key) }.by(1)
end
context 'with aliased legacy key' do
let(:redis_key) { '{event_counters}_web_ide_viewed' }
context 'for every aliased legacy key' do
let(:key_overrides) { YAML.safe_load(File.read(described_class::KEY_OVERRIDES_PATH)) }
it 'counter is increased for a legacy key' do
expect { subject.increment(redis_key) }.to change { subject.total_count('WEB_IDE_VIEWS_COUNT') }.by(1)
key_overrides.each do |alias_key, legacy_key|
expect { subject.increment(alias_key) }.to change { subject.total_count(legacy_key) }.by(1),
"Incrementing #{alias_key} did not increase #{legacy_key}"
end
end
end
end

View File

@ -1,7 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters::ServiceUsageDataCounter do
it_behaves_like 'a redis usage counter', 'Service Usage Data', :download_payload_click
end

View File

@ -7,7 +7,8 @@ require_migration!
RSpec.describe FinalizeNullifyCreatorIdOfOrphanedProjects, :migration, feature_category: :groups_and_projects do
let(:batched_migrations) { table(:batched_background_migrations) }
let(:batch_failed_status) { 2 }
let(:batch_finalized_status) { 3 }
let(:batch_finalized_status) { 6 }
let(:job_finished_status) { 3 }
let!(:migration) { described_class::MIGRATION }
@ -19,8 +20,8 @@ RSpec.describe FinalizeNullifyCreatorIdOfOrphanedProjects, :migration, feature_c
migration_record.reload
failed_job.reload
end.to change { migration_record.status }.from(migration_record.status).to(3).and(
change { failed_job.status }.from(batch_failed_status).to(batch_finalized_status)
end.to change { migration_record.status }.from(migration_record.status).to(batch_finalized_status).and(
change { failed_job.status }.from(batch_failed_status).to(job_finished_status)
)
end
end

View File

@ -7,7 +7,8 @@ require_migration!
RSpec.describe FinalizeEncryptCiTriggerToken, migration: :gitlab_ci, feature_category: :continuous_integration do
let(:batched_migrations) { table(:batched_background_migrations) }
let(:batch_failed_status) { 2 }
let(:batch_finalized_status) { 3 }
let(:batch_finalized_status) { 6 }
let(:job_finished_status) { 3 }
let!(:migration) { described_class::MIGRATION }
@ -85,7 +86,7 @@ RSpec.describe FinalizeEncryptCiTriggerToken, migration: :gitlab_ci, feature_cat
end.to(
change { migration_record.status }.from(status).to(batch_finalized_status)
.and(
change { failed_job.status }.from(batch_failed_status).to(batch_finalized_status)
change { failed_job.status }.from(batch_failed_status).to(job_finished_status)
)
)
end

View File

@ -9,6 +9,7 @@ RSpec.describe EnsureIncidentWorkItemTypeBackfillIsFinished, :migration, feature
let(:batched_migrations) { table(:batched_background_migrations) }
let(:work_item_types) { table(:work_item_types) }
let(:batch_failed_status) { 2 }
let(:batch_finalized_status) { 6 }
let!(:migration_class) { described_class::MIGRATION }
@ -68,7 +69,7 @@ RSpec.describe EnsureIncidentWorkItemTypeBackfillIsFinished, :migration, feature
migrate!
backfill_migration.reload
end.to change { backfill_migration.status }.from(status).to(3)
end.to change { backfill_migration.status }.from(status).to(batch_finalized_status)
end
end
end

View File

@ -60,7 +60,7 @@ RSpec.describe FinalizeBackfillResourceLinkEvents, feature_category: :team_plann
migrate!
batched_migration.reload
end.to change { batched_migration.status }.from(status).to(3)
end.to change { batched_migration.status }.from(status).to(6)
end
end
end

View File

@ -6713,7 +6713,6 @@
- './spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb'
- './spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb'
- './spec/lib/gitlab/usage_data_counters/search_counter_spec.rb'
- './spec/lib/gitlab/usage_data_counters/service_usage_data_counter_spec.rb'
- './spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb'
- './spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb'
- './spec/lib/gitlab/usage_data_counters_spec.rb'

View File

@ -1321,10 +1321,10 @@
stylelint-declaration-strict-value "1.10.4"
stylelint-scss "6.0.0"
"@gitlab/svgs@3.86.0":
version "3.86.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.86.0.tgz#d6546231fc3b1a7ac24dd0cfe9b6f7facf7f3bff"
integrity sha512-+RDeJ6z05AXWAhCHC/4H4CKdw/nQgGIKBph2nsJ7/w0CJldnh/4GO5op8LmVaUVNyHt02AKbSJOnmen1LL+b8A==
"@gitlab/svgs@3.88.0":
version "3.88.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.88.0.tgz#f57bd0ea94866038b19d94a2fa815d54f10c2c09"
integrity sha512-j2C+Ddt2LcJTf2hQZZ6sybsiQ/mMnZG4wKmJfM5YjXR5qPVaAUyH+MHnvPGcHnt+tMYr2P52zlcpsQa6WB5xeQ==
"@gitlab/ui@77.1.0":
version "77.1.0"