Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_group_variables.vue104
-rw-r--r--app/assets/javascripts/ci_variable_list/graphql/mutations/group_add_variable.mutation.graphql30
-rw-r--r--app/assets/javascripts/ci_variable_list/graphql/mutations/group_delete_variable.mutation.graphql30
-rw-r--r--app/assets/javascripts/ci_variable_list/graphql/mutations/group_update_variable.mutation.graphql30
-rw-r--r--app/assets/javascripts/ci_variable_list/graphql/queries/group_variables.query.graphql17
-rw-r--r--app/assets/javascripts/ci_variable_list/graphql/resolvers.js48
-rw-r--r--app/assets/javascripts/ci_variable_list/index.js7
-rw-r--r--app/assets/javascripts/header_search/components/app.vue47
-rw-r--r--app/assets/javascripts/header_search/constants.js4
-rw-r--r--app/assets/javascripts/header_search/index.js11
-rw-r--r--app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue2
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue12
-rw-r--r--app/assets/javascripts/work_items/constants.js3
-rw-r--r--app/assets/stylesheets/pages/search.scss31
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss2
-rw-r--r--app/assets/stylesheets/themes/theme_helper.scss8
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb3
-rw-r--r--app/models/ml.rb6
-rw-r--r--app/models/ml/candidate.rb12
-rw-r--r--app/models/ml/candidate_metric.rb10
-rw-r--r--app/models/ml/candidate_param.rb10
-rw-r--r--app/models/ml/experiment.rb12
22 files changed, 384 insertions, 55 deletions
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_group_variables.vue b/app/assets/javascripts/ci_variable_list/components/ci_group_variables.vue
new file mode 100644
index 00000000000..3af83ffa8ed
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/components/ci_group_variables.vue
@@ -0,0 +1,104 @@
+<script>
+import createFlash from '~/flash';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import getGroupVariables from '../graphql/queries/group_variables.query.graphql';
+import {
+ ADD_MUTATION_ACTION,
+ DELETE_MUTATION_ACTION,
+ GRAPHQL_GROUP_TYPE,
+ UPDATE_MUTATION_ACTION,
+ genericMutationErrorText,
+ variableFetchErrorText,
+} from '../constants';
+import addGroupVariable from '../graphql/mutations/group_add_variable.mutation.graphql';
+import deleteGroupVariable from '../graphql/mutations/group_delete_variable.mutation.graphql';
+import updateGroupVariable from '../graphql/mutations/group_update_variable.mutation.graphql';
+import ciVariableSettings from './ci_variable_settings.vue';
+
+export default {
+ components: {
+ ciVariableSettings,
+ },
+ mixins: [glFeatureFlagsMixin()],
+ inject: ['endpoint', 'groupPath', 'groupId'],
+ data() {
+ return {
+ groupVariables: [],
+ };
+ },
+ apollo: {
+ groupVariables: {
+ query: getGroupVariables,
+ variables() {
+ return {
+ fullPath: this.groupPath,
+ };
+ },
+ update(data) {
+ return data?.group?.ciVariables?.nodes || [];
+ },
+ error() {
+ createFlash({ message: variableFetchErrorText });
+ },
+ },
+ },
+ computed: {
+ areScopedVariablesAvailable() {
+ return this.glFeatures.groupScopedCiVariables;
+ },
+ isLoading() {
+ return this.$apollo.queries.groupVariables.loading;
+ },
+ },
+ methods: {
+ addVariable(variable) {
+ this.variableMutation(ADD_MUTATION_ACTION, variable);
+ },
+ deleteVariable(variable) {
+ this.variableMutation(DELETE_MUTATION_ACTION, variable);
+ },
+ updateVariable(variable) {
+ this.variableMutation(UPDATE_MUTATION_ACTION, variable);
+ },
+ async variableMutation(mutationAction, variable) {
+ try {
+ const currentMutation = this.$options.mutationData[mutationAction];
+ const { data } = await this.$apollo.mutate({
+ mutation: currentMutation.action,
+ variables: {
+ endpoint: this.endpoint,
+ fullPath: this.groupPath,
+ groupId: convertToGraphQLId(GRAPHQL_GROUP_TYPE, this.groupId),
+ variable,
+ },
+ });
+
+ const { errors } = data[currentMutation.name];
+
+ if (errors.length > 0) {
+ createFlash({ message: errors[0] });
+ }
+ } catch {
+ createFlash({ message: genericMutationErrorText });
+ }
+ },
+ },
+ mutationData: {
+ [ADD_MUTATION_ACTION]: { action: addGroupVariable, name: 'addGroupVariable' },
+ [UPDATE_MUTATION_ACTION]: { action: updateGroupVariable, name: 'updateGroupVariable' },
+ [DELETE_MUTATION_ACTION]: { action: deleteGroupVariable, name: 'deleteGroupVariable' },
+ },
+};
+</script>
+
+<template>
+ <ci-variable-settings
+ :are-scoped-variables-available="areScopedVariablesAvailable"
+ :is-loading="isLoading"
+ :variables="groupVariables"
+ @add-variable="addVariable"
+ @delete-variable="deleteVariable"
+ @update-variable="updateVariable"
+ />
+</template>
diff --git a/app/assets/javascripts/ci_variable_list/graphql/mutations/group_add_variable.mutation.graphql b/app/assets/javascripts/ci_variable_list/graphql/mutations/group_add_variable.mutation.graphql
new file mode 100644
index 00000000000..f8e4dc55fa4
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/graphql/mutations/group_add_variable.mutation.graphql
@@ -0,0 +1,30 @@
+#import "~/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql"
+
+mutation addGroupVariable(
+ $variable: CiVariable!
+ $endpoint: String!
+ $fullPath: ID!
+ $groupId: ID!
+) {
+ addGroupVariable(
+ variable: $variable
+ endpoint: $endpoint
+ fullPath: $fullPath
+ groupId: $groupId
+ ) @client {
+ group {
+ id
+ ciVariables {
+ nodes {
+ ...BaseCiVariable
+ ... on CiGroupVariable {
+ environmentScope
+ masked
+ protected
+ }
+ }
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/ci_variable_list/graphql/mutations/group_delete_variable.mutation.graphql b/app/assets/javascripts/ci_variable_list/graphql/mutations/group_delete_variable.mutation.graphql
new file mode 100644
index 00000000000..310e4a6e551
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/graphql/mutations/group_delete_variable.mutation.graphql
@@ -0,0 +1,30 @@
+#import "~/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql"
+
+mutation deleteGroupVariable(
+ $variable: CiVariable!
+ $endpoint: String!
+ $fullPath: ID!
+ $groupId: ID!
+) {
+ deleteGroupVariable(
+ variable: $variable
+ endpoint: $endpoint
+ fullPath: $fullPath
+ groupId: $groupId
+ ) @client {
+ group {
+ id
+ ciVariables {
+ nodes {
+ ...BaseCiVariable
+ ... on CiGroupVariable {
+ environmentScope
+ masked
+ protected
+ }
+ }
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/ci_variable_list/graphql/mutations/group_update_variable.mutation.graphql b/app/assets/javascripts/ci_variable_list/graphql/mutations/group_update_variable.mutation.graphql
new file mode 100644
index 00000000000..5291942eb87
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/graphql/mutations/group_update_variable.mutation.graphql
@@ -0,0 +1,30 @@
+#import "~/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql"
+
+mutation updateGroupVariable(
+ $variable: CiVariable!
+ $endpoint: String!
+ $fullPath: ID!
+ $groupId: ID!
+) {
+ updateGroupVariable(
+ variable: $variable
+ endpoint: $endpoint
+ fullPath: $fullPath
+ groupId: $groupId
+ ) @client {
+ group {
+ id
+ ciVariables {
+ nodes {
+ ...BaseCiVariable
+ ... on CiGroupVariable {
+ environmentScope
+ masked
+ protected
+ }
+ }
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/ci_variable_list/graphql/queries/group_variables.query.graphql b/app/assets/javascripts/ci_variable_list/graphql/queries/group_variables.query.graphql
new file mode 100644
index 00000000000..c6dd6d4faaf
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/graphql/queries/group_variables.query.graphql
@@ -0,0 +1,17 @@
+#import "~/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql"
+
+query getGroupVariables($fullPath: ID!) {
+ group(fullPath: $fullPath) {
+ id
+ ciVariables {
+ nodes {
+ ...BaseCiVariable
+ ... on CiGroupVariable {
+ environmentScope
+ masked
+ protected
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/ci_variable_list/graphql/resolvers.js b/app/assets/javascripts/ci_variable_list/graphql/resolvers.js
index 7b57e97a4b8..be7e3f88cfd 100644
--- a/app/assets/javascripts/ci_variable_list/graphql/resolvers.js
+++ b/app/assets/javascripts/ci_variable_list/graphql/resolvers.js
@@ -4,8 +4,9 @@ import {
convertObjectPropsToSnakeCase,
} from '../../lib/utils/common_utils';
import { getIdFromGraphQLId } from '../../graphql_shared/utils';
-import { instanceString } from '../constants';
+import { GRAPHQL_GROUP_TYPE, groupString, instanceString } from '../constants';
import getAdminVariables from './queries/variables.query.graphql';
+import getGroupVariables from './queries/group_variables.query.graphql';
const prepareVariableForApi = ({ variable, destroy = false }) => {
return {
@@ -27,6 +28,20 @@ const mapVariableTypes = (variables = [], kind) => {
});
};
+const prepareGroupGraphQLResponse = ({ data, groupId, errors = [] }) => {
+ return {
+ errors,
+ group: {
+ __typename: GRAPHQL_GROUP_TYPE,
+ id: groupId,
+ ciVariables: {
+ __typename: 'CiVariableConnection',
+ nodes: mapVariableTypes(data.variables, groupString),
+ },
+ },
+ };
+};
+
const prepareAdminGraphQLResponse = ({ data, errors = [] }) => {
return {
errors,
@@ -37,6 +52,28 @@ const prepareAdminGraphQLResponse = ({ data, errors = [] }) => {
};
};
+const callGroupEndpoint = async ({
+ endpoint,
+ fullPath,
+ variable,
+ groupId,
+ cache,
+ destroy = false,
+}) => {
+ try {
+ const { data } = await axios.patch(endpoint, {
+ variables_attributes: [prepareVariableForApi({ variable, destroy })],
+ });
+ return prepareGroupGraphQLResponse({ data, groupId });
+ } catch (e) {
+ return prepareGroupGraphQLResponse({
+ data: cache.readQuery({ query: getGroupVariables, variables: { fullPath } }),
+ groupId,
+ errors: [...e.response.data],
+ });
+ }
+};
+
const callAdminEndpoint = async ({ endpoint, variable, cache, destroy = false }) => {
try {
const { data } = await axios.patch(endpoint, {
@@ -54,6 +91,15 @@ const callAdminEndpoint = async ({ endpoint, variable, cache, destroy = false })
export const resolvers = {
Mutation: {
+ addGroupVariable: async (_, { endpoint, fullPath, variable, groupId }, { cache }) => {
+ return callGroupEndpoint({ endpoint, fullPath, variable, groupId, cache });
+ },
+ updateGroupVariable: async (_, { endpoint, fullPath, variable, groupId }, { cache }) => {
+ return callGroupEndpoint({ endpoint, fullPath, variable, groupId, cache });
+ },
+ deleteGroupVariable: async (_, { endpoint, fullPath, variable, groupId }, { cache }) => {
+ return callGroupEndpoint({ endpoint, fullPath, variable, groupId, cache, destroy: true });
+ },
addAdminVariable: async (_, { endpoint, variable }, { cache }) => {
return callAdminEndpoint({ endpoint, variable, cache });
},
diff --git a/app/assets/javascripts/ci_variable_list/index.js b/app/assets/javascripts/ci_variable_list/index.js
index 713a453561e..a74af8aed12 100644
--- a/app/assets/javascripts/ci_variable_list/index.js
+++ b/app/assets/javascripts/ci_variable_list/index.js
@@ -3,6 +3,7 @@ import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import CiAdminVariables from './components/ci_admin_variables.vue';
+import CiGroupVariables from './components/ci_group_variables.vue';
import LegacyCiVariableSettings from './components/legacy_ci_variable_settings.vue';
import { resolvers } from './graphql/resolvers';
import createStore from './store';
@@ -32,7 +33,11 @@ const mountCiVariableListApp = (containerEl) => {
const parsedIsGroup = parseBoolean(isGroup);
const isProtectedByDefault = parseBoolean(protectedByDefault);
- const component = CiAdminVariables;
+ let component = CiAdminVariables;
+
+ if (parsedIsGroup) {
+ component = CiGroupVariables;
+ }
Vue.use(VueApollo);
diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue
index 72fec17ac9d..f4b939fb20f 100644
--- a/app/assets/javascripts/header_search/components/app.vue
+++ b/app/assets/javascripts/header_search/components/app.vue
@@ -23,6 +23,9 @@ import {
SEARCH_SHORTCUTS_MIN_CHARACTERS,
SCOPE_TOKEN_MAX_LENGTH,
INPUT_FIELD_PADDING,
+ IS_SEARCHING,
+ IS_FOCUSED,
+ IS_NOT_FOCUSED,
} from '../constants';
import HeaderSearchAutocompleteItems from './header_search_autocomplete_items.vue';
import HeaderSearchDefaultItems from './header_search_default_items.vue';
@@ -65,6 +68,7 @@ export default {
data() {
return {
showDropdown: false,
+ isFocused: false,
currentFocusIndex: SEARCH_BOX_INDEX,
};
},
@@ -92,20 +96,18 @@ export default {
if (!this.showDropdown || !this.isLoggedIn) {
return false;
}
-
return this.searchOptions?.length > 0;
},
showDefaultItems() {
return !this.searchText;
},
- showScopes() {
+ searchTermOverMin() {
return this.searchText?.length > SEARCH_SHORTCUTS_MIN_CHARACTERS;
},
defaultIndex() {
if (this.showDefaultItems) {
return SEARCH_BOX_INDEX;
}
-
return FIRST_DROPDOWN_INDEX;
},
@@ -132,12 +134,15 @@ export default {
count: this.searchOptions.length,
});
},
- searchBarStateIndicator() {
- const hasIcon =
- this.searchContext?.project || this.searchContext?.group ? 'has-icon' : 'has-no-icon';
- const isSearching = this.showScopes ? 'is-searching' : 'is-not-searching';
- const isActive = this.showSearchDropdown ? 'is-active' : 'is-not-active';
- return `${isActive} ${isSearching} ${hasIcon}`;
+ searchBarClasses() {
+ return {
+ [IS_SEARCHING]: this.searchTermOverMin,
+ [IS_FOCUSED]: this.isFocused,
+ [IS_NOT_FOCUSED]: !this.isFocused,
+ };
+ },
+ showScopeHelp() {
+ return this.searchTermOverMin && this.isFocused;
},
searchBarItem() {
return this.searchOptions?.[0];
@@ -158,11 +163,22 @@ export default {
...mapActions(['setSearch', 'fetchAutocompleteOptions', 'clearAutocomplete']),
openDropdown() {
this.showDropdown = true;
- this.$emit('toggleDropdown', this.showDropdown);
+ this.isFocused = true;
+ this.$emit('expandSearchBar', true);
},
closeDropdown() {
this.showDropdown = false;
- this.$emit('toggleDropdown', this.showDropdown);
+ },
+ collapseAndCloseSearchBar() {
+ // we need a delay on this method
+ // for the search bar not to remove
+ // the clear button from dom
+ // and register clicks on dropdown items
+ setTimeout(() => {
+ this.showDropdown = false;
+ this.isFocused = false;
+ this.$emit('collapseSearchBar');
+ }, 200);
},
submitSearch() {
if (this.search?.length <= SEARCH_SHORTCUTS_MIN_CHARACTERS && this.currentFocusIndex < 0) {
@@ -171,6 +187,7 @@ export default {
return visitUrl(this.currentFocusedOption?.url || this.searchQuery);
},
getAutocompleteOptions: debounce(function debouncedSearch(searchTerm) {
+ this.openDropdown();
if (!searchTerm) {
this.clearAutocomplete();
} else {
@@ -201,7 +218,7 @@ export default {
role="search"
:aria-label="$options.i18n.searchGitlab"
class="header-search gl-relative gl-rounded-base gl-w-full"
- :class="searchBarStateIndicator"
+ :class="searchBarClasses"
data-testid="header-search-form"
>
<gl-search-box-by-type
@@ -217,12 +234,13 @@ export default {
:aria-describedby="$options.SEARCH_INPUT_DESCRIPTION"
@focus="openDropdown"
@click="openDropdown"
+ @blur="collapseAndCloseSearchBar"
@input="getAutocompleteOptions"
@keydown.enter.stop.prevent="submitSearch"
@keydown.esc.stop.prevent="closeDropdown"
/>
<gl-token
- v-if="showScopes"
+ v-if="showScopeHelp"
v-gl-resize-observer-directive="observeTokenWidth"
class="in-search-scope-help"
:view-only="true"
@@ -242,6 +260,7 @@ export default {
}}
</gl-token>
<kbd
+ v-show="!isFocused"
v-gl-tooltip.bottom.hover.html
class="gl-absolute gl-right-3 gl-top-0 gl-z-index-1 keyboard-shortcut-helper"
:title="$options.i18n.kbdHelp"
@@ -278,7 +297,7 @@ export default {
/>
<template v-else>
<header-search-scoped-items
- v-if="showScopes"
+ v-if="searchTermOverMin"
:current-focused-option="currentFocusedOption"
/>
<header-search-autocomplete-items :current-focused-option="currentFocusedOption" />
diff --git a/app/assets/javascripts/header_search/constants.js b/app/assets/javascripts/header_search/constants.js
index a026386b2bd..3a20fb0216d 100644
--- a/app/assets/javascripts/header_search/constants.js
+++ b/app/assets/javascripts/header_search/constants.js
@@ -51,3 +51,7 @@ export const SCOPE_TOKEN_MAX_LENGTH = 36;
export const INPUT_FIELD_PADDING = 52;
export const HEADER_INIT_EVENTS = ['input', 'focus'];
+
+export const IS_SEARCHING = 'is-searching';
+export const IS_FOCUSED = 'is-focused';
+export const IS_NOT_FOCUSED = 'is-not-focused';
diff --git a/app/assets/javascripts/header_search/index.js b/app/assets/javascripts/header_search/index.js
index b2c505d569f..f6f5c6a14fa 100644
--- a/app/assets/javascripts/header_search/index.js
+++ b/app/assets/javascripts/header_search/index.js
@@ -26,12 +26,11 @@ export const initHeaderSearchApp = (search = '') => {
render(createElement) {
return createElement(HeaderSearchApp, {
on: {
- toggleDropdown: (isVisible = false) => {
- if (isVisible) {
- navBarEl?.classList.add('header-search-is-active');
- } else {
- navBarEl?.classList.remove('header-search-is-active');
- }
+ expandSearchBar: () => {
+ navBarEl?.classList.add('header-search-is-active');
+ },
+ collapseSearchBar: () => {
+ navBarEl?.classList.remove('header-search-is-active');
},
},
});
diff --git a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
index 6748a62e777..9cce6723bf7 100644
--- a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
+++ b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
@@ -68,7 +68,7 @@ export default {
}),
tableCell({
key: 'created_at',
- label: __('Date'),
+ label: __('Start date'),
}),
tableCell({
key: 'status',
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 8e862aa8b32..a5580c14a7a 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -264,9 +264,15 @@ export default {
data-testid="work-item-type"
/>
<gl-loading-icon v-if="updateInProgress" :inline="true" class="gl-mr-3" />
- <gl-badge v-if="workItem.confidential" variant="warning" icon="eye-slash" class="gl-mr-3">{{
- __('Confidential')
- }}</gl-badge>
+ <gl-badge
+ v-if="workItem.confidential"
+ v-gl-tooltip.bottom
+ :title="$options.i18n.confidentialTooltip"
+ variant="warning"
+ icon="eye-slash"
+ class="gl-mr-3 gl-cursor-help"
+ >{{ __('Confidential') }}</gl-badge
+ >
<work-item-actions
v-if="canUpdate || canDelete"
:work-item-id="workItem.id"
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index f1fc21b74f4..a2aea3cd327 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -26,6 +26,9 @@ export const WORK_ITEM_TYPE_ENUM_REQUIREMENTS = 'REQUIREMENTS';
export const i18n = {
fetchError: s__('WorkItem|Something went wrong when fetching the work item. Please try again.'),
updateError: s__('WorkItem|Something went wrong while updating the work item. Please try again.'),
+ confidentialTooltip: s__(
+ 'WorkItem|Only project members with at least the Reporter role, the author, and assignees can view or be notified about this task.',
+ ),
};
export const WIDGET_ICONS = {
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 012407cf09c..6c909b8d9fa 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -82,19 +82,17 @@ input[type='checkbox']:hover {
min-width: $search-input-field-x-min-width;
}
- &.is-active {
- &.is-searching {
- .in-search-scope-help {
- position: absolute;
- top: $gl-spacing-scale-2;
- right: 2.125rem;
- z-index: 2;
- }
+ &.is-searching {
+ .in-search-scope-help {
+ position: absolute;
+ top: $gl-spacing-scale-2;
+ right: 2.125rem;
+ z-index: 2;
}
}
- &.is-not-searching {
- .in-search-scope-help {
+ &.is-not-focused {
+ .gl-search-box-by-type-clear {
display: none;
}
}
@@ -104,19 +102,6 @@ input[type='checkbox']:hover {
box-shadow: none;
border-color: transparent;
}
-
- &.is-active {
- .keyboard-shortcut-helper {
- display: none;
- }
- }
-
- &.is-not-active {
- .btn.gl-clear-icon-button,
- .in-search-scope-help {
- display: none;
- }
- }
}
.header-search-dropdown-menu {
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index a86e489439f..ffe4d5dde9d 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -1892,7 +1892,7 @@ body.gl-dark .header-search input::placeholder {
body.gl-dark .header-search input:active::placeholder {
color: #868686;
}
-body.gl-dark .header-search.is-not-active .keyboard-shortcut-helper {
+body.gl-dark .header-search .keyboard-shortcut-helper {
color: #fafafa;
background-color: rgba(250, 250, 250, 0.2);
}
diff --git a/app/assets/stylesheets/themes/theme_helper.scss b/app/assets/stylesheets/themes/theme_helper.scss
index 2b6221a6c87..042e21cebd6 100644
--- a/app/assets/stylesheets/themes/theme_helper.scss
+++ b/app/assets/stylesheets/themes/theme_helper.scss
@@ -176,11 +176,9 @@
}
}
- &.is-not-active {
- .keyboard-shortcut-helper {
- color: $search-and-nav-links;
- background-color: rgba($search-and-nav-links, 0.2);
- }
+ .keyboard-shortcut-helper {
+ color: $search-and-nav-links;
+ background-color: rgba($search-and-nav-links, 0.2);
}
}
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index b1afac1f1c7..e164a834519 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -10,6 +10,9 @@ module Groups
before_action :define_variables, only: [:show]
before_action :push_licensed_features, only: [:show]
before_action :assign_variables_to_gon, only: [:show]
+ before_action do
+ push_frontend_feature_flag(:ci_variable_settings_graphql, @group)
+ end
feature_category :continuous_integration
urgency :low
diff --git a/app/models/ml.rb b/app/models/ml.rb
new file mode 100644
index 00000000000..e426ce851eb
--- /dev/null
+++ b/app/models/ml.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+module Ml
+ def self.table_name_prefix
+ 'ml_'
+ end
+end
diff --git a/app/models/ml/candidate.rb b/app/models/ml/candidate.rb
new file mode 100644
index 00000000000..e181217f01c
--- /dev/null
+++ b/app/models/ml/candidate.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Ml
+ class Candidate < ApplicationRecord
+ validates :iid, :experiment, presence: true
+
+ belongs_to :experiment, class_name: 'Ml::Experiment'
+ belongs_to :user
+ has_many :metrics, class_name: 'Ml::CandidateMetric'
+ has_many :params, class_name: 'Ml::CandidateParam'
+ end
+end
diff --git a/app/models/ml/candidate_metric.rb b/app/models/ml/candidate_metric.rb
new file mode 100644
index 00000000000..e03a8b83ee6
--- /dev/null
+++ b/app/models/ml/candidate_metric.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Ml
+ class CandidateMetric < ApplicationRecord
+ validates :candidate, presence: true
+ validates :name, length: { maximum: 250 }, presence: true
+
+ belongs_to :candidate, class_name: 'Ml::Candidate'
+ end
+end
diff --git a/app/models/ml/candidate_param.rb b/app/models/ml/candidate_param.rb
new file mode 100644
index 00000000000..cbdddcc8a1a
--- /dev/null
+++ b/app/models/ml/candidate_param.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Ml
+ class CandidateParam < ApplicationRecord
+ validates :candidate, presence: true
+ validates :name, :value, length: { maximum: 250 }, presence: true
+
+ belongs_to :candidate, class_name: 'Ml::Candidate'
+ end
+end
diff --git a/app/models/ml/experiment.rb b/app/models/ml/experiment.rb
new file mode 100644
index 00000000000..7ef9c70ba7e
--- /dev/null
+++ b/app/models/ml/experiment.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Ml
+ class Experiment < ApplicationRecord
+ validates :name, :iid, :project, presence: true
+ validates :iid, :name, uniqueness: { scope: :project, message: "should be unique in the project" }
+
+ belongs_to :project
+ belongs_to :user
+ has_many :candidates, class_name: 'Ml::Candidate'
+ end
+end