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
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/package-and-test/main.gitlab-ci.yml3
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml10
-rw-r--r--app/assets/javascripts/environments/mount_show.js2
-rw-r--r--app/assets/javascripts/graphql_shared/utils.js25
-rw-r--r--app/assets/javascripts/security_configuration/components/app.vue47
-rw-r--r--app/assets/javascripts/security_configuration/components/constants.js18
-rw-r--r--app/assets/javascripts/security_configuration/index.js6
-rw-r--r--app/assets/javascripts/security_configuration/utils.js8
-rw-r--r--app/assets/javascripts/vue_shared/security_reports/constants.js1
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue42
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue7
-rw-r--r--app/assets/stylesheets/page_bundles/tree.scss7
-rw-r--r--app/assets/stylesheets/themes/dark_mode_overrides.scss5
-rw-r--r--app/controllers/projects/merge_requests_controller.rb14
-rw-r--r--app/helpers/sessions_helper.rb4
-rw-r--r--app/models/application_setting.rb3
-rw-r--r--app/models/bulk_imports/configuration.rb2
-rw-r--r--app/models/bulk_imports/entity.rb27
-rw-r--r--app/models/clusters/kubernetes_namespace.rb6
-rw-r--r--app/models/container_repository.rb17
-rw-r--r--app/models/cycle_analytics/project_level_stage_adapter.rb12
-rw-r--r--app/models/deployment.rb8
-rw-r--r--app/models/design_management/design.rb4
-rw-r--r--app/models/design_management/version.rb8
-rw-r--r--app/models/diff_discussion.rb14
-rw-r--r--app/models/diff_viewer/base.rb5
-rw-r--r--app/models/integrations/prometheus.rb7
-rw-r--r--app/models/organization.rb6
-rw-r--r--app/models/project.rb6
-rw-r--r--app/models/user.rb7
-rw-r--r--app/services/members/destroy_service.rb45
-rw-r--r--app/views/admin/sessions/_signin_box.html.haml2
-rw-r--r--app/views/admin/sessions/new.html.haml2
-rw-r--r--app/views/admin/sessions/two_factor.html.haml2
-rw-r--r--app/views/authentication/_authenticate.html.haml2
-rw-r--r--app/views/devise/sessions/_new_base.html.haml16
-rw-r--r--app/views/devise/sessions/_new_crowd.html.haml2
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml4
-rw-r--r--app/views/devise/sessions/two_factor.html.haml5
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml4
-rw-r--r--app/views/projects/_self_monitoring_deprecation_notice.html.haml13
-rw-r--r--app/views/projects/empty.html.haml1
-rw-r--r--app/views/projects/show.html.haml1
-rw-r--r--db/docs/organizations.yml10
-rw-r--r--db/migrate/20230420012220_create_organizations.rb9
-rw-r--r--db/schema_migrations/202304200122201
-rw-r--r--db/structure.sql20
-rw-r--r--doc/user/ai_features.md99
-rw-r--r--lib/tasks/gitlab/db.rake6
-rw-r--r--locale/gitlab.pot14
-rw-r--r--spec/factories/organizations.rb5
-rw-r--r--spec/features/users/login_spec.rb82
-rw-r--r--spec/frontend/graphql_shared/utils_spec.js71
-rw-r--r--spec/frontend/issues/show/components/description_spec.js9
-rw-r--r--spec/frontend/security_configuration/components/app_spec.js41
-rw-r--r--spec/frontend/security_configuration/mock_data.js18
-rw-r--r--spec/frontend/security_configuration/utils_spec.js38
-rw-r--r--spec/frontend/work_items/components/work_item_actions_spec.js16
-rw-r--r--spec/frontend/work_items/components/work_item_assignees_spec.js27
-rw-r--r--spec/frontend/work_items/components/work_item_detail_spec.js35
-rw-r--r--spec/frontend/work_items/mock_data.js93
-rw-r--r--spec/frontend/work_items/router_spec.js4
-rw-r--r--spec/helpers/sessions_helper_spec.rb20
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb6
-rw-r--r--spec/models/integrations/prometheus_spec.rb48
-rw-r--r--spec/models/organization_spec.rb10
-rw-r--r--spec/models/project_spec.rb18
-rw-r--r--spec/models/user_spec.rb14
-rw-r--r--spec/services/members/destroy_service_spec.rb141
-rw-r--r--spec/support/helpers/user_login_helper.rb16
-rw-r--r--spec/tasks/gitlab/db_rake_spec.rb12
72 files changed, 665 insertions, 650 deletions
diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
index 8db781ddff2..e61eb0b5ca6 100644
--- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml
+++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
@@ -130,7 +130,8 @@ trigger-omnibus-env:
echo "OMNIBUS_GITLAB_BUILD_ON_ALL_OS=${OMNIBUS_GITLAB_BUILD_ON_ALL_OS:-false}" >> $BUILD_ENV
echo "GITLAB_ASSETS_TAG=$(assets_image_tag)" >> $BUILD_ENV
echo "EE=$([[ $FOSS_ONLY == '1' ]] && echo 'false' || echo 'true')" >> $BUILD_ENV
- echo "TRIGGER_BRANCH=$([[ "$CI_COMMIT_REF_NAME" =~ ^[0-9-]+-stable(-ee)?$ ]] && echo ${CI_COMMIT_REF_NAME%-ee} || echo 'master')" >> $BUILD_ENV
+ target_branch_name="${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-${CI_COMMIT_REF_NAME}}"
+ echo "TRIGGER_BRANCH=$([[ "${target_branch_name}" =~ ^[0-9-]+-stable(-ee)?$ ]] && echo ${target_branch_name%-ee} || echo 'master')" >> $BUILD_ENV
echo "Built environment file for omnibus build:"
cat $BUILD_ENV
artifacts:
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index a710a8ea871..a54bd87b1ae 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -506,16 +506,6 @@ Layout/ArgumentAlignment:
- 'app/graphql/types/work_items/widgets/start_and_due_date_update_input_type.rb'
- 'app/graphql/types/x509_certificate_type.rb'
- 'app/graphql/types/x509_issuer_type.rb'
- - 'app/models/bulk_imports/configuration.rb'
- - 'app/models/bulk_imports/entity.rb'
- - 'app/models/clusters/kubernetes_namespace.rb'
- - 'app/models/container_repository.rb'
- - 'app/models/cycle_analytics/project_level_stage_adapter.rb'
- - 'app/models/deployment.rb'
- - 'app/models/design_management/design.rb'
- - 'app/models/design_management/version.rb'
- - 'app/models/diff_discussion.rb'
- - 'app/models/diff_viewer/base.rb'
- 'app/models/discussion.rb'
- 'app/models/environment.rb'
- 'app/models/generic_commit_status.rb'
diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js
index 364f68cefb7..cc13a237aca 100644
--- a/app/assets/javascripts/environments/mount_show.js
+++ b/app/assets/javascripts/environments/mount_show.js
@@ -94,7 +94,7 @@ export const initPage = async () => {
router,
provide: {
projectPath: dataSet.projectFullPath,
- graphqlEtagKey: dataSet.graphqlEtagPath,
+ graphqlEtagKey: dataSet.graphqlEtagKey,
},
render(createElement) {
return createElement('router-view');
diff --git a/app/assets/javascripts/graphql_shared/utils.js b/app/assets/javascripts/graphql_shared/utils.js
index 198d9f980f0..6a64e8a2fa8 100644
--- a/app/assets/javascripts/graphql_shared/utils.js
+++ b/app/assets/javascripts/graphql_shared/utils.js
@@ -16,7 +16,10 @@ export const isGid = (id) => {
return false;
};
-const parseGid = (gid) => parseInt(`${gid}`.replace(/gid:\/\/gitlab\/.*\//g, ''), 10);
+const parseGid = (gid) => {
+ const [type, id] = `${gid}`.replace(/gid:\/\/gitlab\//g, '').split('/');
+ return { type, id };
+};
/**
* Ids generated by GraphQL endpoints are usually in the format
@@ -27,8 +30,24 @@ const parseGid = (gid) => parseInt(`${gid}`.replace(/gid:\/\/gitlab\/.*\//g, '')
* @returns {Number}
*/
export const getIdFromGraphQLId = (gid = '') => {
- const parsedGid = parseGid(gid);
- return Number.isInteger(parsedGid) ? parsedGid : null;
+ const rawId = isGid(gid) ? parseGid(gid).id : gid;
+ const id = parseInt(rawId, 10);
+ return Number.isInteger(id) ? id : null;
+};
+
+/**
+ * Ids generated by GraphQL endpoints are usually in the format
+ * gid://gitlab/Environments/123. This method extracts Type string
+ * from the Id path
+ *
+ * @param {String} gid GraphQL global ID
+ * @returns {String}
+ */
+export const getTypeFromGraphQLId = (gid = '') => {
+ if (!isGid(gid)) return null;
+
+ const { type } = parseGid(gid);
+ return type || null;
};
export const MutationOperationMode = {
diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue
index 66b8db1f764..d57b3fda342 100644
--- a/app/assets/javascripts/security_configuration/components/app.vue
+++ b/app/assets/javascripts/security_configuration/components/app.vue
@@ -12,7 +12,6 @@ import FeatureCard from './feature_card.vue';
import TrainingProviderList from './training_provider_list.vue';
export const i18n = {
- compliance: s__('SecurityConfiguration|Compliance'),
configurationHistory: s__('SecurityConfiguration|Configuration history'),
securityTesting: s__('SecurityConfiguration|Security testing'),
latestPipelineDescription: s__(
@@ -59,10 +58,6 @@ export default {
type: Array,
required: true,
},
- augmentedComplianceFeatures: {
- type: Array,
- required: true,
- },
gitlabCiPresent: {
type: Boolean,
required: false,
@@ -101,9 +96,7 @@ export default {
},
computed: {
canUpgrade() {
- return [...this.augmentedSecurityFeatures, ...this.augmentedComplianceFeatures].some(
- ({ available }) => !available,
- );
+ return [...this.augmentedSecurityFeatures].some(({ available }) => !available);
},
canViewCiHistory() {
return Boolean(this.gitlabCiPresent && this.gitlabCiHistoryPath);
@@ -226,44 +219,6 @@ export default {
</section-layout>
</gl-tab>
<gl-tab
- data-testid="compliance-testing-tab"
- :title="$options.i18n.compliance"
- query-param-value="compliance-testing"
- >
- <section-layout :heading="$options.i18n.compliance">
- <template #description>
- <p>
- <span data-testid="latest-pipeline-info-compliance">
- <gl-sprintf
- v-if="latestPipelinePath"
- :message="$options.i18n.latestPipelineDescription"
- >
- <template #link="{ content }">
- <gl-link :href="latestPipelinePath">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </span>
-
- {{ $options.i18n.description }}
- </p>
- <p v-if="canViewCiHistory">
- <gl-link data-testid="compliance-view-history-link" :href="gitlabCiHistoryPath">{{
- $options.i18n.configurationHistory
- }}</gl-link>
- </p>
- </template>
- <template #features>
- <feature-card
- v-for="feature in augmentedComplianceFeatures"
- :key="feature.type"
- :feature="feature"
- class="gl-mb-6"
- @error="onError"
- />
- </template>
- </section-layout>
- </gl-tab>
- <gl-tab
data-testid="vulnerability-management-tab"
:title="$options.i18n.vulnerabilityManagement"
query-param-value="vulnerability-management"
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index d46e9983a44..3bf0401ef5e 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -13,7 +13,6 @@ import {
REPORT_TYPE_COVERAGE_FUZZING,
REPORT_TYPE_CORPUS_MANAGEMENT,
REPORT_TYPE_API_FUZZING,
- REPORT_TYPE_LICENSE_COMPLIANCE,
} from '~/vue_shared/security_reports/constants';
import kontraLogo from 'images/vulnerability/kontra-logo.svg';
@@ -150,14 +149,6 @@ export const API_FUZZING_NAME = __('API Fuzzing');
export const API_FUZZING_DESCRIPTION = __('Find bugs in your code with API fuzzing.');
export const API_FUZZING_HELP_PATH = helpPagePath('user/application_security/api_fuzzing/index');
-export const LICENSE_COMPLIANCE_NAME = __('License Compliance');
-export const LICENSE_COMPLIANCE_DESCRIPTION = __(
- 'Search your project dependencies for their licenses and apply policies.',
-);
-export const LICENSE_COMPLIANCE_HELP_PATH = helpPagePath(
- 'user/compliance/license_compliance/index',
-);
-
export const CLUSTER_IMAGE_SCANNING_NAME = s__('ciReport|Cluster Image Scanning');
export const SCANNER_NAMES_MAP = {
@@ -273,15 +264,6 @@ export const securityFeatures = [
},
];
-export const complianceFeatures = [
- {
- name: LICENSE_COMPLIANCE_NAME,
- description: LICENSE_COMPLIANCE_DESCRIPTION,
- helpPath: LICENSE_COMPLIANCE_HELP_PATH,
- type: REPORT_TYPE_LICENSE_COMPLIANCE,
- },
-];
-
export const featureToMutationMap = {
[REPORT_TYPE_SAST]: {
mutationId: 'configureSast',
diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js
index 637d510e684..aa3c9c87622 100644
--- a/app/assets/javascripts/security_configuration/index.js
+++ b/app/assets/javascripts/security_configuration/index.js
@@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils';
import SecurityConfigurationApp from './components/app.vue';
-import { securityFeatures, complianceFeatures } from './components/constants';
+import { securityFeatures } from './components/constants';
import { augmentFeatures } from './utils';
export const initSecurityConfiguration = (el) => {
@@ -28,9 +28,8 @@ export const initSecurityConfiguration = (el) => {
vulnerabilityTrainingDocsPath,
} = el.dataset;
- const { augmentedSecurityFeatures, augmentedComplianceFeatures } = augmentFeatures(
+ const { augmentedSecurityFeatures } = augmentFeatures(
securityFeatures,
- complianceFeatures,
features ? JSON.parse(features) : [],
);
@@ -48,7 +47,6 @@ export const initSecurityConfiguration = (el) => {
render(createElement) {
return createElement(SecurityConfigurationApp, {
props: {
- augmentedComplianceFeatures,
augmentedSecurityFeatures,
latestPipelinePath,
gitlabCiHistoryPath,
diff --git a/app/assets/javascripts/security_configuration/utils.js b/app/assets/javascripts/security_configuration/utils.js
index df23698ba7e..72e6d870e13 100644
--- a/app/assets/javascripts/security_configuration/utils.js
+++ b/app/assets/javascripts/security_configuration/utils.js
@@ -2,19 +2,18 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { SCANNER_NAMES_MAP } from '~/security_configuration/components/constants';
/**
- * This function takes in 3 arrays of objects, securityFeatures, complianceFeatures and features.
- * securityFeatures and complianceFeatures are static arrays living in the constants.
+ * This function takes in 3 arrays of objects, securityFeatures and features.
+ * securityFeatures are static arrays living in the constants.
* features is dynamic and coming from the backend.
* This function builds a superset of those arrays.
* It looks for matching keys within the dynamic and the static arrays
* and will enrich the objects with the available static data.
* @param [{}] securityFeatures
- * @param [{}] complianceFeatures
* @param [{}] features
* @returns {Object} Object with enriched features from constants divided into Security and Compliance Features
*/
-export const augmentFeatures = (securityFeatures, complianceFeatures, features = []) => {
+export const augmentFeatures = (securityFeatures, features = []) => {
const featuresByType = features.reduce((acc, feature) => {
acc[feature.type] = convertObjectPropsToCamelCase(feature, { deep: true });
return acc;
@@ -39,7 +38,6 @@ export const augmentFeatures = (securityFeatures, complianceFeatures, features =
return {
augmentedSecurityFeatures: securityFeatures.map((feature) => augmentFeature(feature)),
- augmentedComplianceFeatures: complianceFeatures.map((feature) => augmentFeature(feature)),
};
};
diff --git a/app/assets/javascripts/vue_shared/security_reports/constants.js b/app/assets/javascripts/vue_shared/security_reports/constants.js
index 8b523645973..a1d75e08be9 100644
--- a/app/assets/javascripts/vue_shared/security_reports/constants.js
+++ b/app/assets/javascripts/vue_shared/security_reports/constants.js
@@ -27,7 +27,6 @@ export const REPORT_TYPE_CONTAINER_SCANNING = 'container_scanning';
export const REPORT_TYPE_CLUSTER_IMAGE_SCANNING = 'cluster_image_scanning';
export const REPORT_TYPE_COVERAGE_FUZZING = 'coverage_fuzzing';
export const REPORT_TYPE_CORPUS_MANAGEMENT = 'corpus_management';
-export const REPORT_TYPE_LICENSE_COMPLIANCE = 'license_scanning';
export const REPORT_TYPE_API_FUZZING = 'api_fuzzing';
export const REPORT_TYPE_MANUALLY_ADDED = 'generic';
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 ddf7c789c09..ef99001c0e8 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -46,7 +46,8 @@ import workItemAssigneesSubscription from '../graphql/work_item_assignees.subscr
import workItemMilestoneSubscription from '../graphql/work_item_milestone.subscription.graphql';
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
import updateWorkItemTaskMutation from '../graphql/update_work_item_task.mutation.graphql';
-import { findHierarchyWidgetChildren, getWorkItemQuery } from '../utils';
+import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
+import { findHierarchyWidgetChildren } from '../utils';
import WorkItemTree from './work_item_links/work_item_tree.vue';
import WorkItemActions from './work_item_actions.vue';
@@ -137,18 +138,15 @@ export default {
},
apollo: {
workItem: {
- query() {
- return getWorkItemQuery(this.fetchByIid);
- },
+ query: workItemByIidQuery,
variables() {
return this.queryVariables;
},
skip() {
- return !this.workItemId && !this.workItemIid;
+ return !this.workItemIid;
},
update(data) {
- const workItem = this.fetchByIid ? data.workspace.workItems.nodes[0] : data.workItem;
- return workItem ?? {};
+ return data.workspace.workItems.nodes[0] ?? {};
},
error() {
this.setEmptyState();
@@ -316,18 +314,11 @@ export default {
workItemNotes() {
return this.isWidgetPresent(WIDGET_TYPE_NOTES);
},
- fetchByIid() {
- return true;
- },
queryVariables() {
- return this.fetchByIid
- ? {
- fullPath: this.fullPath,
- iid: this.workItemIid,
- }
- : {
- id: this.workItemId,
- };
+ return {
+ fullPath: this.fullPath,
+ iid: this.workItemIid,
+ };
},
children() {
return this.workItem ? findHierarchyWidgetChildren(this.workItem) : [];
@@ -408,14 +399,12 @@ export default {
},
toggleChildFromCache(workItem, childId, store) {
const sourceData = store.readQuery({
- query: getWorkItemQuery(this.fetchByIid),
+ query: workItemByIidQuery,
variables: this.queryVariables,
});
const newData = produce(sourceData, (draftState) => {
- const widgets = this.fetchByIid
- ? draftState.workspace.workItems.nodes[0].widgets
- : draftState.workItem.widgets;
+ const { widgets } = draftState.workspace.workItems.nodes[0];
const widgetHierarchy = widgets.find((widget) => widget.type === WIDGET_TYPE_HIERARCHY);
const index = widgetHierarchy.children.nodes.findIndex((child) => child.id === childId);
@@ -428,7 +417,7 @@ export default {
});
store.writeQuery({
- query: getWorkItemQuery(this.fetchByIid),
+ query: workItemByIidQuery,
variables: this.queryVariables,
data: newData,
});
@@ -475,12 +464,8 @@ export default {
this.$emit('has-notes');
},
updateUrl(modalWorkItem) {
- const params = this.fetchByIid
- ? { work_item_iid: modalWorkItem?.iid }
- : { work_item_id: getIdFromGraphQLId(modalWorkItem?.id) };
-
updateHistory({
- url: setUrlParams(params),
+ url: setUrlParams({ work_item_iid: modalWorkItem?.iid }),
replace: true,
});
},
@@ -722,7 +707,6 @@ export default {
:can-update="canUpdate"
:project-path="fullPath"
:confidential="workItem.confidential"
- :fetch-by-iid="fetchByIid"
@addWorkItemChild="addChild"
@removeChild="removeChild"
@show-modal="openInModal"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
index 3e5d9453fef..4dcc4d51957 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
@@ -61,11 +61,6 @@ export default {
type: String,
required: true,
},
- fetchByIid: {
- type: Boolean,
- required: false,
- default: false,
- },
},
data() {
return {
@@ -174,7 +169,7 @@ export default {
:work-item-id="workItemId"
:work-item-iid="workItemIid"
:work-item-type="workItemType"
- :fetch-by-iid="fetchByIid"
+ fetch-by-iid
@removeChild="$emit('removeChild', $event)"
@show-modal="showModal"
/>
diff --git a/app/assets/stylesheets/page_bundles/tree.scss b/app/assets/stylesheets/page_bundles/tree.scss
index 9d13ccc676d..a13b8704095 100644
--- a/app/assets/stylesheets/page_bundles/tree.scss
+++ b/app/assets/stylesheets/page_bundles/tree.scss
@@ -219,10 +219,3 @@
width: calc(100% + 24px);
margin: -28px -12px 0;
}
-
-.ai-genie-chat-message {
- pre,
- code {
- @include gl-font-sm;
- }
-}
diff --git a/app/assets/stylesheets/themes/dark_mode_overrides.scss b/app/assets/stylesheets/themes/dark_mode_overrides.scss
index 619336686e8..3a18f735217 100644
--- a/app/assets/stylesheets/themes/dark_mode_overrides.scss
+++ b/app/assets/stylesheets/themes/dark_mode_overrides.scss
@@ -305,3 +305,8 @@ body.gl-dark {
// lightens chat bubble in darkmode as $gray-50 matches drawer background. See tanuki_bot_chat.scss
background-color: $gray-100;
}
+
+.ai-genie-chat,
+.ai-genie-chat .gl-form-input {
+ background-color: $gray-10;
+}
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index d967aa89eb7..dbcbd2467b6 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -51,7 +51,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:code_quality_inline_drawer, project)
push_frontend_feature_flag(:hide_create_issue_resolve_all, project)
push_frontend_feature_flag(:auto_merge_labels_mr_widget, project)
- push_frontend_feature_flag(:summarize_my_code_review, current_user)
+ push_force_frontend_feature_flag(:summarize_my_code_review, summarize_my_code_review_enabled?)
push_frontend_feature_flag(:mr_activity_filters, current_user)
end
@@ -603,6 +603,18 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
Date.strptime(date, "%Y-%m-%d")&.to_time&.to_i if date
rescue Date::Error, TypeError
end
+
+ def summarize_my_code_review_enabled?
+ namespace = project&.group&.root_ancestor
+ return false if namespace.nil?
+
+ Feature.enabled?(:summarize_my_code_review, current_user) &&
+ namespace.group_namespace? &&
+ namespace.licensed_feature_available?(:summarize_my_mr_code_review) &&
+ namespace.experiment_features_enabled &&
+ namespace.third_party_ai_features_enabled &&
+ merge_request.send_to_ai?
+ end
end
Projects::MergeRequestsController.prepend_mod_with('Projects::MergeRequestsController')
diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb
index 8251e1cba8a..9ef347fff16 100644
--- a/app/helpers/sessions_helper.rb
+++ b/app/helpers/sessions_helper.rb
@@ -48,4 +48,8 @@ module SessionsHelper
# Moved to Gitlab::Utils::Email in 15.9
Gitlab::Utils::Email.obfuscated_email(email)
end
+
+ def remember_me_enabled?
+ Gitlab::CurrentSettings.remember_me_enabled?
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index aba8727c030..8f57d73a6f4 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -38,10 +38,7 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
add_authentication_token_field :static_objects_external_storage_auth_token, encrypted: :required
add_authentication_token_field :error_tracking_access_token, encrypted: :required
- belongs_to :self_monitoring_project, class_name: "Project", foreign_key: :instance_administration_project_id,
- inverse_of: :application_setting
belongs_to :push_rule
- alias_attribute :self_monitoring_project_id, :instance_administration_project_id
belongs_to :instance_group, class_name: "Group", foreign_key: :instance_administrators_group_id,
inverse_of: :application_setting
diff --git a/app/models/bulk_imports/configuration.rb b/app/models/bulk_imports/configuration.rb
index 3b263ed0340..6d9f598583e 100644
--- a/app/models/bulk_imports/configuration.rb
+++ b/app/models/bulk_imports/configuration.rb
@@ -9,7 +9,7 @@ class BulkImports::Configuration < ApplicationRecord
validates :url, :access_token, length: { maximum: 255 }, presence: true
validates :url, public_url: { schemes: %w[http https], enforce_sanitization: true, ascii_only: true },
- allow_nil: true
+ allow_nil: true
attr_encrypted :url,
key: Settings.attr_encrypted_db_key_base_32,
diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb
index b3540917197..94e4a8165eb 100644
--- a/app/models/bulk_imports/entity.rb
+++ b/app/models/bulk_imports/entity.rb
@@ -41,22 +41,14 @@ class BulkImports::Entity < ApplicationRecord
validates :project, absence: true, if: :group
validates :group, absence: true, if: :project
validates :source_type, presence: true
- validates :source_full_path,
- presence: true,
- format: { with: Gitlab::Regex.bulk_import_source_full_path_regex,
- message: Gitlab::Regex.bulk_import_source_full_path_regex_message }
+ validates :source_full_path, presence: true, format: {
+ with: Gitlab::Regex.bulk_import_source_full_path_regex,
+ message: Gitlab::Regex.bulk_import_source_full_path_regex_message
+ }
- validates :destination_name,
- presence: true,
- if: -> { group || project }
-
- validates :destination_namespace,
- exclusion: [nil],
- if: :group
-
- validates :destination_namespace,
- presence: true,
- if: :project?
+ validates :destination_name, presence: true, if: -> { group || project }
+ validates :destination_namespace, exclusion: [nil], if: :group
+ validates :destination_namespace, presence: true, if: :project?
validate :validate_parent_is_a_group, if: :parent
validate :validate_imported_entity_type
@@ -72,9 +64,8 @@ class BulkImports::Entity < ApplicationRecord
alias_attribute :destination_slug, :destination_name
- delegate :default_project_visibility,
- :default_group_visibility,
- to: :'Gitlab::CurrentSettings.current_application_settings'
+ delegate :default_project_visibility, :default_group_visibility,
+ to: :'Gitlab::CurrentSettings.current_application_settings'
state_machine :status, initial: :created do
state :created, value: 0
diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb
index 42332bdc193..dfb5c4cc5eb 100644
--- a/app/models/clusters/kubernetes_namespace.rb
+++ b/app/models/clusters/kubernetes_namespace.rb
@@ -22,9 +22,9 @@ module Clusters
delegate :api_url, to: :platform_kubernetes, allow_nil: true
attr_encrypted :service_account_token,
- mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_truncated,
- algorithm: 'aes-256-cbc'
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-cbc'
scope :has_service_account_token, -> { where.not(encrypted_service_account_token: nil) }
scope :with_environment_name, -> (name) { joins(:environment).where(environments: { name: name }) }
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 62b6effeb89..0f0abeae795 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -38,8 +38,8 @@ class ContainerRepository < ApplicationRecord
validates :migration_aborted_in_state, inclusion: { in: ABORTABLE_MIGRATION_STATES }, allow_nil: true
validates :migration_retries_count, presence: true,
- numericality: { greater_than_or_equal_to: 0 },
- allow_nil: false
+ numericality: { greater_than_or_equal_to: 0 },
+ allow_nil: false
enum status: { delete_scheduled: 0, delete_failed: 1, delete_ongoing: 2 }
enum expiration_policy_cleanup_status: { cleanup_unscheduled: 0, cleanup_scheduled: 1, cleanup_unfinished: 2, cleanup_ongoing: 3 }
@@ -124,9 +124,7 @@ class ContainerRepository < ApplicationRecord
state :import_done
state :import_skipped do
- validates :migration_skipped_reason,
- :migration_skipped_at,
- presence: true
+ validates :migration_skipped_reason, :migration_skipped_at, presence: true
end
state :import_aborted do
@@ -603,8 +601,7 @@ class ContainerRepository < ApplicationRecord
end
def self.build_from_path(path)
- self.new(project: path.repository_project,
- name: path.repository_name)
+ self.new(project: path.repository_project, name: path.repository_name)
end
def self.find_or_create_from_path(path)
@@ -622,13 +619,11 @@ class ContainerRepository < ApplicationRecord
end
def self.find_by_path!(path)
- self.find_by!(project: path.repository_project,
- name: path.repository_name)
+ self.find_by!(project: path.repository_project, name: path.repository_name)
end
def self.find_by_path(path)
- self.find_by(project: path.repository_project,
- name: path.repository_name)
+ self.find_by(project: path.repository_project, name: path.repository_name)
end
private
diff --git a/app/models/cycle_analytics/project_level_stage_adapter.rb b/app/models/cycle_analytics/project_level_stage_adapter.rb
index 9b9c0822f63..ae21a4a6bfe 100644
--- a/app/models/cycle_analytics/project_level_stage_adapter.rb
+++ b/app/models/cycle_analytics/project_level_stage_adapter.rb
@@ -16,12 +16,12 @@ module CycleAnalytics
presenter = Analytics::CycleAnalytics::StagePresenter.new(stage)
serializer.new.represent(ProjectLevelStage.new(
- title: presenter.title,
- description: presenter.description,
- legend: presenter.legend,
- name: stage.name,
- project_median: median
- ))
+ title: presenter.title,
+ description: presenter.description,
+ legend: presenter.legend,
+ name: stage.name,
+ project_median: median
+ ))
end
# rubocop: enable CodeReuse/Presenter
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index f8873d388a3..f3ee21ea4e0 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -372,9 +372,11 @@ class Deployment < ApplicationRecord
# i.e.:
# MergeRequest.select(1, 2).to_sql #=> SELECT 1, 2 FROM "merge_requests"
# MergeRequest.select(1, 1).to_sql #=> SELECT 1 FROM "merge_requests"
- select = relation.select('merge_requests.id',
- "#{id} as deployment_id",
- "#{environment_id} as environment_id").to_sql
+ select = relation.select(
+ 'merge_requests.id',
+ "#{id} as deployment_id",
+ "#{environment_id} as environment_id"
+ ).to_sql
# We don't use `ApplicationRecord.legacy_bulk_insert` here so that we don't need to
# first pluck lots of IDs into memory.
diff --git a/app/models/design_management/design.rb b/app/models/design_management/design.rb
index cb6d4e72c80..505935bb230 100644
--- a/app/models/design_management/design.rb
+++ b/app/models/design_management/design.rb
@@ -31,8 +31,8 @@ module DesignManagement
has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_internal_id :iid, scope: :project, presence: true,
- hook_names: %i[create update], # Deal with old records
- track_if: -> { !importing? }
+ hook_names: %i[create update], # Deal with old records
+ track_if: -> { !importing? }
validates :project, :filename, presence: true
validates :issue, presence: true, unless: :importing?
diff --git a/app/models/design_management/version.rb b/app/models/design_management/version.rb
index 5819404efb9..dd6812f0eac 100644
--- a/app/models/design_management/version.rb
+++ b/app/models/design_management/version.rb
@@ -36,10 +36,10 @@ module DesignManagement
belongs_to :author, class_name: 'User'
has_many :actions
has_many :designs,
- through: :actions,
- class_name: "DesignManagement::Design",
- source: :design,
- inverse_of: :versions
+ through: :actions,
+ class_name: "DesignManagement::Design",
+ source: :design,
+ inverse_of: :versions
validates :designs, presence: true, unless: :importing?
validates :sha, presence: true
diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb
index 041ec98ffc9..e2ee951522d 100644
--- a/app/models/diff_discussion.rb
+++ b/app/models/diff_discussion.rb
@@ -10,13 +10,13 @@ class DiffDiscussion < Discussion
DiffNote
end
- delegate :position,
- :original_position,
- :change_position,
- :diff_note_positions,
- :on_text?,
- :on_image?,
- to: :first_note
+ delegate :position,
+ :original_position,
+ :change_position,
+ :diff_note_positions,
+ :on_text?,
+ :on_image?,
+ to: :first_note
def legacy_diff_discussion?
false
diff --git a/app/models/diff_viewer/base.rb b/app/models/diff_viewer/base.rb
index 75aa51348c8..05552e83700 100644
--- a/app/models/diff_viewer/base.rb
+++ b/app/models/diff_viewer/base.rb
@@ -101,8 +101,9 @@ module DiffViewer
def render_error_options
options = []
- blob_url = Gitlab::Routing.url_helpers.project_blob_path(diff_file.repository.project,
- File.join(diff_file.content_sha, diff_file.file_path))
+ blob_url = Gitlab::Routing.url_helpers.project_blob_path(
+ diff_file.repository.project, File.join(diff_file.content_sha, diff_file.file_path)
+ )
options << ActionController::Base.helpers.link_to(_('view the blob'), blob_url)
options
diff --git a/app/models/integrations/prometheus.rb b/app/models/integrations/prometheus.rb
index 2f0995e9ab0..b148539dec6 100644
--- a/app/models/integrations/prometheus.rb
+++ b/app/models/integrations/prometheus.rb
@@ -99,8 +99,7 @@ module Integrations
end
def allow_local_api_url?
- allow_local_requests_from_web_hooks_and_services? ||
- (self_monitoring_project? && internal_prometheus_url?)
+ allow_local_requests_from_web_hooks_and_services? || internal_prometheus_url?
end
def configured?
@@ -127,10 +126,6 @@ module Integrations
delegate :allow_local_requests_from_web_hooks_and_services?, to: :current_settings, private: true
- def self_monitoring_project?
- project && project.id == current_settings.self_monitoring_project_id
- end
-
def internal_prometheus_url?
api_url.present? && api_url == ::Gitlab::Prometheus::Internal.uri
end
diff --git a/app/models/organization.rb b/app/models/organization.rb
new file mode 100644
index 00000000000..73a7e84305f
--- /dev/null
+++ b/app/models/organization.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+# rubocop: disable Gitlab/NamespacedClass
+class Organization < ApplicationRecord
+end
+# rubocop: enable Gitlab/NamespacedClass
diff --git a/app/models/project.rb b/app/models/project.rb
index 8f9b7042a0e..16719316ede 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -173,7 +173,7 @@ class Project < ApplicationRecord
has_one :last_event, -> { order 'events.created_at DESC' }, class_name: 'Event'
has_many :boards
- has_many :application_setting, inverse_of: :self_monitoring_project
+ has_many :application_setting
def self.integration_association_name(name)
"#{name}_integration"
@@ -2857,10 +2857,6 @@ class Project < ApplicationRecord
Feature.enabled?(:group_protected_branches, group) || Feature.enabled?(:allow_protected_branches_for_group, group)
end
- def self_monitoring?
- Gitlab::CurrentSettings.self_monitoring_project_id == id
- end
-
def deploy_token_create_url(opts = {})
Gitlab::Routing.url_helpers.create_deploy_token_project_settings_repository_path(self, opts)
end
diff --git a/app/models/user.rb b/app/models/user.rb
index a5d8541c8ef..f0797e41c12 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1077,6 +1077,13 @@ class User < ApplicationRecord
update(otp_backup_codes: nil)
end
+ # Returns true if the user is allowed to sign in with either otp or recovery codes.
+ def sign_in_with_codes_allowed?
+ return two_factor_otp_enabled? unless Feature.enabled?(:webauthn_without_totp)
+
+ two_factor_enabled?
+ end
+
def two_factor_enabled?
two_factor_otp_enabled? || two_factor_webauthn_enabled?
end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index dd84b890385..0f195663a61 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -15,18 +15,39 @@ module Members
@skip_auth = skip_authorization
if a_group_owner?(member)
- process_destroy_of_group_owner_member(member, skip_subresources, unassign_issuables)
+ process_destroy_of_group_owner_member(member, skip_subresources)
else
destroy_member(member)
- destroy_data_related_to_member(member, skip_subresources, unassign_issuables)
+ destroy_data_related_to_member(member, skip_subresources)
end
+ enqueue_jobs_that_needs_to_be_run_only_once_per_hierarchy(member, unassign_issuables)
+
member
end
+ # We use this to mark recursive calls made to this service from within the same service.
+ # We do this so as to help us run some tasks that needs to be run only once per hierarchy, and not recursively.
+ def mark_as_recursive_call
+ @recursive_call = true
+ end
+
private
- def process_destroy_of_group_owner_member(member, skip_subresources, unassign_issuables)
+ # These actions need to be executed only once per hierarchy because the underlying services
+ # apply these actions to the entire hierarchy anyway, so there is no need to execute them recursively.
+ def enqueue_jobs_that_needs_to_be_run_only_once_per_hierarchy(member, unassign_issuables)
+ return if recursive_call?
+
+ enqueue_delete_todos(member)
+ enqueue_unassign_issuables(member) if unassign_issuables
+ end
+
+ def recursive_call?
+ @recursive_call == true
+ end
+
+ def process_destroy_of_group_owner_member(member, skip_subresources)
# Deleting 2 different group owners via the API in quick succession could lead to
# wrong results for the `last_owner?` check due to race conditions. To prevent this
# we wrap both the last_owner? check and the deletes of owners within a lock.
@@ -40,23 +61,23 @@ module Members
end
# deletion of related data does not have to be within the lock.
- destroy_data_related_to_member(member, skip_subresources, unassign_issuables) unless last_group_owner
+ destroy_data_related_to_member(member, skip_subresources) unless last_group_owner
end
def destroy_member(member)
member.destroy
end
- def destroy_data_related_to_member(member, skip_subresources, unassign_issuables)
+ def destroy_data_related_to_member(member, skip_subresources)
member.user&.invalidate_cache_counts
- delete_member_associations(member, skip_subresources, unassign_issuables)
+ delete_member_associations(member, skip_subresources)
end
def a_group_owner?(member)
member.is_a?(GroupMember) && member.owner?
end
- def delete_member_associations(member, skip_subresources, unassign_issuables)
+ def delete_member_associations(member, skip_subresources)
if member.request? && member.user != current_user
notification_service.decline_access_request(member)
end
@@ -64,8 +85,6 @@ module Members
delete_subresources(member) unless skip_subresources
delete_project_invitations_by(member) unless skip_subresources
resolve_access_request_todos(current_user, member)
- enqueue_delete_todos(member)
- enqueue_unassign_issuables(member) if unassign_issuables
after_execute(member: member)
end
@@ -110,13 +129,17 @@ module Members
def destroy_project_members(members)
members.each do |project_member|
- self.class.new(current_user).execute(project_member, skip_authorization: @skip_auth)
+ service = self.class.new(current_user)
+ service.mark_as_recursive_call
+ service.execute(project_member, skip_authorization: @skip_auth)
end
end
def destroy_group_members(members)
members.each do |group_member|
- self.class.new(current_user).execute(group_member, skip_authorization: @skip_auth, skip_subresources: true)
+ service = self.class.new(current_user)
+ service.mark_as_recursive_call
+ service.execute(group_member, skip_authorization: @skip_auth, skip_subresources: true)
end
end
diff --git a/app/views/admin/sessions/_signin_box.html.haml b/app/views/admin/sessions/_signin_box.html.haml
index c7382266480..2cd2eabd4b7 100644
--- a/app/views/admin/sessions/_signin_box.html.haml
+++ b/app/views/admin/sessions/_signin_box.html.haml
@@ -7,7 +7,7 @@
- ldap_servers.each_with_index do |server, i|
.login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain)) }
.login-body
- = render 'devise/sessions/new_ldap', server: server, hide_remember_me: true, submit_message: _('Enter Admin Mode')
+ = render 'devise/sessions/new_ldap', server: server, render_remember_me: false, submit_message: _('Enter Admin Mode')
= render_if_exists 'devise/sessions/new_smartcard'
diff --git a/app/views/admin/sessions/new.html.haml b/app/views/admin/sessions/new.html.haml
index e9442cf6b53..21b2c6df014 100644
--- a/app/views/admin/sessions/new.html.haml
+++ b/app/views/admin/sessions/new.html.haml
@@ -20,4 +20,4 @@
- if omniauth_enabled? && button_based_providers_enabled?
.clearfix
- = render 'devise/shared/omniauth_box', hide_remember_me: true
+ = render 'devise/shared/omniauth_box', render_remember_me: false
diff --git a/app/views/admin/sessions/two_factor.html.haml b/app/views/admin/sessions/two_factor.html.haml
index d02090d4880..33d73745138 100644
--- a/app/views/admin/sessions/two_factor.html.haml
+++ b/app/views/admin/sessions/two_factor.html.haml
@@ -9,7 +9,7 @@
.tab-content
.login-box.tab-pane.gl-p-5.active{ id: 'login-pane', role: 'tabpanel' }
.login-body
- - if current_user.two_factor_otp_enabled?
+ - if current_user.sign_in_with_codes_allowed?
= render 'admin/sessions/two_factor_otp'
- if current_user.two_factor_webauthn_enabled?
= render 'authentication/authenticate', render_remember_me: false, target_path: admin_session_path
diff --git a/app/views/authentication/_authenticate.html.haml b/app/views/authentication/_authenticate.html.haml
index 7d7bd395836..9e09bcd6e54 100644
--- a/app/views/authentication/_authenticate.html.haml
+++ b/app/views/authentication/_authenticate.html.haml
@@ -21,7 +21,7 @@
%div
%p= _("We heard back from your device. You have been authenticated.")
= form_tag(target_path, method: :post, id: 'js-login-token-2fa-form') do |f|
- - if render_remember_me
+ - if remember_me_enabled? && render_remember_me
- resource_params = params[resource_name].presence || params
= hidden_field_tag 'user[remember_me]', resource_params.fetch(:remember_me, 0)
= hidden_field_tag 'user[device_response]', nil, class: 'form-control', required: true, id: "js-device-response"
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 3aeb89979bb..1c40931c5bb 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -5,15 +5,15 @@
.form-group.gl-px-5
= f.label :password, class: "label-bold #{'gl-mb-1' if Feature.enabled?(:restyle_login_page, @project)}"
= f.password_field :password, class: 'form-control gl-form-input bottom', autocomplete: 'current-password', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field', testid: 'password-field' }
- - if devise_mapping.rememberable?
- .gl-px-5
- .gl-display-inline-block
+ .gl-px-5
+ .gl-display-inline-block
+ - if remember_me_enabled?
= f.gitlab_ui_checkbox_component :remember_me, _('Remember me')
- .gl-float-right
- - if unconfirmed_email?
- = link_to _('Resend confirmation email'), new_user_confirmation_path
- - else
- = link_to _('Forgot your password?'), new_password_path(:user)
+ .gl-float-right
+ - if unconfirmed_email?
+ = link_to _('Resend confirmation email'), new_user_confirmation_path
+ - else
+ = link_to _('Forgot your password?'), new_password_path(:user)
%div
- if Feature.enabled?(:arkose_labs_login_challenge)
= render_if_exists 'devise/sessions/arkose_labs'
diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml
index bdf357c5f74..14038f3c3c7 100644
--- a/app/views/devise/sessions/_new_crowd.html.haml
+++ b/app/views/devise/sessions/_new_crowd.html.haml
@@ -5,7 +5,7 @@
.form-group.gl-px-5
= label_tag :password
= password_field_tag :password, nil, { autocomplete: 'current-password', class: "form-control bottom", title: _("This field is required."), required: true }
- - if devise_mapping.rememberable?
+ - if remember_me_enabled?
.remember-me.gl-px-5
%label{ for: "remember_me" }
= check_box_tag :remember_me, '1', false, id: 'remember_me'
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 7affbafbdeb..8f9d6ee556e 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,5 +1,5 @@
- server = local_assigns.fetch(:server)
-- hide_remember_me = local_assigns.fetch(:hide_remember_me, false)
+- render_remember_me = remember_me_enabled? && local_assigns.fetch(:render_remember_me, true)
- submit_message = local_assigns.fetch(:submit_message, _('Sign in'))
= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do
@@ -9,7 +9,7 @@
.form-group.gl-px-5
= label_tag :password
= password_field_tag :password, nil, { autocomplete: 'current-password', class: "form-control gl-form-input bottom", title: _("This field is required."), data: { qa_selector: 'password_field' }, required: true }
- - if !hide_remember_me && devise_mapping.rememberable?
+ - if render_remember_me
.gl-px-5
= render Pajamas::CheckboxTagComponent.new(name: 'remember_me') do |c|
= c.label do
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 12e5a7263f7..789f6072b68 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -2,10 +2,11 @@
= render 'devise/shared/tab_single', tab_title: _('Two-Factor Authentication') if Feature.disabled?(:restyle_login_page, @project)
.login-box.gl-p-5
.login-body
- - if @user.two_factor_otp_enabled? || (Feature.enabled?(:webauthn_without_totp) && @user.two_factor_enabled?)
+ - if @user.sign_in_with_codes_allowed?
= gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: "edit_user gl-show-field-errors js-2fa-form #{'hidden' if @user.two_factor_webauthn_enabled?}" }) do |f|
- resource_params = params[resource_name].presence || params
- = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
+ - if remember_me_enabled?
+ = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
%div
= f.label _('Enter verification code'), name: :otp_attempt, class: Feature.enabled?(:restyle_login_page, @project) ? 'gl-mb-1' : ''
= f.text_field :otp_attempt, class: 'form-control gl-form-input', required: true, autofocus: true, autocomplete: 'off', inputmode: 'numeric', title: _('This field is required.'), data: { qa_selector: 'two_fa_code_field' }
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index 150f61a97e0..f59bdc67d17 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -1,4 +1,4 @@
-- hide_remember_me = local_assigns.fetch(:hide_remember_me, false)
+- render_remember_me = remember_me_enabled? && local_assigns.fetch(:render_remember_me, true)
- restyle_login_page_enabled = Feature.enabled?(:restyle_login_page, @project)
%div{ class: restyle_login_page_enabled ? 'omniauth-container gl-mt-5 gl-p-5 gl-text-center gl-w-90p gl-ml-auto gl-mr-auto' : 'omniauth-container gl-mt-5 gl-p-5' }
%label{ class: restyle_login_page_enabled ? 'gl-font-weight-normal' : 'gl-font-weight-bold' }
@@ -12,7 +12,7 @@
= provider_image_tag(provider)
%span.gl-button-text
= label_for_provider(provider)
- - unless hide_remember_me
+ - if render_remember_me
= render Pajamas::CheckboxTagComponent.new(name: 'remember_me_omniauth', value: nil) do |c|
= c.label do
= _('Remember me')
diff --git a/app/views/projects/_self_monitoring_deprecation_notice.html.haml b/app/views/projects/_self_monitoring_deprecation_notice.html.haml
deleted file mode 100644
index b9e32356688..00000000000
--- a/app/views/projects/_self_monitoring_deprecation_notice.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-- return unless project.self_monitoring?
-
-= content_for :page_level_alert do
- .flash-container.flash-container-page.sticky
- %div{ class: [container_class, 'limit-container-width', 'gl-pt-5!'] }
- = render Pajamas::AlertComponent.new(title: _('Deprecation notice'),
- variant: :danger,
- alert_options: { class: 'gl-mb-3 gl-sticky' }) do |c|
- = c.body do
- - deprecation_link = '<a href="%{url}">'.html_safe % { url: help_page_path('update/deprecations', anchor: 'gitlab-self-monitoring-project') }
- - removal_link = '<a href="%{url}">'.html_safe % { url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/348909' }
- - opstrace_link = '<a href="%{url}">'.html_safe % { url: 'https://gitlab.com/groups/gitlab-org/-/epics/6976' }
- = _("Self-monitoring was %{deprecation}deprecated%{link_end} in GitLab 14.9, and is %{removal}scheduled for removal%{link_end} in GitLab 16.0. For information on a possible replacement, %{opstrace}learn more about Opstrace%{link_end}.").html_safe % { deprecation: deprecation_link, removal: removal_link, opstrace: opstrace_link, link_end: '</a>'.html_safe }
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index b6c21588193..a51d1080d96 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -7,7 +7,6 @@
= render "home_panel"
= render "archived_notice", project: @project
-= render "self_monitoring_deprecation_notice", project: @project
= render "invite_members_empty_project" if can_admin_project_member?(@project)
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index ab2f6745dfd..e21bf7d318b 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -8,7 +8,6 @@
= render_if_exists 'shared/ultimate_feature_removal_banner', project: @project
= render partial: 'flash_messages', locals: { project: @project }
-= render "self_monitoring_deprecation_notice", project: @project
= render 'clusters_deprecation_alert'
diff --git a/db/docs/organizations.yml b/db/docs/organizations.yml
new file mode 100644
index 00000000000..68278d0e6e4
--- /dev/null
+++ b/db/docs/organizations.yml
@@ -0,0 +1,10 @@
+---
+table_name: organizations
+classes:
+- Organization
+feature_categories:
+- cell
+description: Define ownership of namespaces, projects, and users by organizations
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119421
+milestone: "16.0"
+gitlab_schema: gitlab_main
diff --git a/db/migrate/20230420012220_create_organizations.rb b/db/migrate/20230420012220_create_organizations.rb
new file mode 100644
index 00000000000..ba6e89837d8
--- /dev/null
+++ b/db/migrate/20230420012220_create_organizations.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class CreateOrganizations < Gitlab::Database::Migration[2.1]
+ def change
+ create_table :organizations do |t|
+ t.timestamps_with_timezone null: false
+ end
+ end
+end
diff --git a/db/schema_migrations/20230420012220 b/db/schema_migrations/20230420012220
new file mode 100644
index 00000000000..56a78b90585
--- /dev/null
+++ b/db/schema_migrations/20230420012220
@@ -0,0 +1 @@
+c6897ef9e8c57b2b0dc8c94c0b2b9311996528b8f88bbf9b6a955de5d5c5120f \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 5eb98c14623..2fd0a3e40b3 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -19253,6 +19253,21 @@ CREATE SEQUENCE operations_user_lists_id_seq
ALTER SEQUENCE operations_user_lists_id_seq OWNED BY operations_user_lists.id;
+CREATE TABLE organizations (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL
+);
+
+CREATE SEQUENCE organizations_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE organizations_id_seq OWNED BY organizations.id;
+
CREATE TABLE packages_build_infos (
id bigint NOT NULL,
package_id integer NOT NULL,
@@ -25413,6 +25428,8 @@ ALTER TABLE ONLY operations_strategies_user_lists ALTER COLUMN id SET DEFAULT ne
ALTER TABLE ONLY operations_user_lists ALTER COLUMN id SET DEFAULT nextval('operations_user_lists_id_seq'::regclass);
+ALTER TABLE ONLY organizations ALTER COLUMN id SET DEFAULT nextval('organizations_id_seq'::regclass);
+
ALTER TABLE ONLY p_ci_builds_metadata ALTER COLUMN id SET DEFAULT nextval('ci_builds_metadata_id_seq'::regclass);
ALTER TABLE ONLY packages_build_infos ALTER COLUMN id SET DEFAULT nextval('packages_build_infos_id_seq'::regclass);
@@ -27639,6 +27656,9 @@ ALTER TABLE ONLY operations_strategies_user_lists
ALTER TABLE ONLY operations_user_lists
ADD CONSTRAINT operations_user_lists_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY organizations
+ ADD CONSTRAINT organizations_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY p_ci_runner_machine_builds
ADD CONSTRAINT p_ci_runner_machine_builds_pkey PRIMARY KEY (build_id, partition_id);
diff --git a/doc/user/ai_features.md b/doc/user/ai_features.md
index 8517ac9d75b..82e8b4cec36 100644
--- a/doc/user/ai_features.md
+++ b/doc/user/ai_features.md
@@ -7,7 +7,7 @@ type: index, reference
# AI/ML powered features
-GitLab is creating AI-assisted features across our DevSecOps platform. These features aim to help increase velocity and solve key painpoints across the software development lifecycle.
+GitLab is creating AI-assisted features across our DevSecOps platform. These features aim to help increase velocity and solve key pain points across the software development lifecycle.
## Enable AI/ML features
@@ -32,13 +32,13 @@ When a feature is [Generally Available](../policy/alpha-beta-support.md#generall
## Beta AI features
-[Beta features](../policy/alpha-beta-support.md#beta) do not require the [group-level experiment features setting](group/manage.md#group-experiment-features-setting) to be enabled.
+[Beta features](../policy/alpha-beta-support.md#beta) do not require the [group-level experiment features setting](group/manage.md#group-experiment-features-setting) to be enabled.
- [Code Suggestions](project/repository/code_suggestions.md)
-## Experiment AI features
+## Experiment AI features
-[Experiment features](../policy/alpha-beta-support.md#experiment) will soon require the [group-level Experiment features setting](group/manage.md#group-experiment-features-setting) to be enabled.
+[Experiment features](../policy/alpha-beta-support.md#experiment) will soon require the [group-level Experiment features setting](group/manage.md#group-experiment-features-setting) to be enabled.
## Third-party AI features
@@ -50,25 +50,32 @@ Third-party AI features require the [group-level third-party AI features setting
This feature is an [Experiment](../policy/alpha-beta-support.md) on GitLab.com that is powered by OpenAI's GPT-3.
-If you spend a lot of time trying to understand pieces of code that others have created, or
-are struggling to understand code written in a language that you are not familiar with, GitLab can help you get up to speed faster. By using a large language model, GitLab can explain the code in natural language.
+GitLab can help you get up to speed faster if you:
+
+- Spend a lot of time trying to understand pieces of code that others have created, or
+- Struggle to understand code written in a language that you are not familiar with.
+
+By using a large language model, GitLab can explain the code in natural language.
Prerequisites:
- The project must be a public project on GitLab.com.
-- You must have the Ultimate subscription tier.
+- You must have the GitLab Ultimate subscription tier.
- You must be a member of the project.
To explain your code:
-1. Navigate to the file and select the lines that you want to have explained.
-1. On the left side, select the question mark. You might have to scroll to the first line of your selection to view it. This sends the selected code together with a prompt to provide an explanation to the large language model referenced above.
-1. A drawer is displayed. Wait a moment for the explanation to be generated.
-1. Provide feedback about how satisfied you are with the explanation so we can improve the results.
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Merge requests**, then select your merge request.
+1. On the secondary menu, select **Changes**.
+1. Go to the file, and select the lines that you want to have explained.
+1. On the left side, select the question mark (**{question}**). You might have to scroll to the first line of your selection to view it. This sends the selected code, together with a prompt, to provide an explanation to the large language model.
+1. A drawer is displayed on the right side of the page. Wait a moment for the explanation to be generated.
+1. Provide feedback about how satisfied you are with the explanation, so we can improve the results.
![How to use the Explain Code Experiment](img/explain_code_experiment.png)
-Please be aware we cannot guarantee that the large language model will produce results that are correct. Use the explanation with caution.
+We cannot guarantee that the large language model produces results that are correct. Use the explanation with caution.
### GitLab Chat **(ULTIMATE SAAS)**
@@ -76,14 +83,74 @@ Please be aware we cannot guarantee that the large language model will produce r
This feature is an [Experiment](../policy/alpha-beta-support.md) on GitLab.com that is powered by OpenAI's GPT-3. It requires the [group-level third-party AI features setting](group/manage.md#group-third-party-ai-features-setting) to be enabled.
-Getting help has never been easier. If you have a question about how the GitLab product works, you can ask product how-to questions and get AI generated support from GitLab Chat.
+Getting help has never been easier. If you have a question about how the GitLab product works, you can ask product how-to questions and get AI generated support from GitLab Chat.
1. In the lower-left corner, select the Help icon.
1. Select **Ask in GitLab Chat**. A drawer opens on the right side of your screen.
-1. Enter your question in the chat input box and press **Enter** or select **Send**. It may take a few seconds for the interactive AI chat to search the product docs and produce an answer.
+1. Enter your question in the chat input box and press **Enter** or select **Send**. It may take a few seconds for the interactive AI chat to search the product documentation and produce an answer.
To give feedback, select the **Give Feedback** link.
+### Summarize merge request changes **(ULTIMATE SAAS)**
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10400) in GitLab 16.0 as an [Experiment](../policy/alpha-beta-support.md#experiment).
+
+This feature is an [Experiment](../policy/alpha-beta-support.md) on GitLab.com that is powered by OpenAI's GPT-3. It requires the [group-level third-party AI features setting](group/manage.md#group-third-party-ai-features-setting) to be enabled.
+
+Merge request summaries can be generated by using the `/summarize_diff` quick action in a merge request comment. This posts a comment from a GitLab bot that provides a summary of the changes and the related SHA for when that summary was generated.
+
+Feedback on this experimental feature can be provided in [issue 408726](https://gitlab.com/gitlab-org/gitlab/-/issues/408726).
+
+#### Data usage
+
+This data is sent to the large language model referenced above when you use the `/summarize_diff` quick action:
+
+1. The diff of changes between the head of the source branch and the target branch
+
+### Summarize my merge request review **(ULTIMATE SAAS)**
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10466) in GitLab 16.0 as an [Experiment](../policy/alpha-beta-support.md#experiment).
+
+This feature is an [Experiment](../policy/alpha-beta-support.md) on GitLab.com that is powered by OpenAI's GPT-3. It requires the [group-level third-party AI features setting](group/manage.md#group-third-party-ai-features-setting) to be enabled.
+
+When you've completed your review of a merge request and are ready to [submit your review](project/merge_requests/reviews/index.md#submit-a-review) you can choose to have summary generated for you. To generate the summary:
+
+1. Select the AI Actions dropdown list.
+1. Select **Summarize my code review**.
+
+The summary is generated and entered in to the comment box where you can edit and refine prior to submitting with your review.
+
+Feedback on this experimental feature can be provided in [issue 408991](https://gitlab.com/gitlab-org/gitlab/-/issues/408991).
+
+#### Data usage
+
+This data is sent to the large language model referenced above when you click on **Summarize my code review**:
+
+1. Draft comment's text
+1. File path of the commented file(s)
+
+### Generate suggested tests in merge requests **(ULTIMATE SAAS)**
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10366) in GitLab 16.0 as an [Experiment](../policy/alpha-beta-support.md#experiment).
+
+This feature is an [Experiment](../policy/alpha-beta-support.md) on GitLab.com that is powered by OpenAI's GPT-3. It requires the [group-level third-party AI features setting](group/manage.md#group-third-party-ai-features-setting) to be enabled.
+
+When in a merge request you can choose to have GitLab suggest tests for the file you are reviewing. This can help to determine if appropriate test coverage has been provided or help with writing tests to provide more coverage for your project. To generate a test suggestion:
+
+1. Select the menu icon on the header of a file.
+1. Select **Generate test with AI**.
+
+A sidebar opens where the test suggestion is generated. From there you can choose to copy that suggestion in to your editor as the start of your tests.
+
+Feedback on this experimental feature can be provided in [issue 408995](https://gitlab.com/gitlab-org/gitlab/-/issues/408995).
+
+#### Data usage
+
+This data is sent to the large language model referenced above when you click on **Generate test with AI**:
+
+1. Contents of the file
+1. The file name
+
## Data Usage
GitLab AI features leverage generative AI to help increase velocity and aim to help make you more productive. Each feature operates independently of other features and is not required for other features to function.
@@ -102,13 +169,13 @@ These features are in a variety of [feature support levels](../policy/alpha-beta
### Data privacy
-Some AI features require the use of third-party AI services models and APIs from: Google AI and OpenAI. The processing of any personal data is in accordance with our [Privacy Statement](https://about.gitlab.com/privacy/). You may also visit the [Sub-Processors page](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors) to see the list of our Sub-Processors that we use in order to provide these features.
+Some AI features require the use of third-party AI services models and APIs from: Google AI and OpenAI. The processing of any personal data is in accordance with our [Privacy Statement](https://about.gitlab.com/privacy/). You may also visit the [Sub-Processors page](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors) to see the list of our Sub-Processors that we use in order to provide these features.
Group owners can control which top-level groups have access to third-party AI features by using the [group level third-party AI features setting](group/manage.md#group-third-party-ai-features-setting).
### Model accuracy and quality
-Generative AI may produce unexpected results that may be:
+Generative AI may produce unexpected results that may be:
- Low-quality
- Incoherent
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 7d95f744d44..42a34a2d00b 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -340,11 +340,7 @@ namespace :gitlab do
exit 1
end
- # A list of projects that GitLab creates automatically on install/upgrade
- # gc = Gitlab::CurrentSettings.current_application_settings
- seed_projects = [Gitlab::CurrentSettings.current_application_settings.self_monitoring_project]
-
- if (Project.count - seed_projects.count { |x| !x.nil? }).eql?(0)
+ if Project.count.eql?(0)
puts "No user created projects. Database not active"
exit 1
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 286f3aa4f7f..70d3ddf1f43 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -15146,9 +15146,6 @@ msgstr ""
msgid "Deprecated API rate limits"
msgstr ""
-msgid "Deprecation notice"
-msgstr ""
-
msgid "Deprecations|For information on a possible replacement %{epicStart} learn more about Opstrace %{epicEnd}."
msgstr ""
@@ -39766,9 +39763,6 @@ msgstr ""
msgid "Search users or groups"
msgstr ""
-msgid "Search your project dependencies for their licenses and apply policies."
-msgstr ""
-
msgid "Search your projects"
msgstr ""
@@ -40007,9 +40001,6 @@ msgstr ""
msgid "SecurityConfiguration|By default, all analyzers are applied in order to cover all languages across your project, and only run if the language is detected in the merge request."
msgstr ""
-msgid "SecurityConfiguration|Compliance"
-msgstr ""
-
msgid "SecurityConfiguration|Configuration guide"
msgstr ""
@@ -40076,7 +40067,7 @@ msgstr ""
msgid "SecurityConfiguration|Manage profiles for use by DAST scans."
msgstr ""
-msgid "SecurityConfiguration|More scan types, including DAST, Dependency Scanning, Fuzzing, and Licence Compliance"
+msgid "SecurityConfiguration|More scan types, including DAST, Dependency Scanning, Fuzzing"
msgstr ""
msgid "SecurityConfiguration|Not enabled"
@@ -41198,9 +41189,6 @@ msgstr ""
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By %{link_open}@johnsmith%{link_close}\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Self-monitoring was %{deprecation}deprecated%{link_end} in GitLab 14.9, and is %{removal}scheduled for removal%{link_end} in GitLab 16.0. For information on a possible replacement, %{opstrace}learn more about Opstrace%{link_end}."
-msgstr ""
-
msgid "Send"
msgstr ""
diff --git a/spec/factories/organizations.rb b/spec/factories/organizations.rb
new file mode 100644
index 00000000000..a6684a8f95f
--- /dev/null
+++ b/spec/factories/organizations.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :organization
+end
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index e6e5a1f9894..6f155959adb 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -394,6 +394,24 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
providers: [mock_saml_config_with_upstream_two_factor_authn_contexts])
end
+ it 'displays the remember me checkbox' do
+ visit new_user_session_path
+
+ expect(page).to have_field('remember_me_omniauth')
+ end
+
+ context 'when remember me is not enabled' do
+ before do
+ stub_application_setting(remember_me_enabled: false)
+ end
+
+ it 'does not display the remember me checkbox' do
+ visit new_user_session_path
+
+ expect(page).not_to have_field('remember_me_omniauth')
+ end
+ end
+
context 'when authn_context is worth two factors' do
let(:mock_saml_response) do
File.read('spec/fixtures/authentication/saml_response.xml')
@@ -444,6 +462,24 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
end
describe 'without two-factor authentication' do
+ it 'displays the remember me checkbox' do
+ visit new_user_session_path
+
+ expect(page).to have_content(_('Remember me'))
+ end
+
+ context 'when remember me is not enabled' do
+ before do
+ stub_application_setting(remember_me_enabled: false)
+ end
+
+ it 'does not display the remember me checkbox' do
+ visit new_user_session_path
+
+ expect(page).not_to have_content(_('Remember me'))
+ end
+ end
+
context 'with correct username and password' do
let(:user) { create(:user) }
@@ -750,17 +786,37 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
allow(instance).to receive(:"user_#{provider}_omniauth_callback_path")
.and_return("/users/auth/#{provider}/callback")
end
-
- visit new_user_session_path
end
it 'correctly renders tabs and panes' do
+ visit new_user_session_path
+
ensure_tab_pane_correctness(['Main LDAP', 'Standard'])
end
it 'renders link to sign up path' do
+ visit new_user_session_path
+
expect(page.body).to have_link('Register now', href: new_user_registration_path)
end
+
+ it 'displays the remember me checkbox' do
+ visit new_user_session_path
+
+ ensure_remember_me_in_tab(ldap_server_config['label'])
+ end
+
+ context 'when remember me is not enabled' do
+ before do
+ stub_application_setting(remember_me_enabled: false)
+ end
+
+ it 'does not display the remember me checkbox' do
+ visit new_user_session_path
+
+ ensure_remember_me_not_in_tab(ldap_server_config['label'])
+ end
+ end
end
context 'when crowd is enabled' do
@@ -775,13 +831,31 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
allow(instance).to receive(:user_crowd_omniauth_authorize_path)
.and_return("/users/auth/crowd/callback")
end
-
- visit new_user_session_path
end
it 'correctly renders tabs and panes' do
+ visit new_user_session_path
+
ensure_tab_pane_correctness(%w(Crowd Standard))
end
+
+ it 'displays the remember me checkbox' do
+ visit new_user_session_path
+
+ ensure_remember_me_in_tab(_('Crowd'))
+ end
+
+ context 'when remember me is not enabled' do
+ before do
+ stub_application_setting(remember_me_enabled: false)
+ end
+
+ it 'does not display the remember me checkbox' do
+ visit new_user_session_path
+
+ ensure_remember_me_not_in_tab(_('Crowd'))
+ end
+ end
end
end
diff --git a/spec/frontend/graphql_shared/utils_spec.js b/spec/frontend/graphql_shared/utils_spec.js
index 35ae8de1b1f..f03856e5f75 100644
--- a/spec/frontend/graphql_shared/utils_spec.js
+++ b/spec/frontend/graphql_shared/utils_spec.js
@@ -3,6 +3,7 @@ import Visibility from 'visibilityjs';
import {
isGid,
getIdFromGraphQLId,
+ getTypeFromGraphQLId,
convertToGraphQLId,
convertToGraphQLIds,
convertFromGraphQLIds,
@@ -26,52 +27,30 @@ describe('isGid', () => {
});
});
-describe('getIdFromGraphQLId', () => {
- [
- {
- input: '',
- output: null,
- },
- {
- input: null,
- output: null,
- },
- {
- input: 2,
- output: 2,
- },
- {
- input: 'gid://',
- output: null,
- },
- {
- input: 'gid://gitlab/',
- output: null,
- },
- {
- input: 'gid://gitlab/Environments',
- output: null,
- },
- {
- input: 'gid://gitlab/Environments/',
- output: null,
- },
- {
- input: 'gid://gitlab/Environments/0',
- output: 0,
- },
- {
- input: 'gid://gitlab/Environments/123',
- output: 123,
- },
- {
- input: 'gid://gitlab/DesignManagement::Version/2',
- output: 2,
- },
- ].forEach(({ input, output }) => {
- it(`getIdFromGraphQLId returns ${output} when passed ${input}`, () => {
- expect(getIdFromGraphQLId(input)).toBe(output);
- });
+describe.each`
+ input | id | type
+ ${''} | ${null} | ${null}
+ ${null} | ${null} | ${null}
+ ${0} | ${0} | ${null}
+ ${'0'} | ${0} | ${null}
+ ${2} | ${2} | ${null}
+ ${'2'} | ${2} | ${null}
+ ${'gid://'} | ${null} | ${null}
+ ${'gid://gitlab'} | ${null} | ${null}
+ ${'gid://gitlab/'} | ${null} | ${null}
+ ${'gid://gitlab/Environments'} | ${null} | ${'Environments'}
+ ${'gid://gitlab/Environments/'} | ${null} | ${'Environments'}
+ ${'gid://gitlab/Environments/0'} | ${0} | ${'Environments'}
+ ${'gid://gitlab/Environments/123'} | ${123} | ${'Environments'}
+ ${'gid://gitlab/Environments/123/test'} | ${123} | ${'Environments'}
+ ${'gid://gitlab/DesignManagement::Version/123'} | ${123} | ${'DesignManagement::Version'}
+`('parses GraphQL ID `$input`', ({ input, id, type }) => {
+ it(`getIdFromGraphQLId returns ${id}`, () => {
+ expect(getIdFromGraphQLId(input)).toBe(id);
+ });
+
+ it(`getTypeFromGraphQLId returns ${type}`, () => {
+ expect(getTypeFromGraphQLId(input)).toBe(type);
});
});
diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js
index c6869573b2e..9a0cde15b24 100644
--- a/spec/frontend/issues/show/components/description_spec.js
+++ b/spec/frontend/issues/show/components/description_spec.js
@@ -8,7 +8,6 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createAlert } from '~/alert';
import Description from '~/issues/show/components/description.vue';
import eventHub from '~/issues/show/event_hub';
-import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
import workItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
import TaskList from '~/task_list';
@@ -35,13 +34,6 @@ const $toast = {
};
const issueDetailsResponse = getIssueDetailsResponse();
-const workItemQueryResponse = {
- data: {
- workItem: null,
- },
-};
-
-const queryHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
const workItemTypesQueryHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse);
describe('Description component', () => {
@@ -72,7 +64,6 @@ describe('Description component', () => {
...provide,
},
apolloProvider: createMockApollo([
- [workItemQuery, queryHandler],
[workItemTypesQuery, workItemTypesQueryHandler],
[getIssueDetailsQuery, issueDetailsQueryHandler],
[createWorkItemMutation, createWorkItemMutationHandler],
diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js
index c4e6bef71eb..364fe733a41 100644
--- a/spec/frontend/security_configuration/components/app_spec.js
+++ b/spec/frontend/security_configuration/components/app_spec.js
@@ -11,7 +11,7 @@ import AutoDevopsEnabledAlert from '~/security_configuration/components/auto_dev
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from '~/security_configuration/components/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
-import { complianceFeaturesMock, securityFeaturesMock, provideMock } from '../mock_data';
+import { securityFeaturesMock, provideMock } from '../mock_data';
const gitlabCiHistoryPath = 'test/historyPath';
const { vulnerabilityTrainingDocsPath, projectFullPath } = provideMock;
@@ -29,7 +29,6 @@ describe('~/security_configuration/components/app', () => {
wrapper = mountExtended(SecurityConfigurationApp, {
propsData: {
augmentedSecurityFeatures: securityFeaturesMock,
- augmentedComplianceFeatures: complianceFeaturesMock,
securityTrainingEnabled: true,
...propsData,
},
@@ -72,12 +71,7 @@ describe('~/security_configuration/components/app', () => {
text: i18n.configurationHistory,
container: findByTestId('security-testing-tab'),
});
- const findComplianceViewHistoryLink = () =>
- findLink({
- href: gitlabCiHistoryPath,
- text: i18n.configurationHistory,
- container: findByTestId('compliance-testing-tab'),
- });
+
const findAutoDevopsAlert = () => wrapper.findComponent(AutoDevopsAlert);
const findAutoDevopsEnabledAlert = () => wrapper.findComponent(AutoDevopsEnabledAlert);
const findVulnerabilityManagementTab = () => wrapper.findByTestId('vulnerability-management-tab');
@@ -94,7 +88,7 @@ describe('~/security_configuration/components/app', () => {
});
describe('tabs', () => {
- const expectedTabs = ['security-testing', 'compliance-testing', 'vulnerability-management'];
+ const expectedTabs = ['security-testing', 'vulnerability-management'];
it('renders GlTab Component', () => {
expect(findTab().exists()).toBe(true);
@@ -123,9 +117,8 @@ describe('~/security_configuration/components/app', () => {
it('renders right amount of feature cards for given props with correct props', () => {
const cards = findFeatureCards();
- expect(cards).toHaveLength(2);
+ expect(cards).toHaveLength(1);
expect(cards.at(0).props()).toEqual({ feature: securityFeaturesMock[0] });
- expect(cards.at(1).props()).toEqual({ feature: complianceFeaturesMock[0] });
});
it('renders a basic description', () => {
@@ -137,7 +130,6 @@ describe('~/security_configuration/components/app', () => {
});
it('should not show configuration History Link when gitlabCiPresent & gitlabCiHistoryPath are not defined', () => {
- expect(findComplianceViewHistoryLink().exists()).toBe(false);
expect(findSecurityViewHistoryLink().exists()).toBe(false);
});
});
@@ -158,7 +150,7 @@ describe('~/security_configuration/components/app', () => {
it('should show Alert with error Message', async () => {
expect(findManageViaMRErrorAlert().exists()).toBe(false);
- findFeatureCards().at(1).vm.$emit('error', errorMessage);
+ findFeatureCards().at(0).vm.$emit('error', errorMessage);
await nextTick();
expect(findManageViaMRErrorAlert().exists()).toBe(true);
@@ -166,7 +158,7 @@ describe('~/security_configuration/components/app', () => {
});
it('should hide Alert when it is dismissed', async () => {
- findFeatureCards().at(1).vm.$emit('error', errorMessage);
+ findFeatureCards().at(0).vm.$emit('error', errorMessage);
await nextTick();
expect(findManageViaMRErrorAlert().exists()).toBe(true);
@@ -257,7 +249,6 @@ describe('~/security_configuration/components/app', () => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
- augmentedComplianceFeatures: complianceFeaturesMock,
autoDevopsEnabled: true,
});
@@ -285,24 +276,6 @@ describe('~/security_configuration/components/app', () => {
latestPipelinePath: 'test/path',
});
});
-
- it('should show latest pipeline info on the security tab with correct link when latestPipelinePath is defined', () => {
- const latestPipelineInfoSecurity = findByTestId('latest-pipeline-info-security');
-
- expect(latestPipelineInfoSecurity.text()).toMatchInterpolatedText(
- i18n.latestPipelineDescription,
- );
- expect(latestPipelineInfoSecurity.find('a').attributes('href')).toBe('test/path');
- });
-
- it('should show latest pipeline info on the compliance tab with correct link when latestPipelinePath is defined', () => {
- const latestPipelineInfoCompliance = findByTestId('latest-pipeline-info-compliance');
-
- expect(latestPipelineInfoCompliance.text()).toMatchInterpolatedText(
- i18n.latestPipelineDescription,
- );
- expect(latestPipelineInfoCompliance.find('a').attributes('href')).toBe('test/path');
- });
});
describe('given gitlabCiPresent & gitlabCiHistoryPath props', () => {
@@ -314,10 +287,8 @@ describe('~/security_configuration/components/app', () => {
});
it('should show configuration History Link', () => {
- expect(findComplianceViewHistoryLink().exists()).toBe(true);
expect(findSecurityViewHistoryLink().exists()).toBe(true);
- expect(findComplianceViewHistoryLink().attributes('href')).toBe('test/historyPath');
expect(findSecurityViewHistoryLink().attributes('href')).toBe('test/historyPath');
});
});
diff --git a/spec/frontend/security_configuration/mock_data.js b/spec/frontend/security_configuration/mock_data.js
index 24ad8024b01..3d4f01d0da1 100644
--- a/spec/frontend/security_configuration/mock_data.js
+++ b/spec/frontend/security_configuration/mock_data.js
@@ -4,14 +4,8 @@ import {
SAST_DESCRIPTION,
SAST_HELP_PATH,
SAST_CONFIG_HELP_PATH,
- LICENSE_COMPLIANCE_NAME,
- LICENSE_COMPLIANCE_DESCRIPTION,
- LICENSE_COMPLIANCE_HELP_PATH,
} from '~/security_configuration/components/constants';
-import {
- REPORT_TYPE_LICENSE_COMPLIANCE,
- REPORT_TYPE_SAST,
-} from '~/vue_shared/security_reports/constants';
+import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants';
export const testProjectPath = 'foo/bar';
export const testProviderIds = [101, 102, 103];
@@ -128,16 +122,6 @@ export const securityFeaturesMock = [
},
];
-export const complianceFeaturesMock = [
- {
- name: LICENSE_COMPLIANCE_NAME,
- description: LICENSE_COMPLIANCE_DESCRIPTION,
- helpPath: LICENSE_COMPLIANCE_HELP_PATH,
- type: REPORT_TYPE_LICENSE_COMPLIANCE,
- configurationHelpPath: LICENSE_COMPLIANCE_HELP_PATH,
- },
-];
-
export const provideMock = {
upgradePath: '/upgrade',
autoDevopsHelpPagePath: '/autoDevopsHelpPagePath',
diff --git a/spec/frontend/security_configuration/utils_spec.js b/spec/frontend/security_configuration/utils_spec.js
index 241e69204d2..6e731e45da2 100644
--- a/spec/frontend/security_configuration/utils_spec.js
+++ b/spec/frontend/security_configuration/utils_spec.js
@@ -9,13 +9,6 @@ describe('augmentFeatures', () => {
},
];
- const mockComplianceFeatures = [
- {
- name: 'LICENSE_COMPLIANCE',
- type: 'LICENSE_COMPLIANCE',
- },
- ];
-
const mockFeaturesWithSecondary = [
{
name: 'DAST',
@@ -51,30 +44,25 @@ describe('augmentFeatures', () => {
const expectedOutputDefault = {
augmentedSecurityFeatures: mockSecurityFeatures,
- augmentedComplianceFeatures: mockComplianceFeatures,
};
const expectedOutputSecondary = {
augmentedSecurityFeatures: mockSecurityFeatures,
- augmentedComplianceFeatures: mockFeaturesWithSecondary,
};
const expectedOutputCustomFeature = {
augmentedSecurityFeatures: mockValidCustomFeature,
- augmentedComplianceFeatures: mockComplianceFeatures,
};
- describe('returns an object with augmentedSecurityFeatures and augmentedComplianceFeatures when', () => {
+ describe('returns an object with augmentedSecurityFeatures when', () => {
it('given an empty array', () => {
- expect(augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, [])).toEqual(
- expectedOutputDefault,
- );
+ expect(augmentFeatures(mockSecurityFeatures, [])).toEqual(expectedOutputDefault);
});
it('given an invalid populated array', () => {
- expect(
- augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockInvalidCustomFeature),
- ).toEqual(expectedOutputDefault);
+ expect(augmentFeatures(mockSecurityFeatures, mockInvalidCustomFeature)).toEqual(
+ expectedOutputDefault,
+ );
});
it('features have secondary key', () => {
@@ -84,21 +72,17 @@ describe('augmentFeatures', () => {
});
it('given a valid populated array', () => {
- expect(
- augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockValidCustomFeature),
- ).toEqual(expectedOutputCustomFeature);
+ expect(augmentFeatures(mockSecurityFeatures, mockValidCustomFeature)).toEqual(
+ expectedOutputCustomFeature,
+ );
});
});
describe('returns an object with camelcased keys', () => {
it('given a customfeature in snakecase', () => {
- expect(
- augmentFeatures(
- mockSecurityFeatures,
- mockComplianceFeatures,
- mockValidCustomFeatureSnakeCase,
- ),
- ).toEqual(expectedOutputCustomFeature);
+ expect(augmentFeatures(mockSecurityFeatures, mockValidCustomFeatureSnakeCase)).toEqual(
+ expectedOutputCustomFeature,
+ );
});
});
});
diff --git a/spec/frontend/work_items/components/work_item_actions_spec.js b/spec/frontend/work_items/components/work_item_actions_spec.js
index 6e8f6defb17..0045abe50d0 100644
--- a/spec/frontend/work_items/components/work_item_actions_spec.js
+++ b/spec/frontend/work_items/components/work_item_actions_spec.js
@@ -18,10 +18,10 @@ import updateWorkItemNotificationsMutation from '~/work_items/graphql/update_wor
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
import convertWorkItemMutation from '~/work_items/graphql/work_item_convert.mutation.graphql';
import {
- workItemResponseFactory,
convertWorkItemMutationResponse,
projectWorkItemTypesQueryResponse,
convertWorkItemMutationErrorResponse,
+ workItemByIidResponseFactory,
} from '../mock_data';
jest.mock('~/lib/utils/common_utils');
@@ -211,45 +211,45 @@ describe('WorkItemActions component', () => {
describe('notifications action', () => {
const errorMessage = 'Failed to subscribe';
+ const id = 'gid://gitlab/WorkItem/1';
const notificationToggledOffMessage = 'Notifications turned off.';
const notificationToggledOnMessage = 'Notifications turned on.';
- const workItemQueryResponse = workItemResponseFactory({ canUpdate: true, canDelete: true });
const inputVariablesOff = {
- id: workItemQueryResponse.data.workItem.id,
+ id,
notificationsWidget: {
subscribed: false,
},
};
const inputVariablesOn = {
- id: workItemQueryResponse.data.workItem.id,
+ id,
notificationsWidget: {
subscribed: true,
},
};
- const notificationsOffExpectedResponse = workItemResponseFactory({
+ const notificationsOffExpectedResponse = workItemByIidResponseFactory({
subscribed: false,
});
const toggleNotificationsOffHandler = jest.fn().mockResolvedValue({
data: {
workItemUpdate: {
- workItem: notificationsOffExpectedResponse.data.workItem,
+ workItem: notificationsOffExpectedResponse.data.workspace.workItems.nodes[0],
errors: [],
},
},
});
- const notificationsOnExpectedResponse = workItemResponseFactory({
+ const notificationsOnExpectedResponse = workItemByIidResponseFactory({
subscribed: true,
});
const toggleNotificationsOnHandler = jest.fn().mockResolvedValue({
data: {
workItemUpdate: {
- workItem: notificationsOnExpectedResponse.data.workItem,
+ workItem: notificationsOnExpectedResponse.data.workspace.workItems.nodes[0],
errors: [],
},
},
diff --git a/spec/frontend/work_items/components/work_item_assignees_spec.js b/spec/frontend/work_items/components/work_item_assignees_spec.js
index af97b3680f9..1e336a928a0 100644
--- a/spec/frontend/work_items/components/work_item_assignees_spec.js
+++ b/spec/frontend/work_items/components/work_item_assignees_spec.js
@@ -8,9 +8,7 @@ import { mockTracking } from 'helpers/tracking_helper';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
import currentUserQuery from '~/graphql_shared/queries/current_user.query.graphql';
-import { config } from '~/graphql_shared/issuable_client';
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
-import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
import {
@@ -22,7 +20,6 @@ import {
import {
projectMembersResponseWithCurrentUser,
mockAssignees,
- workItemQueryResponse,
currentUserResponse,
currentUserNullResponse,
projectMembersResponseWithoutCurrentUser,
@@ -78,25 +75,11 @@ describe('WorkItemAssignees component', () => {
canInviteMembers = false,
canUpdate = true,
} = {}) => {
- const apolloProvider = createMockApollo(
- [
- [userSearchQuery, searchQueryHandler],
- [currentUserQuery, currentUserQueryHandler],
- [updateWorkItemMutation, updateWorkItemMutationHandler],
- ],
- {},
- {
- typePolicies: config.cacheConfig.typePolicies,
- },
- );
-
- apolloProvider.clients.defaultClient.writeQuery({
- query: workItemQuery,
- variables: {
- id: workItemId,
- },
- data: workItemQueryResponse.data,
- });
+ const apolloProvider = createMockApollo([
+ [userSearchQuery, searchQueryHandler],
+ [currentUserQuery, currentUserQueryHandler],
+ [updateWorkItemMutation, updateWorkItemMutationHandler],
+ ]);
wrapper = mountExtended(WorkItemAssignees, {
propsData: {
diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js
index 291dacfd844..630eb78d5c0 100644
--- a/spec/frontend/work_items/components/work_item_detail_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_spec.js
@@ -39,7 +39,7 @@ import updateWorkItemTaskMutation from '~/work_items/graphql/update_work_item_ta
import {
mockParent,
workItemDatesSubscriptionResponse,
- workItemByIidResponseFactory as workItemResponseFactory,
+ workItemByIidResponseFactory,
workItemTitleSubscriptionResponse,
workItemAssigneesSubscriptionResponse,
workItemMilestoneSubscriptionResponse,
@@ -52,8 +52,8 @@ describe('WorkItemDetail component', () => {
Vue.use(VueApollo);
- const workItemQueryResponse = workItemResponseFactory({ canUpdate: true, canDelete: true });
- const workItemQueryResponseWithoutParent = workItemResponseFactory({
+ const workItemQueryResponse = workItemByIidResponseFactory({ canUpdate: true, canDelete: true });
+ const workItemQueryResponseWithoutParent = workItemByIidResponseFactory({
parent: null,
canUpdate: true,
canDelete: true,
@@ -221,7 +221,7 @@ describe('WorkItemDetail component', () => {
describe('confidentiality', () => {
const errorMessage = 'Mutation failed';
- const confidentialWorkItem = workItemResponseFactory({
+ const confidentialWorkItem = workItemByIidResponseFactory({
confidential: true,
});
const workItem = confidentialWorkItem.data.workspace.workItems.nodes[0];
@@ -398,7 +398,7 @@ describe('WorkItemDetail component', () => {
describe('with parent', () => {
beforeEach(() => {
- const parentResponse = workItemResponseFactory(mockParent);
+ const parentResponse = workItemByIidResponseFactory(mockParent);
createComponent({ handler: jest.fn().mockResolvedValue(parentResponse) });
return waitForPromises();
@@ -437,7 +437,7 @@ describe('WorkItemDetail component', () => {
},
},
};
- const parentResponse = workItemResponseFactory(mockParentObjective);
+ const parentResponse = workItemByIidResponseFactory(mockParentObjective);
createComponent({ handler: jest.fn().mockResolvedValue(parentResponse) });
await waitForPromises();
@@ -492,7 +492,7 @@ describe('WorkItemDetail component', () => {
describe('when the assignees widget does not exist', () => {
it('does not call the assignees subscription', async () => {
- const response = workItemResponseFactory({ assigneesWidgetPresent: false });
+ const response = workItemByIidResponseFactory({ assigneesWidgetPresent: false });
const handler = jest.fn().mockResolvedValue(response);
createComponent({ handler });
await waitForPromises();
@@ -514,7 +514,7 @@ describe('WorkItemDetail component', () => {
describe('when the due date widget does not exist', () => {
it('does not call the dates subscription', async () => {
- const response = workItemResponseFactory({ datesWidgetPresent: false });
+ const response = workItemByIidResponseFactory({ datesWidgetPresent: false });
const handler = jest.fn().mockResolvedValue(response);
createComponent({ handler });
await waitForPromises();
@@ -537,7 +537,7 @@ describe('WorkItemDetail component', () => {
createComponent({
handler: jest
.fn()
- .mockResolvedValue(workItemResponseFactory({ assigneesWidgetPresent: false })),
+ .mockResolvedValue(workItemByIidResponseFactory({ assigneesWidgetPresent: false })),
});
await waitForPromises();
@@ -551,7 +551,7 @@ describe('WorkItemDetail component', () => {
${'renders when widget is returned from API'} | ${true} | ${true}
${'does not render when widget is not returned from API'} | ${false} | ${false}
`('$description', async ({ labelsWidgetPresent, exists }) => {
- const response = workItemResponseFactory({ labelsWidgetPresent });
+ const response = workItemByIidResponseFactory({ labelsWidgetPresent });
const handler = jest.fn().mockResolvedValue(response);
createComponent({ handler });
await waitForPromises();
@@ -567,7 +567,7 @@ describe('WorkItemDetail component', () => {
${'when widget is not returned from API'} | ${false} | ${false}
`('$description', ({ datesWidgetPresent, exists }) => {
it(`${datesWidgetPresent ? 'renders' : 'does not render'} due date component`, async () => {
- const response = workItemResponseFactory({ datesWidgetPresent });
+ const response = workItemByIidResponseFactory({ datesWidgetPresent });
const handler = jest.fn().mockResolvedValue(response);
createComponent({ handler });
await waitForPromises();
@@ -594,7 +594,7 @@ describe('WorkItemDetail component', () => {
${'renders when widget is returned from API'} | ${true} | ${true}
${'does not render when widget is not returned from API'} | ${false} | ${false}
`('$description', async ({ milestoneWidgetPresent, exists }) => {
- const response = workItemResponseFactory({ milestoneWidgetPresent });
+ const response = workItemByIidResponseFactory({ milestoneWidgetPresent });
const handler = jest.fn().mockResolvedValue(response);
createComponent({ handler });
await waitForPromises();
@@ -614,7 +614,7 @@ describe('WorkItemDetail component', () => {
describe('when the assignees widget does not exist', () => {
it('does not call the milestone subscription', async () => {
- const response = workItemResponseFactory({ milestoneWidgetPresent: false });
+ const response = workItemByIidResponseFactory({ milestoneWidgetPresent: false });
const handler = jest.fn().mockResolvedValue(response);
createComponent({ handler });
await waitForPromises();
@@ -632,6 +632,13 @@ describe('WorkItemDetail component', () => {
expect(successHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
});
+ it('skips the work item query when there is no workItemIid', async () => {
+ createComponent({ workItemIid: null });
+ await waitForPromises();
+
+ expect(successHandler).not.toHaveBeenCalled();
+ });
+
it('calls the work item query when isModal=true', async () => {
createComponent({ isModal: true });
await waitForPromises();
@@ -648,7 +655,7 @@ describe('WorkItemDetail component', () => {
});
describe('work item has children', () => {
- const objectiveWorkItem = workItemResponseFactory({
+ const objectiveWorkItem = workItemByIidResponseFactory({
workItemType: objectiveType,
confidential: true,
});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 2ab31908577..1c319844af3 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -684,87 +684,6 @@ export const createWorkItemMutationErrorResponse = {
},
};
-export const createWorkItemFromTaskMutationResponse = {
- data: {
- workItemCreateFromTask: {
- __typename: 'WorkItemCreateFromTaskPayload',
- errors: [],
- workItem: {
- __typename: 'WorkItem',
- description: 'New description',
- id: 'gid://gitlab/WorkItem/1',
- iid: '1',
- title: 'Updated title',
- state: 'OPEN',
- confidential: false,
- createdAt: '2022-08-03T12:41:54Z',
- closedAt: null,
- project: {
- __typename: 'Project',
- id: '1',
- fullPath: 'test-project-path',
- archived: false,
- },
- workItemType: {
- __typename: 'WorkItemType',
- id: 'gid://gitlab/WorkItems::Type/5',
- name: 'Task',
- iconName: 'issue-type-task',
- },
- userPermissions: {
- deleteWorkItem: false,
- updateWorkItem: false,
- setWorkItemMetadata: false,
- __typename: 'WorkItemPermissions',
- },
- widgets: [
- {
- __typename: 'WorkItemWidgetDescription',
- type: 'DESCRIPTION',
- description: 'New description',
- descriptionHtml: '<p>New description</p>',
- lastEditedAt: '2022-09-21T06:18:42Z',
- lastEditedBy: {
- name: 'Administrator',
- webPath: '/root',
- },
- },
- ],
- },
- newWorkItem: {
- __typename: 'WorkItem',
- id: 'gid://gitlab/WorkItem/1000000',
- iid: '100',
- title: 'Updated title',
- state: 'OPEN',
- createdAt: '2022-08-03T12:41:54Z',
- closedAt: null,
- description: '',
- confidential: false,
- project: {
- __typename: 'Project',
- id: '1',
- fullPath: 'test-project-path',
- archived: false,
- },
- workItemType: {
- __typename: 'WorkItemType',
- id: 'gid://gitlab/WorkItems::Type/5',
- name: 'Task',
- iconName: 'issue-type-task',
- },
- userPermissions: {
- deleteWorkItem: false,
- updateWorkItem: false,
- setWorkItemMetadata: false,
- __typename: 'WorkItemPermissions',
- },
- widgets: [],
- },
- },
- },
-};
-
export const deleteWorkItemResponse = {
data: { workItemDelete: { errors: [], __typename: 'WorkItemDeletePayload' } },
};
@@ -1831,18 +1750,6 @@ export const projectMilestonesResponseWithNoMilestones = {
},
};
-export const projectWorkItemResponse = {
- data: {
- workspace: {
- id: 'gid://gitlab/Project/1',
- workItems: {
- nodes: [workItemQueryResponse.data.workItem],
- },
- __typename: 'Project',
- },
- },
-};
-
export const mockWorkItemNotesResponse = {
data: {
workItem: {
diff --git a/spec/frontend/work_items/router_spec.js b/spec/frontend/work_items/router_spec.js
index 86e890ea809..b5d54a7c319 100644
--- a/spec/frontend/work_items/router_spec.js
+++ b/spec/frontend/work_items/router_spec.js
@@ -6,7 +6,7 @@ import {
currentUserResponse,
workItemAssigneesSubscriptionResponse,
workItemDatesSubscriptionResponse,
- workItemByIidResponseFactory as workItemResponseFactory,
+ workItemByIidResponseFactory,
workItemTitleSubscriptionResponse,
workItemLabelsSubscriptionResponse,
workItemMilestoneSubscriptionResponse,
@@ -32,7 +32,7 @@ describe('Work items router', () => {
Vue.use(VueApollo);
- const workItemQueryHandler = jest.fn().mockResolvedValue(workItemResponseFactory());
+ const workItemQueryHandler = jest.fn().mockResolvedValue(workItemByIidResponseFactory());
const currentUserQueryHandler = jest.fn().mockResolvedValue(currentUserResponse);
const datesSubscriptionHandler = jest.fn().mockResolvedValue(workItemDatesSubscriptionResponse);
const titleSubscriptionHandler = jest.fn().mockResolvedValue(workItemTitleSubscriptionResponse);
diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb
index b8290fa2337..5a46a20ce1a 100644
--- a/spec/helpers/sessions_helper_spec.rb
+++ b/spec/helpers/sessions_helper_spec.rb
@@ -87,4 +87,24 @@ RSpec.describe SessionsHelper do
expect(subject).to eq('ma**@e******.com')
end
end
+
+ describe '#remember_me_enabled?' do
+ subject { helper.remember_me_enabled? }
+
+ context 'when application setting is enabled' do
+ before do
+ stub_application_setting(remember_me_enabled: true)
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when application setting is disabled' do
+ before do
+ stub_application_setting(remember_me_enabled: false)
+ end
+
+ it { is_expected.to be false }
+ end
+ end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index a35c9c5e526..e88c89c74dc 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -639,8 +639,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
create(:alert_management_alert, project: project, created_at: n.days.ago)
end
- stub_application_setting(self_monitoring_project: project)
-
for_defined_days_back do
create(:product_analytics_event, project: project, se_category: 'epics', se_action: 'promote')
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 9f292b0a294..e0bfe41a3ae 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -27,12 +27,6 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
describe 'associations' do
it do
- is_expected.to belong_to(:self_monitoring_project).class_name('Project')
- .with_foreign_key(:instance_administration_project_id)
- .inverse_of(:application_setting)
- end
-
- it do
is_expected.to belong_to(:instance_group).class_name('Group')
.with_foreign_key(:instance_administrators_group_id)
.inverse_of(:application_setting)
diff --git a/spec/models/integrations/prometheus_spec.rb b/spec/models/integrations/prometheus_spec.rb
index aa248abd3bb..a533079f906 100644
--- a/spec/models/integrations/prometheus_spec.rb
+++ b/spec/models/integrations/prometheus_spec.rb
@@ -90,37 +90,6 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching,
end
end
end
-
- context 'with self-monitoring project and internal Prometheus' do
- before do
- integration.api_url = 'http://localhost:9090'
-
- stub_application_setting(self_monitoring_project_id: project.id)
- stub_config(prometheus: { enable: true, server_address: 'localhost:9090' })
- end
-
- it 'allows self-monitoring project to connect to internal Prometheus' do
- aggregate_failures do
- ['127.0.0.1', '192.168.2.3'].each do |url|
- allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)])
-
- expect(integration.can_query?).to be true
- end
- end
- end
-
- it 'does not allow self-monitoring project to connect to other local URLs' do
- integration.api_url = 'http://localhost:8000'
-
- aggregate_failures do
- ['127.0.0.1', '192.168.2.3'].each do |url|
- allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)])
-
- expect(integration.can_query?).to be false
- end
- end
- end
- end
end
end
@@ -218,23 +187,6 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching,
it 'blocks local requests' do
expect(integration.prometheus_client).to be_nil
end
-
- context 'with self-monitoring project and internal Prometheus URL' do
- before do
- stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
- stub_application_setting(self_monitoring_project_id: project.id)
-
- stub_config(prometheus: {
- enable: true,
- server_address: api_url
- })
- end
-
- it 'allows local requests' do
- expect(integration.prometheus_client).not_to be_nil
- expect { integration.prometheus_client.ping }.not_to raise_error
- end
- end
end
context 'behind IAP' do
diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb
new file mode 100644
index 00000000000..9966a7132ce
--- /dev/null
+++ b/spec/models/organization_spec.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# rubocop: disable Lint/EmptyBlock
+# rubocop: disable RSpec/EmptyExampleGroup
+RSpec.describe Organization, type: :model, feature_category: :cell do
+end
+# rubocop: enable RSpec/EmptyExampleGroup
+# rubocop: enable Lint/EmptyBlock
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index f25ff0884b9..b5b7a283e39 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -7551,24 +7551,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
end
end
- describe '#self_monitoring?' do
- let_it_be(:project) { create(:project) }
-
- subject { project.self_monitoring? }
-
- context 'when the project is instance self-monitoring' do
- before do
- stub_application_setting(self_monitoring_project_id: project.id)
- end
-
- it { is_expected.to be true }
- end
-
- context 'when the project is not self-monitoring' do
- it { is_expected.to be false }
- end
- end
-
describe '#add_export_job' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 60583bc351d..f1d3b17fd6f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2174,6 +2174,20 @@ RSpec.describe User, feature_category: :user_profile do
end
end
+ describe '#sign_in_with_codes_allowed?' do
+ let_it_be(:user) { create(:user, :two_factor_via_webauthn) }
+
+ context 'when `webauthn_without_totp` disabled' do
+ before do
+ stub_feature_flags(webauthn_without_totp: false)
+ end
+
+ it { expect(user.sign_in_with_codes_allowed?).to eq(false) }
+ end
+
+ it { expect(user.sign_in_with_codes_allowed?).to eq(true) }
+ end
+
describe '#two_factor_otp_enabled?' do
let_it_be(:user) { create(:user) }
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 2b956bec469..48f59ba596b 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -463,16 +463,26 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do
end
context 'subresources' do
- let(:user) { create(:user) }
- let(:member_user) { create(:user) }
+ let_it_be_with_reload(:user) { create(:user) }
+ let_it_be_with_reload(:member_user) { create(:user) }
+
+ let_it_be_with_reload(:group) { create(:group, :public) }
+ let_it_be_with_reload(:subgroup) { create(:group, parent: group) }
+ let_it_be(:private_subgroup) { create(:group, :private, parent: group, name: 'private_subgroup') }
+ let_it_be(:private_subgroup_with_direct_membership) { create(:group, :private, parent: group) }
+ let_it_be_with_reload(:subsubgroup) { create(:group, parent: subgroup) }
+
+ let_it_be_with_reload(:group_project) { create(:project, :public, group: group) }
+ let_it_be_with_reload(:control_project) { create(:project, :private, group: subsubgroup) }
+ let_it_be_with_reload(:subsubproject) { create(:project, :public, group: subsubgroup) }
- let(:group) { create(:group, :public) }
- let(:subgroup) { create(:group, parent: group) }
- let(:subsubgroup) { create(:group, parent: subgroup) }
- let(:subsubproject) { create(:project, group: subsubgroup) }
+ let_it_be(:private_subgroup_project) do
+ create(:project, :private, group: private_subgroup, name: 'private_subgroup_project')
+ end
- let(:group_project) { create(:project, :public, group: group) }
- let(:control_project) { create(:project, group: subsubgroup) }
+ let_it_be(:private_subgroup_with_direct_membership_project) do
+ create(:project, :private, group: private_subgroup_with_direct_membership, name: 'private_subgroup_project')
+ end
context 'with memberships' do
before do
@@ -481,14 +491,68 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do
subsubproject.add_developer(member_user)
group_project.add_developer(member_user)
control_project.add_maintainer(user)
+ private_subgroup_with_direct_membership.add_developer(member_user)
group.add_owner(user)
@group_member = create(:group_member, :developer, group: group, user: member_user)
end
+ let_it_be(:todo_in_public_group_project) do
+ create(:todo, :pending,
+ project: group_project,
+ user: member_user,
+ target: create(:issue, project: group_project)
+ )
+ end
+
+ let_it_be(:mr_in_public_group_project) do
+ create(:merge_request, source_project: group_project, assignees: [member_user])
+ end
+
+ let_it_be(:todo_in_private_subgroup_project) do
+ create(:todo, :pending,
+ project: private_subgroup_project,
+ user: member_user,
+ target: create(:issue, project: private_subgroup_project)
+ )
+ end
+
+ let_it_be(:mr_in_private_subgroup_project) do
+ create(:merge_request, source_project: private_subgroup_project, assignees: [member_user])
+ end
+
+ let_it_be(:todo_in_public_subsubgroup_project) do
+ create(:todo, :pending,
+ project: subsubproject,
+ user: member_user,
+ target: create(:issue, project: subsubproject)
+ )
+ end
+
+ let_it_be(:mr_in_public_subsubgroup_project) do
+ create(:merge_request, source_project: subsubproject, assignees: [member_user])
+ end
+
+ let_it_be(:todo_in_private_subgroup_with_direct_membership_project) do
+ create(:todo, :pending,
+ project: private_subgroup_with_direct_membership_project,
+ user: member_user,
+ target: create(:issue, project: private_subgroup_with_direct_membership_project)
+ )
+ end
+
+ let_it_be(:mr_in_private_subgroup_with_direct_membership_project) do
+ create(:merge_request,
+ source_project: private_subgroup_with_direct_membership_project,
+ assignees: [member_user]
+ )
+ end
+
context 'with skipping of subresources' do
+ subject(:execute_service) { described_class.new(user).execute(@group_member, skip_subresources: true) }
+
before do
- described_class.new(user).execute(@group_member, skip_subresources: true)
+ execute_service
end
it 'removes the group membership' do
@@ -514,11 +578,35 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do
it 'does not remove the user from the control project' do
expect(control_project.members.map(&:user)).to include(user)
end
+
+ context 'todos', :sidekiq_inline do
+ it 'removes todos for which the user no longer has access' do
+ expect(member_user.todos).to include(
+ todo_in_public_group_project,
+ todo_in_public_subsubgroup_project,
+ todo_in_private_subgroup_with_direct_membership_project
+ )
+
+ expect(member_user.todos).not_to include(todo_in_private_subgroup_project)
+ end
+ end
+
+ context 'issuables', :sidekiq_inline do
+ subject(:execute_service) do
+ described_class.new(user).execute(@group_member, skip_subresources: true, unassign_issuables: true)
+ end
+
+ it 'removes assigned issuables, even in subresources' do
+ expect(member_user.assigned_merge_requests).to be_empty
+ end
+ end
end
context 'without skipping of subresources' do
+ subject(:execute_service) { described_class.new(user).execute(@group_member, skip_subresources: false) }
+
before do
- described_class.new(user).execute(@group_member, skip_subresources: false)
+ execute_service
end
it 'removes the project membership' do
@@ -544,6 +632,30 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do
it 'does not remove the user from the control project' do
expect(control_project.members.map(&:user)).to include(user)
end
+
+ context 'todos', :sidekiq_inline do
+ it 'removes todos for which the user no longer has access' do
+ expect(member_user.todos).to include(
+ todo_in_public_group_project,
+ todo_in_public_subsubgroup_project
+ )
+
+ expect(member_user.todos).not_to include(
+ todo_in_private_subgroup_project,
+ todo_in_private_subgroup_with_direct_membership_project
+ )
+ end
+ end
+
+ context 'issuables', :sidekiq_inline do
+ subject(:execute_service) do
+ described_class.new(user).execute(@group_member, skip_subresources: false, unassign_issuables: true)
+ end
+
+ it 'removes assigned issuables' do
+ expect(member_user.assigned_merge_requests).to be_empty
+ end
+ end
end
end
@@ -626,4 +738,13 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do
expect(project.members.not_accepted_invitations_by_user(member_user)).to be_empty
end
end
+
+ describe '#mark_as_recursive_call' do
+ it 'marks the instance as recursive' do
+ service = described_class.new(current_user)
+ service.mark_as_recursive_call
+
+ expect(service.send(:recursive_call?)).to eq(true)
+ end
+ end
end
diff --git a/spec/support/helpers/user_login_helper.rb b/spec/support/helpers/user_login_helper.rb
index 47e858cb68c..d8368a94ad7 100644
--- a/spec/support/helpers/user_login_helper.rb
+++ b/spec/support/helpers/user_login_helper.rb
@@ -30,4 +30,20 @@ module UserLoginHelper
def ensure_one_active_pane
expect(page).to have_selector('.tab-pane.active', count: 1)
end
+
+ def ensure_remember_me_in_tab(tab_name)
+ find_link(tab_name).click
+
+ within '.tab-pane.active' do
+ expect(page).to have_content _('Remember me')
+ end
+ end
+
+ def ensure_remember_me_not_in_tab(tab_name)
+ find_link(tab_name).click
+
+ within '.tab-pane.active' do
+ expect(page).not_to have_content _('Remember me')
+ end
+ end
end
diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb
index fb45bc0864d..c2cc6d05630 100644
--- a/spec/tasks/gitlab/db_rake_spec.rb
+++ b/spec/tasks/gitlab/db_rake_spec.rb
@@ -961,19 +961,17 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor
using RSpec::Parameterized::TableSyntax
let(:task) { 'gitlab:db:active' }
- let(:self_monitoring) { double('self_monitoring') }
- where(:needs_migration, :self_monitoring_project, :project_count, :exit_status, :exit_code) do
- true | nil | nil | 1 | false
- false | :self_monitoring | 1 | 1 | false
- false | nil | 0 | 1 | false
- false | :self_monitoring | 2 | 0 | true
+ where(:needs_migration, :project_count, :exit_status, :exit_code) do
+ true | nil | 1 | false
+ false | 1 | 0 | true
+ false | 0 | 1 | false
+ false | 2 | 0 | true
end
with_them do
it 'exits 0 or 1 depending on user modifications to the database' do
allow_any_instance_of(ActiveRecord::MigrationContext).to receive(:needs_migration?).and_return(needs_migration)
- allow_any_instance_of(ApplicationSetting).to receive(:self_monitoring_project).and_return(self_monitoring_project)
allow(Project).to receive(:count).and_return(project_count)
expect { run_rake_task(task) }.to raise_error do |error|