diff options
20 files changed, 329 insertions, 5 deletions
diff --git a/app/graphql/resolvers/ml/model_detail_resolver.rb b/app/graphql/resolvers/ml/model_detail_resolver.rb new file mode 100644 index 00000000000..01c025c1d8a --- /dev/null +++ b/app/graphql/resolvers/ml/model_detail_resolver.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Resolvers + module Ml + class ModelDetailResolver < Resolvers::BaseResolver + extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1 + + type ::Types::Ml::ModelType, null: true + + argument :id, ::Types::GlobalIDType[::Ml::Model], + required: true, + description: 'ID of the model.' + + def resolve(id:) + Gitlab::Graphql::Lazy.with_value(find_object(id: id)) do |ml_model| + ml_model if current_user.can?(:read_model_registry, ml_model&.project) + end + end + + private + + def find_object(id:) + GitlabSchema.find_by_gid(id) + end + end + end +end diff --git a/app/graphql/types/ml/model_type.rb b/app/graphql/types/ml/model_type.rb new file mode 100644 index 00000000000..2d686b185dd --- /dev/null +++ b/app/graphql/types/ml/model_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + module Ml + # rubocop: disable Graphql/AuthorizeTypes -- authorization in ModelDetailsResolver + class ModelType < ::Types::BaseObject + graphql_name 'MlModel' + description 'Machine learning model in the model registry' + + field :id, ::Types::GlobalIDType[::Ml::Model], null: false, description: 'ID of the model.' + + field :name, ::GraphQL::Types::String, null: false, description: 'Name of the model.' + + field :versions, ::Types::Ml::ModelVersionType.connection_type, null: true, + description: 'Versions of the model.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/ml/model_version_links_type.rb b/app/graphql/types/ml/model_version_links_type.rb new file mode 100644 index 00000000000..142f62bfad2 --- /dev/null +++ b/app/graphql/types/ml/model_version_links_type.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Types + module Ml + # rubocop: disable Graphql/AuthorizeTypes -- authorization in ModelDetailsResolver + class ModelVersionLinksType < BaseObject + graphql_name 'MLModelVersionLinks' + description 'Represents links to perform actions on the model version' + + present_using ::Ml::ModelVersionPresenter + + field :show_path, GraphQL::Types::String, + null: true, description: 'Path to the details page of the model version.', method: :path + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/ml/model_version_type.rb b/app/graphql/types/ml/model_version_type.rb new file mode 100644 index 00000000000..15c36a7a0d8 --- /dev/null +++ b/app/graphql/types/ml/model_version_type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module Ml + # rubocop: disable Graphql/AuthorizeTypes -- authorization in ModelDetailsResolver + class ModelVersionType < ::Types::BaseObject + graphql_name 'MlModelVersion' + description 'Version of a machine learning model' + + connection_type_class Types::LimitedCountableConnectionType + + field :id, ::Types::GlobalIDType[::Ml::ModelVersion], null: false, description: 'ID of the model version.' + + field :created_at, Types::TimeType, null: false, description: 'Date of creation.' + field :version, ::GraphQL::Types::String, null: false, description: 'Name of the version.' + + field :_links, ::Types::Ml::ModelVersionLinksType, null: false, method: :itself, + description: 'Map of links to perform actions on the model version.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 173e877d86c..6b7814754a4 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -212,6 +212,12 @@ module Types description: 'Abuse report labels.', resolver: Resolvers::AbuseReportLabelsResolver + field :ml_model, ::Types::Ml::ModelType, + null: true, + alpha: { milestone: '16.7' }, + description: 'Find machine learning models.', + resolver: Resolvers::Ml::ModelDetailResolver + def design_management DesignManagementObject.new(nil) end diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml index 8ea80700340..b1fa63dbf56 100644 --- a/app/views/groups/settings/_permissions.html.haml +++ b/app/views/groups/settings/_permissions.html.haml @@ -44,6 +44,7 @@ = render 'groups/settings/subgroup_creation_level', f: f, group: @group = render_if_exists 'groups/settings/prevent_forking', f: f, group: @group = render_if_exists 'groups/settings/service_access_tokens_expiration_enforced', f: f, group: @group + = render_if_exists 'groups/settings/enforce_ssh_certificates', f: f, group: @group = render 'groups/settings/two_factor_auth', f: f, group: @group = render_if_exists 'groups/personal_access_token_expiration_policy', f: f, group: @group = render 'groups/settings/membership', f: f, group: @group diff --git a/config/feature_flags/development/approval_rules_disable_joins.yml b/config/feature_flags/development/approval_rules_disable_joins.yml new file mode 100644 index 00000000000..1ee33b45ba7 --- /dev/null +++ b/config/feature_flags/development/approval_rules_disable_joins.yml @@ -0,0 +1,8 @@ +--- +name: approval_rules_disable_joins +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136588 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/431564 +milestone: '16.7' +type: development +group: group::source code +default_enabled: false diff --git a/config/feature_flags/development/duo_chat_absolute_doc_links.yml b/config/feature_flags/development/duo_chat_absolute_doc_links.yml new file mode 100644 index 00000000000..56c894beb78 --- /dev/null +++ b/config/feature_flags/development/duo_chat_absolute_doc_links.yml @@ -0,0 +1,8 @@ +--- +name: duo_chat_absolute_doc_links +introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136240' +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/431338 +milestone: '16.6' +type: development +group: group::ai framework +default_enabled: false diff --git a/config/feature_flags/development/enforce_ssh_certificates_via_settings.yml b/config/feature_flags/development/enforce_ssh_certificates_via_settings.yml new file mode 100644 index 00000000000..e721c2afe8c --- /dev/null +++ b/config/feature_flags/development/enforce_ssh_certificates_via_settings.yml @@ -0,0 +1,8 @@ +--- +name: enforce_ssh_certificates_via_settings +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136498 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/426235 +milestone: '16.7' +type: development +group: group::source code +default_enabled: false diff --git a/db/migrate/20231109165512_add_enforce_ssh_certificates_to_namespace_settings.rb b/db/migrate/20231109165512_add_enforce_ssh_certificates_to_namespace_settings.rb new file mode 100644 index 00000000000..98c4de0cb6a --- /dev/null +++ b/db/migrate/20231109165512_add_enforce_ssh_certificates_to_namespace_settings.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddEnforceSshCertificatesToNamespaceSettings < Gitlab::Database::Migration[2.2] + enable_lock_retries! + + milestone '16.7' + + def change + add_column :namespace_settings, :enforce_ssh_certificates, :boolean, default: false, null: false + end +end diff --git a/db/schema_migrations/20231109165512 b/db/schema_migrations/20231109165512 new file mode 100644 index 00000000000..1e3a229c9d1 --- /dev/null +++ b/db/schema_migrations/20231109165512 @@ -0,0 +1 @@ +2d3abd070d856db04eea298bbbe82681ca01912e19f978de876fce68ed2ada26
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index e93ca31dd51..d803e1551ef 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -19463,6 +19463,7 @@ CREATE TABLE namespace_settings ( service_access_tokens_expiration_enforced boolean DEFAULT true NOT NULL, product_analytics_enabled boolean DEFAULT false NOT NULL, allow_merge_without_pipeline boolean DEFAULT false NOT NULL, + enforce_ssh_certificates boolean DEFAULT false NOT NULL, CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)), CONSTRAINT namespace_settings_unique_project_download_limit_alertlist_size CHECK ((cardinality(unique_project_download_limit_alertlist) <= 100)), CONSTRAINT namespace_settings_unique_project_download_limit_allowlist_size CHECK ((cardinality(unique_project_download_limit_allowlist) <= 100)) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 5649b65c598..51b68078824 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -584,6 +584,22 @@ Returns [`Milestone`](#milestone). | ---- | ---- | ----------- | | <a id="querymilestoneid"></a>`id` | [`MilestoneID!`](#milestoneid) | Find a milestone by its ID. | +### `Query.mlModel` + +Find machine learning models. + +WARNING: +**Introduced** in 16.7. +This feature is an Experiment. It can be changed or removed at any time. + +Returns [`MlModel`](#mlmodel). + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="querymlmodelid"></a>`id` | [`MlModelID!`](#mlmodelid) | ID of the model. | + ### `Query.namespace` Find a namespace. @@ -11364,6 +11380,43 @@ The edge type for [`Milestone`](#milestone). | <a id="milestoneedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | | <a id="milestoneedgenode"></a>`node` | [`Milestone`](#milestone) | The item at the end of the edge. | +#### `MlModelVersionConnection` + +The connection type for [`MlModelVersion`](#mlmodelversion). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mlmodelversionconnectionedges"></a>`edges` | [`[MlModelVersionEdge]`](#mlmodelversionedge) | A list of edges. | +| <a id="mlmodelversionconnectionnodes"></a>`nodes` | [`[MlModelVersion]`](#mlmodelversion) | A list of nodes. | +| <a id="mlmodelversionconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +##### Fields with arguments + +###### `MlModelVersionConnection.count` + +Limited count of collection. Returns limit + 1 for counts greater than the limit. + +Returns [`Int!`](#int). + +####### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mlmodelversionconnectioncountlimit"></a>`limit` | [`Int`](#int) | Limit value to be applied to the count query. Default is 1000. | + +#### `MlModelVersionEdge` + +The edge type for [`MlModelVersion`](#mlmodelversion). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mlmodelversionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | +| <a id="mlmodelversionedgenode"></a>`node` | [`MlModelVersion`](#mlmodelversion) | The item at the end of the edge. | + #### `NamespaceCommitEmailConnection` The connection type for [`NamespaceCommitEmail`](#namespacecommitemail). @@ -20332,6 +20385,16 @@ Represents an entry from the Cloud License history. | <a id="locationblobpath"></a>`blobPath` | [`String`](#string) | HTTP URI path to view the input file in GitLab. | | <a id="locationpath"></a>`path` | [`String`](#string) | Path, relative to the root of the repository, of the filewhich was analyzed to detect the dependency. | +### `MLModelVersionLinks` + +Represents links to perform actions on the model version. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mlmodelversionlinksshowpath"></a>`showPath` | [`String`](#string) | Path to the details page of the model version. | + ### `MavenMetadata` Maven metadata. @@ -21911,6 +21974,31 @@ Contains statistics about a milestone. | <a id="milestonestatsclosedissuescount"></a>`closedIssuesCount` | [`Int`](#int) | Number of closed issues associated with the milestone. | | <a id="milestonestatstotalissuescount"></a>`totalIssuesCount` | [`Int`](#int) | Total number of issues associated with the milestone. | +### `MlModel` + +Machine learning model in the model registry. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mlmodelid"></a>`id` | [`MlModelID!`](#mlmodelid) | ID of the model. | +| <a id="mlmodelname"></a>`name` | [`String!`](#string) | Name of the model. | +| <a id="mlmodelversions"></a>`versions` | [`MlModelVersionConnection`](#mlmodelversionconnection) | Versions of the model. (see [Connections](#connections)) | + +### `MlModelVersion` + +Version of a machine learning model. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mlmodelversion_links"></a>`_links` | [`MLModelVersionLinks!`](#mlmodelversionlinks) | Map of links to perform actions on the model version. | +| <a id="mlmodelversioncreatedat"></a>`createdAt` | [`Time!`](#time) | Date of creation. | +| <a id="mlmodelversionid"></a>`id` | [`MlModelVersionID!`](#mlmodelversionid) | ID of the model version. | +| <a id="mlmodelversionversion"></a>`version` | [`String!`](#string) | Name of the version. | + ### `Namespace` #### Fields @@ -31232,6 +31320,18 @@ A `MilestoneID` is a global ID. It is encoded as a string. An example `MilestoneID` is: `"gid://gitlab/Milestone/1"`. +### `MlModelID` + +A `MlModelID` is a global ID. It is encoded as a string. + +An example `MlModelID` is: `"gid://gitlab/Ml::Model/1"`. + +### `MlModelVersionID` + +A `MlModelVersionID` is a global ID. It is encoded as a string. + +An example `MlModelVersionID` is: `"gid://gitlab/Ml::ModelVersion/1"`. + ### `NamespaceID` A `NamespaceID` is a global ID. It is encoded as a string. diff --git a/lib/banzai/filter/absolute_link_filter.rb b/lib/banzai/filter/absolute_link_filter.rb index cc7bf3ed556..7e3024c521c 100644 --- a/lib/banzai/filter/absolute_link_filter.rb +++ b/lib/banzai/filter/absolute_link_filter.rb @@ -6,13 +6,13 @@ module Banzai module Filter # HTML filter that converts relative urls into absolute ones. class AbsoluteLinkFilter < HTML::Pipeline::Filter - CSS = 'a.gfm' + CSS = 'a.gfm' XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze def call - return doc unless context[:only_path] == false + return doc if skip? - doc.xpath(XPATH).each do |el| + doc.xpath(self.class::XPATH).each do |el| process_link_attr el.attribute('href') end @@ -21,17 +21,21 @@ module Banzai protected + def skip? + context[:only_path] != false + end + def process_link_attr(html_attr) return if html_attr.blank? return if html_attr.value.start_with?('//') uri = URI(html_attr.value) - html_attr.value = absolute_link_attr(uri) if uri.relative? + html_attr.value = convert_link_href(uri) if uri.relative? rescue URI::Error # noop end - def absolute_link_attr(uri) + def convert_link_href(uri) # Here we really want to expand relative path to absolute path URI.join(Gitlab.config.gitlab.url, uri).to_s end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d7e4dbdbae6..1ed622246ed 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -23284,6 +23284,9 @@ msgstr "" msgid "GroupSettings|Enabling these features is your acceptance of the %{link_start}GitLab Testing Agreement%{link_end}." msgstr "" +msgid "GroupSettings|Enforce SSH Certificates" +msgstr "" + msgid "GroupSettings|Experiment and Beta features" msgstr "" diff --git a/spec/graphql/resolvers/ml/model_detail_resolver_spec.rb b/spec/graphql/resolvers/ml/model_detail_resolver_spec.rb new file mode 100644 index 00000000000..1da208eb4d8 --- /dev/null +++ b/spec/graphql/resolvers/ml/model_detail_resolver_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Ml::ModelDetailResolver, feature_category: :mlops do + include GraphqlHelpers + + describe '#resolve' do + let_it_be(:project) { create(:project) } + let_it_be(:model) { create(:ml_models, project: project) } + let_it_be(:user) { project.owner } + + let(:args) { { id: global_id_of(model) } } + let(:read_model_registry) { true } + + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(user, :read_model_registry, project) + .and_return(read_model_registry) + end + + subject { force(resolve(described_class, ctx: { current_user: user }, args: args)) } + + context 'when user is allowed and model exists' do + it { is_expected.to eq(model) } + end + + context 'when user does not have permission' do + let(:read_model_registry) { false } + + it { is_expected.to be_nil } + end + + context 'when model does not exist' do + let(:args) { { id: global_id_of(id: non_existing_record_id, model_name: 'Ml::Model') } } + + it { is_expected.to be_nil } + end + end +end diff --git a/spec/graphql/types/ml/model_type_spec.rb b/spec/graphql/types/ml/model_type_spec.rb new file mode 100644 index 00000000000..9320b251dd4 --- /dev/null +++ b/spec/graphql/types/ml/model_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['MlModel'], feature_category: :mlops do + specify { expect(described_class.description).to eq('Machine learning model in the model registry') } + + it 'includes all the package fields' do + expected_fields = %w[id name versions] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ml/model_version_links_type_spec.rb b/spec/graphql/types/ml/model_version_links_type_spec.rb new file mode 100644 index 00000000000..d2a11643c35 --- /dev/null +++ b/spec/graphql/types/ml/model_version_links_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['MLModelVersionLinks'], feature_category: :mlops do + it 'has the expected fields' do + expected_fields = %w[showPath] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ml/model_version_type_spec.rb b/spec/graphql/types/ml/model_version_type_spec.rb new file mode 100644 index 00000000000..03652c55e20 --- /dev/null +++ b/spec/graphql/types/ml/model_version_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['MlModelVersion'], feature_category: :mlops do + specify { expect(described_class.description).to eq('Version of a machine learning model') } + + it 'includes all the package fields' do + expected_fields = %w[id version created_at _links] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index 8bda738751d..f2a63fbeb57 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -137,4 +137,14 @@ RSpec.describe GitlabSchema.types['Query'], feature_category: :shared do is_expected.to have_graphql_resolver(Resolvers::BoardListResolver) end end + + describe 'mlModel field' do + subject { described_class.fields['mlModel'] } + + it 'returns metadata', :aggregate_failures do + is_expected.to have_graphql_type(Types::Ml::ModelType) + is_expected.to have_graphql_arguments(:id) + is_expected.to have_graphql_resolver(Resolvers::Ml::ModelDetailResolver) + end + end end |