diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-17 21:10:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-17 21:10:11 +0300 |
commit | 5cbf24858edb03505b16474e3b7b41a49b677ff6 (patch) | |
tree | 21999fbb911fe2410cf498b3ed668d202dc90098 /app | |
parent | 673a1a02e97181a5f2d94cd0b116ebb4dba3d875 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
43 files changed, 552 insertions, 160 deletions
diff --git a/app/assets/javascripts/crm/organizations/components/graphql/create_organization.mutation.graphql b/app/assets/javascripts/crm/organizations/components/graphql/create_customer_relations_organization.mutation.graphql index 2cc7e53ee9b..8e019420eb7 100644 --- a/app/assets/javascripts/crm/organizations/components/graphql/create_organization.mutation.graphql +++ b/app/assets/javascripts/crm/organizations/components/graphql/create_customer_relations_organization.mutation.graphql @@ -1,6 +1,6 @@ #import "./crm_organization_fields.fragment.graphql" -mutation createOrganization($input: CustomerRelationsOrganizationCreateInput!) { +mutation createCustomerRelationsOrganization($input: CustomerRelationsOrganizationCreateInput!) { customerRelationsOrganizationCreate(input: $input) { organization { ...OrganizationFragment diff --git a/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue b/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue index 4d2a038458d..fb056e4fa2c 100644 --- a/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue +++ b/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue @@ -4,7 +4,7 @@ import { convertToGraphQLId } from '~/graphql_shared/utils'; import { TYPENAME_CRM_ORGANIZATION, TYPENAME_GROUP } from '~/graphql_shared/constants'; import CrmForm from '../../components/crm_form.vue'; import getGroupOrganizationsQuery from './graphql/get_group_organizations.query.graphql'; -import createOrganizationMutation from './graphql/create_organization.mutation.graphql'; +import createCustomerRelationsOrganizationMutation from './graphql/create_customer_relations_organization.mutation.graphql'; import updateOrganizationMutation from './graphql/update_organization.mutation.graphql'; export default { @@ -31,7 +31,7 @@ export default { mutation() { if (this.isEditMode) return updateOrganizationMutation; - return createOrganizationMutation; + return createCustomerRelationsOrganizationMutation; }, getQuery() { return { diff --git a/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js b/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js index 4e330ab16a8..67a472dac93 100644 --- a/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js +++ b/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js @@ -73,7 +73,7 @@ export default { k8sServices(_, { configuration, namespace }) { const coreV1Api = new CoreV1Api(new Configuration(configuration)); const servicesApi = namespace - ? coreV1Api.listCoreV1NamespacedService(namespace) + ? coreV1Api.listCoreV1NamespacedService({ namespace }) : coreV1Api.listCoreV1ServiceForAllNamespaces(); return servicesApi diff --git a/app/assets/javascripts/import_entities/components/group_dropdown.vue b/app/assets/javascripts/import_entities/components/group_dropdown.vue deleted file mode 100644 index 68bdcf7ef90..00000000000 --- a/app/assets/javascripts/import_entities/components/group_dropdown.vue +++ /dev/null @@ -1,72 +0,0 @@ -<script> -import { GlDropdown, GlSearchBoxByType } from '@gitlab/ui'; -import { debounce } from 'lodash'; - -import { s__ } from '~/locale'; -import { createAlert } from '~/alert'; -import searchNamespacesWhereUserCanImportProjectsQuery from '~/import_entities/import_projects/graphql/queries/search_namespaces_where_user_can_import_projects.query.graphql'; -import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants'; -import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants'; -import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; - -// This is added outside the component as each dropdown on the page triggers a query, -// so if multiple queries fail, we only show 1 error. -const reportNamespaceLoadError = debounce( - () => - createAlert({ - message: s__('ImportProjects|Requesting namespaces failed'), - }), - DEFAULT_DEBOUNCE_AND_THROTTLE_MS, -); - -export default { - components: { - GlDropdown, - GlSearchBoxByType, - }, - inheritAttrs: false, - data() { - return { searchTerm: '' }; - }, - apollo: { - namespaces: { - query: searchNamespacesWhereUserCanImportProjectsQuery, - variables() { - return { - search: this.searchTerm, - }; - }, - skip() { - const hasNotEnoughSearchCharacters = - this.searchTerm.length > 0 && this.searchTerm.length < MINIMUM_SEARCH_LENGTH; - return hasNotEnoughSearchCharacters; - }, - update(data) { - return data.currentUser.groups.nodes; - }, - error: reportNamespaceLoadError, - debounce: DEBOUNCE_DELAY, - }, - }, - computed: { - filteredNamespaces() { - return (this.namespaces ?? []).filter((ns) => - ns.fullPath.toLowerCase().includes(this.searchTerm.toLowerCase()), - ); - }, - }, -}; -</script> -<template> - <gl-dropdown - toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!" - class="gl-h-7 gl-flex-fill-1" - data-qa-selector="target_namespace_selector_dropdown" - v-bind="$attrs" - > - <template #header> - <gl-search-box-by-type v-model.trim="searchTerm" /> - </template> - <slot :namespaces="filteredNamespaces"></slot> - </gl-dropdown> -</template> diff --git a/app/assets/javascripts/import_entities/components/import_target_dropdown.vue b/app/assets/javascripts/import_entities/components/import_target_dropdown.vue index b18a106608a..47c030bf1fc 100644 --- a/app/assets/javascripts/import_entities/components/import_target_dropdown.vue +++ b/app/assets/javascripts/import_entities/components/import_target_dropdown.vue @@ -26,13 +26,19 @@ export default { }, props: { + disabled: { + type: Boolean, + required: false, + default: false, + }, selected: { type: String, required: true, }, userNamespace: { type: String, - required: true, + required: false, + default: undefined, }, }, @@ -66,6 +72,10 @@ export default { }, computed: { + isProject() { + return Boolean(this.userNamespace); + }, + filteredNamespaces() { return (this.namespaces ?? []).filter((ns) => ns.fullPath.toLowerCase().includes(this.searchTerm.toLowerCase()), @@ -78,14 +88,33 @@ export default { items() { return [ - { - text: __('Users'), - options: [{ text: this.userNamespace, value: this.userNamespace }], - }, + this.isProject + ? { + text: __('Users'), + options: [ + { + text: this.userNamespace, + value: this.userNamespace, + }, + ], + } + : { + text: __('Parent'), + textSrOnly: true, + options: [ + { + text: s__('BulkImport|No parent'), + value: '', + }, + ], + }, { text: __('Groups'), options: this.filteredNamespaces.map((namespace) => { - return { text: namespace.fullPath, value: namespace.fullPath }; + return { + text: namespace.fullPath, + value: namespace.fullPath, + }; }), }, ]; @@ -94,7 +123,15 @@ export default { methods: { onSelect(value) { - this.$emit('select', value); + if (this.isProject) { + this.$emit('select', value); + } else if (value === '') { + this.$emit('select', { fullPath: '', id: null }); + } else { + const { fullPath, id } = this.filteredNamespaces.find((ns) => ns.fullPath === value); + + this.$emit('select', { fullPath, id }); + } }, onSearch(value) { @@ -107,12 +144,13 @@ export default { <template> <gl-collapsible-listbox :items="items" + :disabled="disabled" :selected="selected" :toggle-text="toggleText" searchable fluid-width toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!" - data-qa-selector="target_namespace_selector_dropdown" + data-testid="target-namespace-dropdown" @select="onSelect" @search="onSearch" /> diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue b/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue index 8af64e882ab..b4484c89b9f 100644 --- a/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue +++ b/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue @@ -1,20 +1,12 @@ <script> -import { - GlDropdownDivider, - GlDropdownItem, - GlDropdownSectionHeader, - GlFormInput, -} from '@gitlab/ui'; -import { s__ } from '~/locale'; -import ImportGroupDropdown from '../../components/group_dropdown.vue'; +import { GlFormInput } from '@gitlab/ui'; +import ImportTargetDropdown from '../../components/import_target_dropdown.vue'; + import { getInvalidNameValidationMessage } from '../utils'; export default { components: { - ImportGroupDropdown, - GlDropdownDivider, - GlDropdownItem, - GlDropdownSectionHeader, + ImportTargetDropdown, GlFormInput, }, props: { @@ -25,8 +17,8 @@ export default { }, computed: { - fullPath() { - return this.group.importTarget.targetNamespace.fullPath || s__('BulkImport|No parent'); + selectedImportTarget() { + return this.group.importTarget.targetNamespace.fullPath || ''; }, validationMessage() { return ( @@ -47,6 +39,10 @@ export default { focusNewName() { this.$refs.newName.$el.focus(); }, + + onImportTargetSelect(namespace) { + this.$emit('update-target-namespace', namespace); + }, }, }; </script> @@ -54,34 +50,12 @@ export default { <template> <div> <div class="gl-display-flex gl-align-items-stretch"> - <import-group-dropdown - #default="{ namespaces }" - :text="fullPath" + <import-target-dropdown + :selected="selectedImportTarget" :disabled="!isPathSelectionAvailable" - toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!" - class="gl-h-7 gl-flex-grow-1" - data-qa-selector="target_namespace_selector_dropdown" - data-testid="target-namespace-selector" - > - <gl-dropdown-item @click="$emit('update-target-namespace', { fullPath: '', id: null })">{{ - s__('BulkImport|No parent') - }}</gl-dropdown-item> - <template v-if="namespaces.length"> - <gl-dropdown-divider /> - <gl-dropdown-section-header> - {{ s__('BulkImport|Existing groups') }} - </gl-dropdown-section-header> - <gl-dropdown-item - v-for="ns in namespaces" - :key="ns.fullPath" - data-qa-selector="target_group_dropdown_item" - :data-qa-group-name="ns.fullPath" - @click="$emit('update-target-namespace', ns)" - > - {{ ns.fullPath }} - </gl-dropdown-item> - </template> - </import-group-dropdown> + @select="onImportTargetSelect" + /> + <div class="gl-h-7 gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1 gl-bg-gray-10" :class="{ diff --git a/app/assets/javascripts/integrations/constants.js b/app/assets/javascripts/integrations/constants.js index e803e11bf6d..12f5fa21137 100644 --- a/app/assets/javascripts/integrations/constants.js +++ b/app/assets/javascripts/integrations/constants.js @@ -59,6 +59,8 @@ export const integrationTriggerEvents = { DEPLOYMENT: 'deployment_events', ALERT: 'alert_events', INCIDENT: 'incident_events', + GROUP_MENTION: 'group_mention_events', + GROUP_CONFIDENTIAL_MENTION: 'group_confidential_mention_events', }; export const integrationTriggerEventTitles = { @@ -88,6 +90,12 @@ export const integrationTriggerEventTitles = { [integrationTriggerEvents.INCIDENT]: s__( 'IntegrationEvents|An incident is created, closed, or reopened', ), + [integrationTriggerEvents.GROUP_MENTION]: s__( + 'IntegrationEvents|A group is mentioned in a public context', + ), + [integrationTriggerEvents.GROUP_CONFIDENTIAL_MENTION]: s__( + 'IntegrationEvents|A group is mentioned in a confidential context', + ), }; export const billingPlans = { diff --git a/app/assets/javascripts/ml/model_registry/routes/models/index/components/ml_models_index.vue b/app/assets/javascripts/ml/model_registry/routes/models/index/components/ml_models_index.vue index 9c5be18345c..3770b4ec3ac 100644 --- a/app/assets/javascripts/ml/model_registry/routes/models/index/components/ml_models_index.vue +++ b/app/assets/javascripts/ml/model_registry/routes/models/index/components/ml_models_index.vue @@ -1,14 +1,14 @@ <script> -import { GlLink } from '@gitlab/ui'; import { isEmpty } from 'lodash'; import * as translations from '~/ml/model_registry/routes/models/index/translations'; import Pagination from '~/vue_shared/components/incubation/pagination.vue'; +import ModelRow from './model_row.vue'; export default { - name: 'MlExperimentsIndexApp', + name: 'MlModelRegistryApp', components: { - GlLink, Pagination, + ModelRow, }, props: { models: { @@ -40,10 +40,7 @@ export default { </div> <template v-if="hasModels"> - <div v-for="model in models" :key="model.name"> - <gl-link :href="model.path"> {{ model.name }} / {{ model.version }} </gl-link> - </div> - + <model-row v-for="model in models" :key="model.name" :model="model" /> <pagination v-bind="pageInfo" /> </template> diff --git a/app/assets/javascripts/ml/model_registry/routes/models/index/components/model_row.vue b/app/assets/javascripts/ml/model_registry/routes/models/index/components/model_row.vue new file mode 100644 index 00000000000..4f91f0939a8 --- /dev/null +++ b/app/assets/javascripts/ml/model_registry/routes/models/index/components/model_row.vue @@ -0,0 +1,35 @@ +<script> +import { GlLink } from '@gitlab/ui'; +import { modelVersionCountMessage } from '../translations'; + +export default { + name: 'MlModelRow', + components: { + GlLink, + }, + props: { + model: { + type: Object, + required: true, + }, + }, + computed: { + hasVersions() { + return this.model.version != null; + }, + }, + modelVersionCountMessage, +}; +</script> + +<template> + <div class="gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-py-3"> + <gl-link :href="model.path" class="gl-text-body gl-font-weight-bold gl-line-height-24"> + {{ model.name }} + </gl-link> + + <div class="gl-text-secondary"> + {{ $options.modelVersionCountMessage(model.version, model.versionCount) }} + </div> + </div> +</template> diff --git a/app/assets/javascripts/ml/model_registry/routes/models/index/translations.js b/app/assets/javascripts/ml/model_registry/routes/models/index/translations.js index 6f61b0aff46..9210d816373 100644 --- a/app/assets/javascripts/ml/model_registry/routes/models/index/translations.js +++ b/app/assets/javascripts/ml/model_registry/routes/models/index/translations.js @@ -1,4 +1,16 @@ -import { s__ } from '~/locale'; +import { s__, n__, sprintf } from '~/locale'; export const TITLE_LABEL = s__('MlModelRegistry|Model registry'); export const NO_MODELS_LABEL = s__('MlModelRegistry|No models registered in this project'); + +export const modelVersionCountMessage = (version, versionCount) => { + if (!versionCount) return s__('MlModelRegistry|No registered versions'); + + const message = n__( + 'MlModelRegistry|%{version} · No other versions', + 'MlModelRegistry|%{version} · %{versionCount} versions', + versionCount, + ); + + return sprintf(message, { version, versionCount }); +}; diff --git a/app/assets/javascripts/notes/components/email_participants_warning.vue b/app/assets/javascripts/notes/components/email_participants_warning.vue index cf9108992be..478c5847b41 100644 --- a/app/assets/javascripts/notes/components/email_participants_warning.vue +++ b/app/assets/javascripts/notes/components/email_participants_warning.vue @@ -1,11 +1,12 @@ <script> -import { GlSprintf } from '@gitlab/ui'; +import { GlSprintf, GlButton } from '@gitlab/ui'; import { toNounSeriesText } from '~/lib/utils/grammar'; import { s__, sprintf } from '~/locale'; export default { components: { GlSprintf, + GlButton, }, props: { emails: { @@ -58,9 +59,9 @@ export default { <div class="issuable-note-warning"> <gl-sprintf :message="message"> <template #andMore> - <button type="button" class="gl-button btn-link" @click="showMoreParticipants"> + <gl-button variant="link" class="gl-vertical-align-baseline" @click="showMoreParticipants"> {{ moreLabel }} - </button> + </gl-button> </template> <template #emails> <span>{{ title }}</span> diff --git a/app/assets/javascripts/organizations/mock_data.js b/app/assets/javascripts/organizations/mock_data.js index b73c18f19ab..d281a0d8a1c 100644 --- a/app/assets/javascripts/organizations/mock_data.js +++ b/app/assets/javascripts/organizations/mock_data.js @@ -280,3 +280,11 @@ export const organizationGroups = { }, ], }; + +export const createOrganizationResponse = { + organization: { + name: 'Default', + path: '/-/organizations/default', + }, + errors: [], +}; diff --git a/app/assets/javascripts/organizations/new/components/app.vue b/app/assets/javascripts/organizations/new/components/app.vue new file mode 100644 index 00000000000..e97626ef4f8 --- /dev/null +++ b/app/assets/javascripts/organizations/new/components/app.vue @@ -0,0 +1,73 @@ +<script> +import { GlSprintf, GlLink } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { visitUrl } from '~/lib/utils/url_utility'; +import { createAlert } from '~/alert'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import createOrganizationMutation from '../graphql/mutations/create_organization.mutation.graphql'; +import NewEditForm from '../../shared/components/new_edit_form.vue'; + +export default { + name: 'OrganizationNewApp', + components: { NewEditForm, GlSprintf, GlLink }, + i18n: { + pageTitle: s__('Organization|New organization'), + pageDescription: s__( + 'Organization|%{linkStart}Organizations%{linkEnd} are a top-level container to hold your groups and projects.', + ), + errorMessage: s__('Organization|An error occurred creating an organization. Please try again.'), + }, + data() { + return { + loading: false, + }; + }, + computed: { + organizationsHelpPagePath() { + return helpPagePath('user/organization/index'); + }, + }, + methods: { + async onSubmit(formValues) { + this.loading = true; + try { + const { + data: { + createOrganization: { organization, errors }, + }, + } = await this.$apollo.mutate({ + mutation: createOrganizationMutation, + variables: { + ...formValues, + }, + }); + + if (errors.length) { + // TODO: handle errors when using real API after https://gitlab.com/gitlab-org/gitlab/-/issues/417891 is complete. + return; + } + + visitUrl(organization.path); + } catch (error) { + createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true }); + } finally { + this.loading = false; + } + }, + }, +}; +</script> + +<template> + <div class="gl-py-6"> + <h1 class="gl-mt-0 gl-font-size-h-display">{{ $options.i18n.pageTitle }}</h1> + <p> + <gl-sprintf :message="$options.i18n.pageDescription"> + <template #link="{ content }" + ><gl-link :href="organizationsHelpPagePath">{{ content }}</gl-link></template + > + </gl-sprintf> + </p> + <new-edit-form :loading="loading" @submit="onSubmit" /> + </div> +</template> diff --git a/app/assets/javascripts/organizations/new/graphql/mutations/create_organization.mutation.graphql b/app/assets/javascripts/organizations/new/graphql/mutations/create_organization.mutation.graphql new file mode 100644 index 00000000000..766c7e96d14 --- /dev/null +++ b/app/assets/javascripts/organizations/new/graphql/mutations/create_organization.mutation.graphql @@ -0,0 +1,9 @@ +mutation createOrganization($input: LocalCreateOrganizationInput!) { + createOrganization(input: $input) @client { + organization { + name + path + } + errors + } +} diff --git a/app/assets/javascripts/organizations/new/graphql/typedefs.graphql b/app/assets/javascripts/organizations/new/graphql/typedefs.graphql new file mode 100644 index 00000000000..f708c4ad162 --- /dev/null +++ b/app/assets/javascripts/organizations/new/graphql/typedefs.graphql @@ -0,0 +1,5 @@ +# TODO: Use real input type when https://gitlab.com/gitlab-org/gitlab/-/issues/417891 is complete. +input LocalCreateOrganizationInput { + name: String + path: String +} diff --git a/app/assets/javascripts/organizations/new/index.js b/app/assets/javascripts/organizations/new/index.js new file mode 100644 index 00000000000..a65603227f6 --- /dev/null +++ b/app/assets/javascripts/organizations/new/index.js @@ -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 resolvers from '../shared/graphql/resolvers'; +import App from './components/app.vue'; + +export const initOrganizationsNew = () => { + const el = document.getElementById('js-organizations-new'); + + if (!el) return false; + + const { + dataset: { appData }, + } = el; + const { organizationsPath, rootUrl } = convertObjectPropsToCamelCase(JSON.parse(appData)); + + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(resolvers), + }); + + return new Vue({ + el, + name: 'OrganizationNewRoot', + apolloProvider, + provide: { + organizationsPath, + rootUrl, + }, + render(createElement) { + return createElement(App); + }, + }); +}; diff --git a/app/assets/javascripts/organizations/shared/components/new_edit_form.vue b/app/assets/javascripts/organizations/shared/components/new_edit_form.vue new file mode 100644 index 00000000000..db33f240966 --- /dev/null +++ b/app/assets/javascripts/organizations/shared/components/new_edit_form.vue @@ -0,0 +1,125 @@ +<script> +import { + GlForm, + GlFormFields, + GlButton, + GlFormInputGroup, + GlFormInput, + GlInputGroupText, + GlTruncate, +} from '@gitlab/ui'; +import { formValidators } from '@gitlab/ui/dist/utils'; +import { s__, __ } from '~/locale'; +import { slugify } from '~/lib/utils/text_utility'; +import { joinPaths } from '~/lib/utils/url_utility'; + +export default { + name: 'NewEditForm', + components: { + GlForm, + GlFormFields, + GlButton, + GlFormInputGroup, + GlFormInput, + GlInputGroupText, + GlTruncate, + }, + i18n: { + createOrganization: s__('Organization|Create organization'), + cancel: __('Cancel'), + pathPlaceholder: s__('Organization|my-organization'), + }, + formId: 'new-organization-form', + fields: { + name: { + label: s__('Organization|Organization name'), + validators: [formValidators.required(s__('Organization|Organization name is required.'))], + groupAttrs: { + description: s__( + 'Organization|Must start with a letter, digit, emoji, or underscore. Can also contain periods, dashes, spaces, and parentheses.', + ), + }, + inputAttrs: { + class: 'gl-md-form-input-lg', + placeholder: s__('Organization|My organization'), + }, + }, + path: { + label: s__('Organization|Organization URL'), + validators: [formValidators.required(s__('Organization|Organization URL is required.'))], + }, + }, + inject: ['organizationsPath', 'rootUrl'], + props: { + loading: { + type: Boolean, + required: true, + }, + }, + data() { + return { + formValues: { + name: '', + path: '', + }, + hasPathBeenManuallySet: false, + }; + }, + computed: { + baseUrl() { + return joinPaths(this.rootUrl, this.organizationsPath, '/'); + }, + }, + watch: { + 'formValues.name': function watchName(value) { + if (this.hasPathBeenManuallySet) { + return; + } + + this.formValues.path = slugify(value); + }, + }, + methods: { + onPathInput(event, formFieldsInputEvent) { + formFieldsInputEvent(event); + this.hasPathBeenManuallySet = true; + }, + }, +}; +</script> + +<template> + <gl-form :id="$options.formId"> + <gl-form-fields + v-model="formValues" + :form-id="$options.formId" + :fields="$options.fields" + @submit="$emit('submit', formValues)" + > + <template #input(path)="{ id, value, validation, input, blur }"> + <gl-form-input-group> + <template #prepend> + <gl-input-group-text class="organization-root-path"> + <gl-truncate :text="baseUrl" position="middle" /> + </gl-input-group-text> + </template> + <gl-form-input + v-bind="validation" + :id="id" + :value="value" + :placeholder="$options.i18n.pathPlaceholder" + class="gl-h-auto! gl-md-form-input-lg" + @input="onPathInput($event, input)" + @blur="blur" + /> + </gl-form-input-group> + </template> + </gl-form-fields> + <div class="gl-display-flex gl-gap-3"> + <gl-button type="submit" variant="confirm" class="js-no-auto-disable" :loading="loading">{{ + $options.i18n.createOrganization + }}</gl-button> + <gl-button :href="organizationsPath">{{ $options.i18n.cancel }}</gl-button> + </div> + </gl-form> +</template> diff --git a/app/assets/javascripts/organizations/shared/graphql/resolvers.js b/app/assets/javascripts/organizations/shared/graphql/resolvers.js index 318b41647bc..9f7e9b22e1d 100644 --- a/app/assets/javascripts/organizations/shared/graphql/resolvers.js +++ b/app/assets/javascripts/organizations/shared/graphql/resolvers.js @@ -1,4 +1,9 @@ -import { organizations, organizationProjects, organizationGroups } from '../../mock_data'; +import { + organizations, + organizationProjects, + organizationGroups, + createOrganizationResponse, +} from '../../mock_data'; const simulateLoading = () => { return new Promise((resolve) => { @@ -28,4 +33,12 @@ export default { }; }, }, + Mutation: { + createOrganization: async () => { + // Simulate API loading + await simulateLoading(); + + return createOrganizationResponse; + }, + }, }; diff --git a/app/assets/javascripts/pages/organizations/organizations/new/index.js b/app/assets/javascripts/pages/organizations/organizations/new/index.js new file mode 100644 index 00000000000..ab23fbf155d --- /dev/null +++ b/app/assets/javascripts/pages/organizations/organizations/new/index.js @@ -0,0 +1,3 @@ +import { initOrganizationsNew } from '~/organizations/new'; + +initOrganizationsNew(); diff --git a/app/assets/javascripts/vue_shared/components/incubation/pagination.vue b/app/assets/javascripts/vue_shared/components/incubation/pagination.vue index b5afe92316a..6b70e9f3ed9 100644 --- a/app/assets/javascripts/vue_shared/components/incubation/pagination.vue +++ b/app/assets/javascripts/vue_shared/components/incubation/pagination.vue @@ -57,6 +57,7 @@ export default { :next-text="$options.i18n.nextPageButtonLabel" :prev-button-link="previousPageLink" :next-button-link="nextPageLink" + class="gl-mt-4" /> </div> </template> diff --git a/app/assets/stylesheets/page_bundles/organizations.scss b/app/assets/stylesheets/page_bundles/organizations.scss index 7591be064c6..1f1d127a82a 100644 --- a/app/assets/stylesheets/page_bundles/organizations.scss +++ b/app/assets/stylesheets/page_bundles/organizations.scss @@ -4,3 +4,7 @@ .organization-row .organization-description p { @include gl-mb-0; } + +.organization-root-path { + max-width: 40vw; +} diff --git a/app/components/projects/ml/models_index_component.rb b/app/components/projects/ml/models_index_component.rb index 8569d26945c..57900165ad1 100644 --- a/app/components/projects/ml/models_index_component.rb +++ b/app/components/projects/ml/models_index_component.rb @@ -25,6 +25,7 @@ module Projects { name: m.name, version: m.latest_version_name, + version_count: m.version_count, path: m.latest_package_path } end diff --git a/app/finders/concerns/packages/finder_helper.rb b/app/finders/concerns/packages/finder_helper.rb index 0ae99782cd3..585b35981a6 100644 --- a/app/finders/concerns/packages/finder_helper.rb +++ b/app/finders/concerns/packages/finder_helper.rb @@ -13,11 +13,13 @@ module Packages project.packages.installable end - def packages_visible_to_user(user, within_group:) + def packages_visible_to_user(user, within_group:, with_package_registry_enabled: false) return ::Packages::Package.none unless within_group return ::Packages::Package.none unless Ability.allowed?(user, :read_group, within_group) projects = projects_visible_to_reporters(user, within_group: within_group) + projects = projects.with_package_registry_enabled if with_package_registry_enabled + ::Packages::Package.for_projects(projects.select(:id)).installable end diff --git a/app/finders/packages/npm/packages_for_user_finder.rb b/app/finders/packages/npm/packages_for_user_finder.rb index f42e49f9184..dc1d3b6e7fe 100644 --- a/app/finders/packages/npm/packages_for_user_finder.rb +++ b/app/finders/packages/npm/packages_for_user_finder.rb @@ -3,6 +3,8 @@ module Packages module Npm class PackagesForUserFinder < ::Packages::GroupOrProjectPackageFinder + extend ::Gitlab::Utils::Override + def execute packages end @@ -13,6 +15,11 @@ module Packages base.npm .with_name(@params[:package_name]) end + + override :group_packages + def group_packages + packages_visible_to_user(@current_user, within_group: @project_or_group, with_package_registry_enabled: true) + end end end end diff --git a/app/finders/projects/ml/model_finder.rb b/app/finders/projects/ml/model_finder.rb index 004fd20403d..1e407ba4aa4 100644 --- a/app/finders/projects/ml/model_finder.rb +++ b/app/finders/projects/ml/model_finder.rb @@ -11,6 +11,7 @@ module Projects ::Ml::Model .by_project(@project) .including_latest_version + .with_version_count end end end diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb index a88be976337..510561ec614 100644 --- a/app/helpers/integrations_helper.rb +++ b/app/helpers/integrations_helper.rb @@ -30,10 +30,6 @@ module IntegrationsHelper _("Alert") when "incident" _("Incident") - when "group_mention" - _("Group mention in public") - when "group_confidential_mention" - _("Group mention in private") end end # rubocop:enable Metrics/CyclomaticComplexity @@ -295,10 +291,6 @@ module IntegrationsHelper s_("ProjectService|Trigger event when a new, unique alert is recorded.") when "incident", "incident_events" s_("ProjectService|Trigger event when an incident is created.") - when "group_mention" - s_("ProjectService|Trigger event when a group is mentioned in a public context.") - when "group_confidential_mention" - s_("ProjectService|Trigger event when a group is mentioned in a confidential context.") when "build_events" s_("ProjectService|Trigger event when a build is created.") when "archive_trace_events" diff --git a/app/helpers/organizations/organization_helper.rb b/app/helpers/organizations/organization_helper.rb index 0f760d87173..5d89bb93000 100644 --- a/app/helpers/organizations/organization_helper.rb +++ b/app/helpers/organizations/organization_helper.rb @@ -16,6 +16,13 @@ module Organizations }.merge(shared_groups_and_projects_app_data).to_json end + def organization_new_app_data + { + organizations_path: organizations_path, + root_url: root_url + }.to_json + end + def organization_groups_and_projects_app_data shared_groups_and_projects_app_data.to_json end diff --git a/app/models/ci/catalog/components_project.rb b/app/models/ci/catalog/components_project.rb new file mode 100644 index 00000000000..2bc33a6f050 --- /dev/null +++ b/app/models/ci/catalog/components_project.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +module Ci + module Catalog + class ComponentsProject + # ComponentsProject is a type of Catalog Resource which contains one or more + # CI/CD components. + # It is responsible for retrieving the data of a component file, including the content, name, and file path. + + TEMPLATE_FILE = 'template.yml' + TEMPLATES_DIR = 'templates' + TEMPLATE_PATH_REGEX = '^templates\/\w+\-?\w+(?:\/template)?\.yml$' + + ComponentData = Struct.new(:content, :path, keyword_init: true) + + def initialize(project, sha = project&.default_branch) + @project = project + @sha = sha + end + + def fetch_component_paths(sha) + project.repository.search_files_by_regexp(TEMPLATE_PATH_REGEX, sha) + end + + def extract_component_name(path) + return unless path.match?(TEMPLATE_PATH_REGEX) + + dirname = File.dirname(path) + filename = File.basename(path, '.*') + + if dirname == TEMPLATES_DIR + filename + else + File.basename(dirname) + end + end + + def extract_inputs(blob) + result = Gitlab::Ci::Config::Yaml::Loader.new(blob).load_uninterpolated_yaml + + raise result.error_class, result.error unless result.valid? + + result.inputs + end + + def fetch_component(component_name) + path = simple_template_path(component_name) + content = fetch_content(path) + + if content.nil? + path = complex_template_path(component_name) + content = fetch_content(path) + end + + if content.nil? + path = legacy_template_path(component_name) + content = fetch_content(path) + end + + ComponentData.new(content: content, path: path) + end + + private + + attr_reader :project, :sha + + def fetch_content(component_path) + project.repository.blob_data_at(sha, component_path) + end + + # A simple template consists of a single file + def simple_template_path(component_name) + # TODO: Extract this line and move to fetch_content once we remove legacy fetching + return unless component_name.index('/').nil? + + File.join(TEMPLATES_DIR, "#{component_name}.yml") + end + + # A complex template is directory-based and may consist of multiple files. + # Given a path like "my-org/sub-group/the-project/templates/component" + # returns the entry point path: "templates/component/template.yml". + def complex_template_path(component_name) + # TODO: Extract this line and move to fetch_content once we remove legacy fetching + return unless component_name.index('/').nil? + + File.join(TEMPLATES_DIR, component_name, TEMPLATE_FILE) + end + + def legacy_template_path(component_name) + File.join(component_name, TEMPLATE_FILE).delete_prefix('/') + end + end + end +end diff --git a/app/models/integrations/pushover.rb b/app/models/integrations/pushover.rb index e97c7e5e738..2feae29f627 100644 --- a/app/models/integrations/pushover.rb +++ b/app/models/integrations/pushover.rb @@ -125,5 +125,9 @@ module Integrations Gitlab::HTTP.post('/messages.json', base_uri: BASE_URI, body: pushover_data) end + + def avatar_url + ActionController::Base.helpers.image_path('illustrations/third-party-logos/integrations-logos/pushover.svg') + end end end diff --git a/app/models/ml/model.rb b/app/models/ml/model.rb index 0680bb0d381..27f03ed5857 100644 --- a/app/models/ml/model.rb +++ b/app/models/ml/model.rb @@ -19,6 +19,11 @@ module Ml has_one :latest_version, -> { latest_by_model }, class_name: 'Ml::ModelVersion', inverse_of: :model scope :including_latest_version, -> { includes(:latest_version) } + scope :with_version_count, -> { + left_outer_joins(:versions) + .select("ml_models.*, count(ml_model_versions.id) as version_count") + .group(:id) + } scope :by_project, ->(project) { where(project_id: project.id) } def valid_default_experiment? diff --git a/app/models/project.rb b/app/models/project.rb index 6c12c85d45d..fd226d23e77 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -753,6 +753,7 @@ class Project < ApplicationRecord scope :service_desk_enabled, -> { where(service_desk_enabled: true) } scope :with_builds_enabled, -> { with_feature_enabled(:builds) } scope :with_issues_enabled, -> { with_feature_enabled(:issues) } + scope :with_package_registry_enabled, -> { with_feature_enabled(:package_registry) } scope :with_issues_available_for_user, ->(current_user) { with_feature_available_for_user(:issues, current_user) } scope :with_merge_requests_available_for_user, ->(current_user) { with_feature_available_for_user(:merge_requests, current_user) } scope :with_issues_or_mrs_available_for_user, -> (user) do diff --git a/app/services/ci/components/fetch_service.rb b/app/services/ci/components/fetch_service.rb index 45abb415174..4f09d47b530 100644 --- a/app/services/ci/components/fetch_service.rb +++ b/app/services/ci/components/fetch_service.rb @@ -5,8 +5,6 @@ module Ci class FetchService include Gitlab::Utils::StrongMemoize - TEMPLATE_FILE = 'template.yml' - COMPONENT_PATHS = [ ::Gitlab::Ci::Components::InstancePath ].freeze @@ -23,11 +21,16 @@ module Ci reason: :unsupported_path) end - component_path = component_path_class.new(address: address, content_filename: TEMPLATE_FILE) - content = component_path.fetch_content!(current_user: current_user) + component_path = component_path_class.new(address: address) + result = component_path.fetch_content!(current_user: current_user) - if content.present? - ServiceResponse.success(payload: { content: content, path: component_path }) + if result + ServiceResponse.success(payload: { + content: result.content, + path: result.path, + project: component_path.project, + sha: component_path.sha + }) else ServiceResponse.error(message: "#{error_prefix} content not found", reason: :content_not_found) end diff --git a/app/services/ml/find_or_create_model_version_service.rb b/app/services/ml/find_or_create_model_version_service.rb index 1316b2546b9..f4d3f3e72d3 100644 --- a/app/services/ml/find_or_create_model_version_service.rb +++ b/app/services/ml/find_or_create_model_version_service.rb @@ -11,7 +11,6 @@ module Ml def execute model = Ml::FindOrCreateModelService.new(project, name).execute - Ml::ModelVersion.find_or_create!(model, version, package) end diff --git a/app/views/organizations/organizations/new.html.haml b/app/views/organizations/organizations/new.html.haml index 4d7f552c87b..1a6c5a79ff6 100644 --- a/app/views/organizations/organizations/new.html.haml +++ b/app/views/organizations/organizations/new.html.haml @@ -1,3 +1,6 @@ +- add_page_specific_style 'page_bundles/organizations' - page_title s_('Organization|New organization') - header_title _("Your work"), root_path - add_to_breadcrumbs s_('Organization|Organizations'), organizations_path + +#js-organizations-new{ data: { app_data: organization_new_app_data } } diff --git a/app/workers/ci/merge_requests/add_todo_when_build_fails_worker.rb b/app/workers/ci/merge_requests/add_todo_when_build_fails_worker.rb index 98bb259db0a..8bcbe9d6c9f 100644 --- a/app/workers/ci/merge_requests/add_todo_when_build_fails_worker.rb +++ b/app/workers/ci/merge_requests/add_todo_when_build_fails_worker.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Ci module MergeRequests class AddTodoWhenBuildFailsWorker diff --git a/app/workers/concerns/auto_devops_queue.rb b/app/workers/concerns/auto_devops_queue.rb index 61e3c1544bd..cdf429a8be5 100644 --- a/app/workers/concerns/auto_devops_queue.rb +++ b/app/workers/concerns/auto_devops_queue.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# + module AutoDevopsQueue extend ActiveSupport::Concern diff --git a/app/workers/concerns/chaos_queue.rb b/app/workers/concerns/chaos_queue.rb index 23e58b5182b..9a3d518dda8 100644 --- a/app/workers/concerns/chaos_queue.rb +++ b/app/workers/concerns/chaos_queue.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# + module ChaosQueue extend ActiveSupport::Concern diff --git a/app/workers/concerns/limited_capacity/job_tracker.rb b/app/workers/concerns/limited_capacity/job_tracker.rb index 4b5ce8a01f6..b4d884f914d 100644 --- a/app/workers/concerns/limited_capacity/job_tracker.rb +++ b/app/workers/concerns/limited_capacity/job_tracker.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module LimitedCapacity class JobTracker # rubocop:disable Scalability/IdempotentWorker include Gitlab::Utils::StrongMemoize diff --git a/app/workers/database/batched_background_migration/ci_database_worker.rb b/app/workers/database/batched_background_migration/ci_database_worker.rb index 58b0f5496f4..417af4c7172 100644 --- a/app/workers/database/batched_background_migration/ci_database_worker.rb +++ b/app/workers/database/batched_background_migration/ci_database_worker.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Database module BatchedBackgroundMigration class CiDatabaseWorker # rubocop:disable Scalability/IdempotentWorker diff --git a/app/workers/gitlab/github_gists_import/import_gist_worker.rb b/app/workers/gitlab/github_gists_import/import_gist_worker.rb index 60e4c8fdad6..151788150dd 100644 --- a/app/workers/gitlab/github_gists_import/import_gist_worker.rb +++ b/app/workers/gitlab/github_gists_import/import_gist_worker.rb @@ -106,9 +106,9 @@ module Gitlab def error(user_id, error_message, github_identifiers) attributes = { user_id: user_id, - github_identifiers: github_identifiers, + external_identifiers: github_identifiers, message: 'importer failed', - 'error.message': error_message + 'exception.message': error_message } Gitlab::GithubImport::Logger.error(structured_payload(attributes)) @@ -120,7 +120,7 @@ module Gitlab attributes = { user_id: user_id, message: message, - github_identifiers: gist_id + external_identifiers: gist_id } Gitlab::GithubImport::Logger.info(structured_payload(attributes)) diff --git a/app/workers/gitlab/github_gists_import/start_import_worker.rb b/app/workers/gitlab/github_gists_import/start_import_worker.rb index 33c91611719..f7d3eb1d759 100644 --- a/app/workers/gitlab/github_gists_import/start_import_worker.rb +++ b/app/workers/gitlab/github_gists_import/start_import_worker.rb @@ -51,7 +51,7 @@ module Gitlab end def log_error_and_raise!(user_id, error) - logger.error(structured_payload(user_id: user_id, message: 'import failed', 'error.message': error.message)) + logger.error(structured_payload(user_id: user_id, message: 'import failed', 'exception.message': error.message)) raise(error) end diff --git a/app/workers/gitlab/import/stuck_project_import_jobs_worker.rb b/app/workers/gitlab/import/stuck_project_import_jobs_worker.rb index 01979b2029f..93d670e1b8b 100644 --- a/app/workers/gitlab/import/stuck_project_import_jobs_worker.rb +++ b/app/workers/gitlab/import/stuck_project_import_jobs_worker.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Gitlab module Import class StuckProjectImportJobsWorker # rubocop:disable Scalability/IdempotentWorker diff --git a/app/workers/projects/after_import_worker.rb b/app/workers/projects/after_import_worker.rb index 06211b2d991..47bd07d0850 100644 --- a/app/workers/projects/after_import_worker.rb +++ b/app/workers/projects/after_import_worker.rb @@ -31,7 +31,7 @@ module Projects message: 'Project housekeeping failed', project_full_path: @project.full_path, project_id: @project.id, - 'error.message' => e.message + 'exception.message' => e.message ) end |