diff options
27 files changed, 530 insertions, 110 deletions
diff --git a/app/assets/javascripts/batch_comments/components/preview_dropdown.vue b/app/assets/javascripts/batch_comments/components/preview_dropdown.vue index 91b3b6a685c..e90c29e939f 100644 --- a/app/assets/javascripts/batch_comments/components/preview_dropdown.vue +++ b/app/assets/javascripts/batch_comments/components/preview_dropdown.vue @@ -15,14 +15,14 @@ export default { ...mapGetters('batchComments', ['draftsCount', 'sortedDrafts']), }, methods: { - ...mapActions('diffs', ['toggleActiveFileByHash']), + ...mapActions('diffs', ['setCurrentFileHash']), ...mapActions('batchComments', ['scrollToDraft']), isLast(index) { return index === this.sortedDrafts.length - 1; }, async onClickDraft(draft) { if (this.viewDiffsFileByFile && draft.file_hash) { - await this.toggleActiveFileByHash(draft.file_hash); + await this.setCurrentFileHash(draft.file_hash); } await this.scrollToDraft(draft); diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index 4bcb99424db..746646948fe 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -221,7 +221,7 @@ export default { 'toggleFileDiscussions', 'toggleFileDiscussionWrappers', 'toggleFullDiff', - 'toggleActiveFileByHash', + 'setCurrentFileHash', 'reviewFile', 'setFileCollapsedByUser', ]), @@ -244,7 +244,7 @@ export default { scrollToElement(document.querySelector(selector)); window.location.hash = selector; if (!this.viewDiffsFileByFile) { - this.toggleActiveFileByHash(this.diffFile.file_hash); + this.setCurrentFileHash(this.diffFile.file_hash); } } }, diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 9e828450acf..692cb913a57 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -85,6 +85,12 @@ export const setBaseConfig = ({ commit }, options) => { viewDiffsFileByFile, mrReviews, }); + + Array.from(new Set(Object.values(mrReviews).flat())).forEach((id) => { + const viewedId = id.replace(/^hash:/, ''); + + commit(types.SET_DIFF_FILE_VIEWED, { id: viewedId, seen: true }); + }); }; export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => { @@ -127,7 +133,7 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => { } if (!isNoteLink && !state.currentDiffFileId) { - commit(types.VIEW_DIFF_FILE, diff_files[0]?.file_hash); + commit(types.SET_CURRENT_DIFF_FILE, diff_files[0]?.file_hash); } if (isNoteLink) { @@ -143,7 +149,7 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => { !state.diffFiles.some((f) => f.file_hash === state.currentDiffFileId) && !isNoteLink ) { - commit(types.VIEW_DIFF_FILE, state.diffFiles[0].file_hash); + commit(types.SET_CURRENT_DIFF_FILE, state.diffFiles[0].file_hash); } if (state.diffFiles?.length) { @@ -248,7 +254,7 @@ export const fetchCoverageFiles = ({ commit, state }) => { export const setHighlightedRow = ({ commit }, lineCode) => { const fileHash = lineCode.split('_')[0]; commit(types.SET_HIGHLIGHTED_ROW, lineCode); - commit(types.VIEW_DIFF_FILE, fileHash); + commit(types.SET_CURRENT_DIFF_FILE, fileHash); handleLocationHash(); }; @@ -514,8 +520,8 @@ export const toggleTreeOpen = ({ commit }, path) => { commit(types.TOGGLE_FOLDER_OPEN, path); }; -export const toggleActiveFileByHash = ({ commit }, hash) => { - commit(types.VIEW_DIFF_FILE, hash); +export const setCurrentFileHash = ({ commit }, hash) => { + commit(types.SET_CURRENT_DIFF_FILE, hash); }; export const scrollToFile = ({ state, commit, getters }, { path, setHash = true }) => { @@ -523,7 +529,7 @@ export const scrollToFile = ({ state, commit, getters }, { path, setHash = true const { fileHash } = state.treeEntries[path]; - commit(types.VIEW_DIFF_FILE, fileHash); + commit(types.SET_CURRENT_DIFF_FILE, fileHash); if (getters.isVirtualScrollingEnabled) { eventHub.$emit('scrollToFileHash', fileHash); @@ -806,7 +812,7 @@ export const setCurrentDiffFileIdFromNote = ({ commit, state, rootGetters }, not const fileHash = rootGetters.getDiscussion(note.discussion_id).diff_file?.file_hash; if (fileHash && state.diffFiles.some((f) => f.file_hash === fileHash)) { - commit(types.VIEW_DIFF_FILE, fileHash); + commit(types.SET_CURRENT_DIFF_FILE, fileHash); } }; @@ -814,7 +820,7 @@ export const navigateToDiffFileIndex = ({ commit, state }, index) => { const fileHash = state.diffFiles[index].file_hash; document.location.hash = fileHash; - commit(types.VIEW_DIFF_FILE, fileHash); + commit(types.SET_CURRENT_DIFF_FILE, fileHash); }; export const setFileByFile = ({ state, commit }, { fileByFile }) => { @@ -850,6 +856,8 @@ export function reviewFile({ commit, state }, { file, reviewed = true }) { const reviews = markFileReview(state.mrReviews, file, reviewed); setReviewsForMergeRequest(mrPath, reviews); + + commit(types.SET_DIFF_FILE_VIEWED, { id: file.file_hash, seen: reviewed }); commit(types.SET_MR_FILE_REVIEWS, reviews); } diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js index 60836f747f5..51c21c1bfc4 100644 --- a/app/assets/javascripts/diffs/store/mutation_types.js +++ b/app/assets/javascripts/diffs/store/mutation_types.js @@ -20,7 +20,8 @@ export const SET_LINE_DISCUSSIONS_FOR_FILE = 'SET_LINE_DISCUSSIONS_FOR_FILE'; export const REMOVE_LINE_DISCUSSIONS_FOR_FILE = 'REMOVE_LINE_DISCUSSIONS_FOR_FILE'; export const TOGGLE_FOLDER_OPEN = 'TOGGLE_FOLDER_OPEN'; export const SET_SHOW_TREE_LIST = 'SET_SHOW_TREE_LIST'; -export const VIEW_DIFF_FILE = 'VIEW_DIFF_FILE'; +export const SET_CURRENT_DIFF_FILE = 'SET_CURRENT_DIFF_FILE'; +export const SET_DIFF_FILE_VIEWED = 'SET_DIFF_FILE_VIEWED'; export const OPEN_DIFF_FILE_COMMENT_FORM = 'OPEN_DIFF_FILE_COMMENT_FORM'; export const UPDATE_DIFF_FILE_COMMENT_FORM = 'UPDATE_DIFF_FILE_COMMENT_FORM'; diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 6bc927b9d1f..4a9df0eafcc 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -254,9 +254,11 @@ export default { [types.SET_SHOW_TREE_LIST](state, showTreeList) { state.showTreeList = showTreeList; }, - [types.VIEW_DIFF_FILE](state, fileId) { + [types.SET_CURRENT_DIFF_FILE](state, fileId) { state.currentDiffFileId = fileId; - Vue.set(state.viewedDiffFileIds, fileId, true); + }, + [types.SET_DIFF_FILE_VIEWED](state, { id, seen }) { + Vue.set(state.viewedDiffFileIds, id, seen); }, [types.OPEN_DIFF_FILE_COMMENT_FORM](state, formData) { state.commentForms.push({ diff --git a/app/assets/javascripts/diffs/utils/file_reviews.js b/app/assets/javascripts/diffs/utils/file_reviews.js index 7a4b1aa6b17..227be4e4a6c 100644 --- a/app/assets/javascripts/diffs/utils/file_reviews.js +++ b/app/assets/javascripts/diffs/utils/file_reviews.js @@ -52,8 +52,10 @@ export function markFileReview(reviews, file, reviewed = true) { if (reviewed) { fileReviews.add(file.id); + fileReviews.add(`hash:${file.file_hash}`); } else { fileReviews.delete(file.id); + fileReviews.delete(`hash:${file.file_hash}`); } updatedReviews[file.file_identifier_hash] = Array.from(fileReviews); diff --git a/config/metrics/counts_28d/20211102205024_p_ci_templates_security_sast_iac_latest_monthly.yml b/config/metrics/counts_28d/20211102205024_p_ci_templates_security_sast_iac_latest_monthly.yml new file mode 100644 index 00000000000..c9589f3ff29 --- /dev/null +++ b/config/metrics/counts_28d/20211102205024_p_ci_templates_security_sast_iac_latest_monthly.yml @@ -0,0 +1,26 @@ +--- +key_path: redis_hll_counters.ci_templates.p_ci_templates_security_sast_iac_latest_monthly +description: Count of pipelines using the latest SAST IaC template +product_section: sec +product_stage: secure +product_group: "group::static analysis" +product_category: SAST +value_type: number +status: active +milestone: "14.5" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73076 +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_security_sast_iac_latest diff --git a/config/metrics/counts_28d/20211102205223_p_ci_templates_implicit_security_sast_iac_latest_monthly.yml b/config/metrics/counts_28d/20211102205223_p_ci_templates_implicit_security_sast_iac_latest_monthly.yml new file mode 100644 index 00000000000..9e5724b1e40 --- /dev/null +++ b/config/metrics/counts_28d/20211102205223_p_ci_templates_implicit_security_sast_iac_latest_monthly.yml @@ -0,0 +1,26 @@ +--- +key_path: redis_hll_counters.ci_templates.p_ci_templates_implicit_security_sast_iac_latest_monthly +description: Count of pipelines with implicit runs using the latest SAST IaC template +product_section: sec +product_stage: secure +product_group: "group::static analysis" +product_category: SAST +value_type: number +status: active +milestone: "14.5" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73076 +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_implicit_security_sast_iac_latest diff --git a/config/metrics/counts_28d/20211102213704_p_ci_templates_jobs_sast_iac_latest_monthly.yml b/config/metrics/counts_28d/20211102213704_p_ci_templates_jobs_sast_iac_latest_monthly.yml new file mode 100644 index 00000000000..40aa9cdbab1 --- /dev/null +++ b/config/metrics/counts_28d/20211102213704_p_ci_templates_jobs_sast_iac_latest_monthly.yml @@ -0,0 +1,26 @@ +--- +key_path: redis_hll_counters.ci_templates.p_ci_templates_jobs_sast_iac_latest_monthly +description: Count of pipelines using the latest SAST IaC template +product_section: sec +product_stage: secure +product_group: "group::static analysis" +product_category: SAST +value_type: number +status: active +milestone: "14.5" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73074 +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_jobs_sast_iac_latest diff --git a/config/metrics/counts_28d/20211102213921_p_ci_templates_implicit_jobs_sast_iac_latest_monthly.yml b/config/metrics/counts_28d/20211102213921_p_ci_templates_implicit_jobs_sast_iac_latest_monthly.yml new file mode 100644 index 00000000000..3fa3c751366 --- /dev/null +++ b/config/metrics/counts_28d/20211102213921_p_ci_templates_implicit_jobs_sast_iac_latest_monthly.yml @@ -0,0 +1,26 @@ +--- +key_path: redis_hll_counters.ci_templates.p_ci_templates_implicit_jobs_sast_iac_latest_monthly +description: Count of pipelines with implicit runs using the latest SAST IaC template +product_section: sec +product_stage: secure +product_group: "group::static analysis" +product_category: SAST +value_type: number +status: active +milestone: "14.5" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73074 +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_implicit_jobs_sast_iac_latest diff --git a/config/metrics/counts_7d/20211102202454_p_ci_templates_security_sast_iac_latest_weekly.yml b/config/metrics/counts_7d/20211102202454_p_ci_templates_security_sast_iac_latest_weekly.yml new file mode 100644 index 00000000000..e7e2c096902 --- /dev/null +++ b/config/metrics/counts_7d/20211102202454_p_ci_templates_security_sast_iac_latest_weekly.yml @@ -0,0 +1,27 @@ +--- +data_category: optional +key_path: redis_hll_counters.ci_templates.p_ci_templates_security_sast_iac_latest_weekly +description: Count of pipelines using the latest SAST IaC template +product_section: sec +product_stage: secure +product_group: "group::static analysis" +product_category: SAST +value_type: number +status: active +milestone: "14.5" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73076 +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_security_sast_iac_latest diff --git a/config/metrics/counts_7d/20211102204352_p_ci_templates_implicit_security_sast_iac_latest_weekly.yml b/config/metrics/counts_7d/20211102204352_p_ci_templates_implicit_security_sast_iac_latest_weekly.yml new file mode 100644 index 00000000000..09070adb2c1 --- /dev/null +++ b/config/metrics/counts_7d/20211102204352_p_ci_templates_implicit_security_sast_iac_latest_weekly.yml @@ -0,0 +1,26 @@ +--- +key_path: redis_hll_counters.ci_templates.p_ci_templates_implicit_security_sast_iac_latest_weekly +description: Count of pipelines with implicit runs using the latest SAST IaC template +product_section: sec +product_stage: secure +product_group: "group::static analysis" +product_category: SAST +value_type: number +status: active +milestone: "14.5" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73076 +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_implicit_security_sast_iac_latest diff --git a/config/metrics/counts_7d/20211102213112_p_ci_templates_jobs_sast_iac_latest_weekly.yml b/config/metrics/counts_7d/20211102213112_p_ci_templates_jobs_sast_iac_latest_weekly.yml new file mode 100644 index 00000000000..3fdd37bffa5 --- /dev/null +++ b/config/metrics/counts_7d/20211102213112_p_ci_templates_jobs_sast_iac_latest_weekly.yml @@ -0,0 +1,26 @@ +--- +key_path: redis_hll_counters.ci_templates.p_ci_templates_jobs_sast_iac_latest_weekly +description: Count of pipelines using the latest SAST IaC template +product_section: sec +product_stage: secure +product_group: "group::static analysis" +product_category: SAST +value_type: number +status: active +milestone: "14.5" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73074 +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_jobs_sast_iac_latest diff --git a/config/metrics/counts_7d/20211102213417_p_ci_templates_implicit_jobs_sast_iac_latest_weekly.yml b/config/metrics/counts_7d/20211102213417_p_ci_templates_implicit_jobs_sast_iac_latest_weekly.yml new file mode 100644 index 00000000000..3a58cd033f2 --- /dev/null +++ b/config/metrics/counts_7d/20211102213417_p_ci_templates_implicit_jobs_sast_iac_latest_weekly.yml @@ -0,0 +1,26 @@ +--- +key_path: redis_hll_counters.ci_templates.p_ci_templates_implicit_jobs_sast_iac_latest_weekly +description: Count of pipelines with implicit runs using the latest SAST IaC template +product_section: sec +product_stage: secure +product_group: "group::static analysis" +product_category: SAST +value_type: number +status: active +milestone: "14.5" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73074 +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_implicit_jobs_sast_iac_latest diff --git a/doc/architecture/blueprints/consolidating_groups_and_projects/index.md b/doc/architecture/blueprints/consolidating_groups_and_projects/index.md index d75c2c8e703..53357220755 100644 --- a/doc/architecture/blueprints/consolidating_groups_and_projects/index.md +++ b/doc/architecture/blueprints/consolidating_groups_and_projects/index.md @@ -139,7 +139,7 @@ The initial iteration will provide a framework to house features under `Namespac 1. **Tier**: Is there any tier functionality that is differentiated by projects and groups? 1. **Documentation**: Is the structure and content of documentation impacted by these changes at all? 1. **Solution proposal**: - - Think big: This analysis provides a great opportunity to zoom out and consider the feature UX as a whole. How could you make this feature lovable based on the new structure, inheritance, and capabilities afforded by `Namespaces`? Is there any UI which doesn't comply with Pajamas? + - Think big: This analysis provides a great opportunity to zoom out and consider the feature UX as a whole. How could you make this feature lovable based on the new structure, inheritance, and capabilities afforded by `Namespaces`? Is there any UI which doesn't comply with Pajamas? - Start small: What are the product changes that need to be made to assist with the migration? - Move fast: Prioritise these solution ideas, document in issues, and create a roadmap for implementation. @@ -170,3 +170,7 @@ DRIs: | Design | Nick Post | <!-- vale gitlab.Spelling = YES --> + +## Related topics + +- [Workspaces developer documentation](../../../development/workspaces/index.md) diff --git a/doc/ci/runners/build_cloud/linux_build_cloud.md b/doc/ci/runners/build_cloud/linux_build_cloud.md index 6cdc93591a7..ae9a4c4620d 100644 --- a/doc/ci/runners/build_cloud/linux_build_cloud.md +++ b/doc/ci/runners/build_cloud/linux_build_cloud.md @@ -31,6 +31,8 @@ Below are the runners' settings. | Default Docker image | `ruby:2.5` | - | | `privileged` (run [Docker in Docker](https://hub.docker.com/_/docker/)) | `true` | `false` | +These runners share a [distributed cache](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) through use of a Google Cloud Storage (GCS) bucket. Cache contents not updated within the last 14 days are automatically removed through use of an [object lifecycle management policy](https://cloud.google.com/storage/docs/lifecycle). + ## Pre-clone script Build Cloud runners for Linux provide a way to run commands in a CI diff --git a/doc/development/workspaces/index.md b/doc/development/workspaces/index.md index 9fb5f404061..b36c79e84d7 100644 --- a/doc/development/workspaces/index.md +++ b/doc/development/workspaces/index.md @@ -9,87 +9,112 @@ description: "Development Guidelines: learn about workspaces when developing Git # Workspaces -[Read more](../../user/workspace/index.md) about the workspaces initiative. +The [Workspaces initiative](../../user/workspace/index.md) focuses on reaching feature parity between +SaaS and self-managed installations. -## Consolidate Groups and Projects +## Consolidate groups and projects - [Architecture blueprint](../../architecture/blueprints/consolidating_groups_and_projects/index.md) -Consolidate Groups and Projects is one facet of the workspaces initiative, addressing the feature disparity between -groups and projects. +One facet of the workspaces initiative is to consolidate groups and projects, +addressing the feature disparity between them. Some features, such as epics, are +only available at the group level. Some features, such as issues, are only available +at the project level. Other features, such as milestones, are available to both groups +and projects. -There is feature disparity between groups and projects. Some features only available at group level (for example epics). -Some features only available at project level (for example issues). And some features available at both levels -(for example labels, milestones). +We receive many requests to add features either to the group or project level. +Moving features around to different levels is problematic on multiple levels: -We get more and more requests to get one feature or another added to either group or project level. This takes -engineering time, to just move features around to different levels. This also adds a UX overhead of keeping a mental -model of which features are available at which level. +- It requires engineering time to move the features. +- It requires UX overhead to maintain mental models of feature availability. +- It creates redundant code. -This also creates lots of redundant code. Features get copied from project, to group to instance level with small -nuances between them. This also causes extra maintenance, when something needs to be fixed. The fix is copied to -several locations. This also creates different user experience for the same feature but in the different locations. +When features are copied from one level (project, group, or instance) to another, +the copies often have small, nuanced differences between them. These nuances cause +extra engineering time when fixes are needed, because the fix must be copied to +several locations. These nuances also create different user experiences when the +feature is used in different places. -To solve this we are moving towards consolidating groups and projects into a single entity, namespace. The work on this -solution is split into several phases and is tracked in [epic 6473](https://gitlab.com/groups/gitlab-org/-/epics/6473). +A solution for this problem is to consolidate groups and projects into a single +entity, `namespace`. The work on this solution is split into several phases and +is tracked in [epic 6473](https://gitlab.com/groups/gitlab-org/-/epics/6473). ### Phase 1 -Epic tracking [Phase 1](https://gitlab.com/groups/gitlab-org/-/epics/6697) - -Goal of Phase 1 is to ensure that every project gets a corresponding record in `namespaces` table with `type='Project'`. -For user namespaces, type changes from `NULL` to `User`. - -Places where we should make sure project and project namespace go hand in hand: - -- Create project. - - Use Rails callbacks to ensure a new project namespace is created for each project. - - In this case project namespace records should have `created_at`/`updated_at` attributes equal to project's `created_at`/`updated_at` attributes. -- Update Project. - - Use Rails `after_save` callback to ensure some attributes are kept in sync between project and project namespaces, - see [project#after_save](https://gitlab.com/gitlab-org/gitlab/blob/6d26634e864d7b748dda0e283eb2477362263bc3/app/models/project.rb#L101-L101). -- Delete project. - - Use FKs cascade delete or Rails callbacks to ensure when either a `Project` or its `ProjectNamespace` is removed its - corresponding `ProjectNamespace` or `Project` respectively is removed as well. -- Transfer project to a different group. - - Make sure that when a project is transferred, its corresponding project namespace is transferred to the same group. -- Transfer group. - - Make sure when transferring a group that all of its sub projects, either direct or through descendant groups, have their - corresponding project namespaces transferred correctly as well. -- Export/import project. - - Export project would continue to only export the project and not its project namespace in this phase. Project - namespace does not contain any specific information that has to be exported at this point. Eventually we want the - project namespace to be exported as well. - - Import creates a new project, so project namespace is created through Rails `after_save` callback on the project model. -- Export/import group. - - As of this writing, when importing or exporting a `Group`, `Project`s are not included in the operation. If that functionality is changed to include `Project` when its Group is imported/exported, the logic must be sure to include their corresponding project namespaces in the import/export. - -After ensuring the above points, we plan to run a DB migration to create a `ProjectNamespace` record for every `Project`. -Project namespace records created during migration should have `created_at`/`updated_at` attributes set to migration -runtime. That means that project namespaces `created_at`/`updated_at` attributes don't match their corresponding -project's `created_at`/`updated_at` attributes. We want the different dates to help audit any of the created project -namespaces, in case we need it. We plan to run the back-filling migration in 14.5 milestone. +- [Phase 1 epic](https://gitlab.com/groups/gitlab-org/-/epics/6697). +- **Goals**: + 1. Ensure every project receives a corresponding record in the `namespaces` + table with `type='Project'`. + 1. For user namespaces, the type changes from `NULL` to `User`. + +We should make sure that projects, and the project namespace, are equivalent: + +- **Create project:** use Rails callbacks to ensure a new project namespace is + created for each project. Project namespace records should contain `created_at` and + `updated_at` attributes equal to the project's `created_at`/`updated_at` attributes. +- **Update project:** use the `after_save` callback in Rails to ensure some + attributes are kept in sync between project and project namespaces. + Read [`project#after_save`](https://gitlab.com/gitlab-org/gitlab/blob/6d26634e864d7b748dda0e283eb2477362263bc3/app/models/project.rb#L101-L101) + for more information. +- **Delete project:** use FKs cascade delete or Rails callbacks to ensure when a `Project` + or its `ProjectNamespace` is removed, its corresponding `ProjectNamespace` or `Project` + is also removed. +- **Transfer project to a different group:** make sure that when a project is transferred, + its corresponding project namespace is transferred to the same group. +- **Transfer group:** make sure when transferring a group that all of its sub-projects, + either direct or through descendant groups, have their corresponding project + namespaces transferred correctly as well. +- **Export or import project** + - **Export project** continues to export only the project, and not its project namespace, + in this phase. The project namespace does not contain any specific information + to export at this point. Eventually we want the project namespace to be exported as well. + - **Import project** creates a new project, so the project namespace is created through + Rails `after_save` callback on the project model. +- **Export or import group:** when importing or exporting a `Group`, projects are not + included in the operation. If that feature is changed to include `Project` when its group is + imported or exported, the logic must include their corresponding project namespaces + in the import or export. + +After ensuring these points, run a database migration to create a `ProjectNamespace` +record for every `Project`. Project namespace records created during the migration +should have `created_at` and `updated_at` attributes set to the migration runtime. +The project namespaces' `created_at` and `updated_at` attributes would not match +their corresponding project's `created_at` and `updated_at` attributes. We want +the different dates to help audit any of the created project namespaces, in case we need it. +After this work completes, we must migrate data as described in +[Backfill `ProjectNamespace` for every Project](https://gitlab.com/gitlab-org/gitlab/-/issues/337100). ### Phase 2 -Epic tracking [Phase 2](https://gitlab.com/groups/gitlab-org/-/epics/6768) +- [Phase 2 epic](https://gitlab.com/groups/gitlab-org/-/epics/6768). +- **Goal**: Make `ProjectNamespace` the front entity to interact with instead of `Project`. + +In this phase: + +- Communicate the changes company-wide at the engineering level. We want to make + engineers aware of the upcoming changes, even though teams are not expected to + collaborate actively until phase 3. +- Raise awareness to avoid regressions, and conflicting or duplicate work that + can be dealt with before phase 3. -The focus of this phase is to make `ProjectNamespace` the front entity to interact with instead of `Project` . Problems to solve as part of this phase: -- Routes handling through project namespace rather than project. +- Routes handling through `ProjectNamespace` rather than `Project`. - Memberships handling. - Policies handling. -- Import/export. +- Import and export. - Other interactions between project namespace and project models. -Communicate the changes company wide at the engineers level. We want engineers to be aware of the upcoming changes even -though active collaboration of teams is expected only in phase 3. Raise awareness to avoid regressions, conflicting or duplicate work -that can be taken care of even before phase 3. - ### Phase 3 -Epic tracking [Phase 3](https://gitlab.com/groups/gitlab-org/-/epics/6585) +- [Phase 3 epic](https://gitlab.com/groups/gitlab-org/-/epics/6585). +- **Goal**: Feature parity between the namespace types. + +Phase 3 is when the active migration of features from `Project` to `ProjectNamespace`, +or directly to `Namespace`, happens. + +## Related topics -The focus of this phase is to get feature parity between the namespace types. Phase 3 is when active migration -of features from `Project` to `ProjectNamespace` or directly to `Namespace` happens. +- [Consolidating groups and projects](../../architecture/blueprints/consolidating_groups_and_projects/index.md) + architecture documentation +- [Workspace user documentation](../../user/workspace/index.md) diff --git a/doc/user/workspace/index.md b/doc/user/workspace/index.md index d75d91f087d..8ca7f7defb2 100644 --- a/doc/user/workspace/index.md +++ b/doc/user/workspace/index.md @@ -46,3 +46,7 @@ The following provide a preview to the Workspace concept. ![Admin Overview](img/Admin_Settings.png) ![Admin Overview](img/hardware_settings.png) + +## Related topics + +- [Workspaces developer documentation](../../development/workspaces/index.md) diff --git a/lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml new file mode 100644 index 00000000000..b763705857e --- /dev/null +++ b/lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml @@ -0,0 +1,34 @@ +variables: + # Setting this variable will affect all Security templates + # (SAST, Dependency Scanning, ...) + SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers" + SAST_EXCLUDED_PATHS: "spec, test, tests, tmp" + +iac-sast: + stage: test + artifacts: + reports: + sast: gl-sast-report.json + rules: + - when: never + # `rules` must be overridden explicitly by each child job + # see https://gitlab.com/gitlab-org/gitlab/-/issues/218444 + variables: + SEARCH_MAX_DEPTH: 4 + allow_failure: true + script: + - /analyzer run + +kics-iac-sast: + extends: iac-sast + image: + name: "$SAST_ANALYZER_IMAGE" + variables: + SAST_ANALYZER_IMAGE_TAG: 0 + SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/kics:$SAST_ANALYZER_IMAGE_TAG" + rules: + - if: $SAST_DISABLED + when: never + - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/ + when: never + - if: $CI_COMMIT_BRANCH diff --git a/lib/gitlab/ci/templates/Security/SAST-IaC.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST-IaC.latest.gitlab-ci.yml new file mode 100644 index 00000000000..8c0d72ff282 --- /dev/null +++ b/lib/gitlab/ci/templates/Security/SAST-IaC.latest.gitlab-ci.yml @@ -0,0 +1,2 @@ +include: + template: Jobs/SAST-IaC.latest.gitlab-ci.yml diff --git a/lib/gitlab/usage_data_counters/known_events/ci_templates.yml b/lib/gitlab/usage_data_counters/known_events/ci_templates.yml index 60a709e9a1d..40922433635 100644 --- a/lib/gitlab/usage_data_counters/known_events/ci_templates.yml +++ b/lib/gitlab/usage_data_counters/known_events/ci_templates.yml @@ -83,6 +83,10 @@ category: ci_templates redis_slot: ci_templates aggregation: weekly +- name: p_ci_templates_security_sast_iac_latest + category: ci_templates + redis_slot: ci_templates + aggregation: weekly - name: p_ci_templates_security_dast_runner_validation category: ci_templates redis_slot: ci_templates @@ -267,6 +271,10 @@ category: ci_templates redis_slot: ci_templates aggregation: weekly +- name: p_ci_templates_jobs_sast_iac_latest + category: ci_templates + redis_slot: ci_templates + aggregation: weekly - name: p_ci_templates_jobs_secret_detection category: ci_templates redis_slot: ci_templates @@ -447,6 +455,10 @@ category: ci_templates redis_slot: ci_templates aggregation: weekly +- name: p_ci_templates_implicit_jobs_sast_iac_latest + category: ci_templates + redis_slot: ci_templates + aggregation: weekly - name: p_ci_templates_implicit_jobs_secret_detection category: ci_templates redis_slot: ci_templates @@ -503,6 +515,10 @@ category: ci_templates redis_slot: ci_templates aggregation: weekly +- name: p_ci_templates_implicit_security_sast_iac_latest + category: ci_templates + redis_slot: ci_templates + aggregation: weekly - name: p_ci_templates_implicit_security_dast_runner_validation category: ci_templates redis_slot: ci_templates diff --git a/spec/frontend/batch_comments/components/preview_dropdown_spec.js b/spec/frontend/batch_comments/components/preview_dropdown_spec.js index 41be04d0b7e..5327879f003 100644 --- a/spec/frontend/batch_comments/components/preview_dropdown_spec.js +++ b/spec/frontend/batch_comments/components/preview_dropdown_spec.js @@ -7,7 +7,7 @@ Vue.use(Vuex); let wrapper; -const toggleActiveFileByHash = jest.fn(); +const setCurrentFileHash = jest.fn(); const scrollToDraft = jest.fn(); function factory({ viewDiffsFileByFile = false, draftsCount = 1, sortedDrafts = [] } = {}) { @@ -16,7 +16,7 @@ function factory({ viewDiffsFileByFile = false, draftsCount = 1, sortedDrafts = diffs: { namespaced: true, actions: { - toggleActiveFileByHash, + setCurrentFileHash, }, state: { viewDiffsFileByFile, @@ -51,7 +51,7 @@ describe('Batch comments preview dropdown', () => { await Vue.nextTick(); - expect(toggleActiveFileByHash).toHaveBeenCalledWith(expect.anything(), 'hash'); + expect(setCurrentFileHash).toHaveBeenCalledWith(expect.anything(), 'hash'); expect(scrollToDraft).toHaveBeenCalledWith(expect.anything(), { id: 1, file_hash: 'hash' }); }); diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js index b16ef8fe6b0..e39b761b54d 100644 --- a/spec/frontend/diffs/components/diff_file_header_spec.js +++ b/spec/frontend/diffs/components/diff_file_header_spec.js @@ -7,7 +7,7 @@ import { mockTracking, triggerEvent } from 'helpers/tracking_helper'; import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; import { DIFF_FILE_AUTOMATIC_COLLAPSE, DIFF_FILE_MANUAL_COLLAPSE } from '~/diffs/constants'; import { reviewFile } from '~/diffs/store/actions'; -import { SET_MR_FILE_REVIEWS } from '~/diffs/store/mutation_types'; +import { SET_DIFF_FILE_VIEWED, SET_MR_FILE_REVIEWS } from '~/diffs/store/mutation_types'; import { diffViewerModes } from '~/ide/constants'; import { scrollToElement } from '~/lib/utils/common_utils'; import { truncateSha } from '~/lib/utils/text_utility'; @@ -23,6 +23,7 @@ jest.mock('~/lib/utils/common_utils'); const diffFile = Object.freeze( Object.assign(diffDiscussionsMockData.diff_file, { id: '123', + file_hash: 'xyz', file_identifier_hash: 'abc', edit_path: 'link:/to/edit/path', blob: { @@ -58,7 +59,7 @@ describe('DiffFileHeader component', () => { toggleFileDiscussions: jest.fn(), toggleFileDiscussionWrappers: jest.fn(), toggleFullDiff: jest.fn(), - toggleActiveFileByHash: jest.fn(), + setCurrentFileHash: jest.fn(), setFileCollapsedByUser: jest.fn(), reviewFile: jest.fn(), }, @@ -553,7 +554,13 @@ describe('DiffFileHeader component', () => { reviewFile, { file, reviewed: true }, {}, - [{ type: SET_MR_FILE_REVIEWS, payload: { [file.file_identifier_hash]: [file.id] } }], + [ + { type: SET_DIFF_FILE_VIEWED, payload: { id: file.file_hash, seen: true } }, + { + type: SET_MR_FILE_REVIEWS, + payload: { [file.file_identifier_hash]: [file.id, `hash:${file.file_hash}`] }, + }, + ], [], ); }); diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js index 11ef3985abc..b5003a54917 100644 --- a/spec/frontend/diffs/store/actions_spec.js +++ b/spec/frontend/diffs/store/actions_spec.js @@ -99,6 +99,10 @@ describe('DiffsStoreActions', () => { const projectPath = '/root/project'; const dismissEndpoint = '/-/user_callouts'; const showSuggestPopover = false; + const mrReviews = { + a: ['z', 'hash:a'], + b: ['y', 'hash:a'], + }; testAction( setBaseConfig, @@ -110,6 +114,7 @@ describe('DiffsStoreActions', () => { projectPath, dismissEndpoint, showSuggestPopover, + mrReviews, }, { endpoint: '', @@ -131,8 +136,21 @@ describe('DiffsStoreActions', () => { projectPath, dismissEndpoint, showSuggestPopover, + mrReviews, }, }, + { + type: types.SET_DIFF_FILE_VIEWED, + payload: { id: 'z', seen: true }, + }, + { + type: types.SET_DIFF_FILE_VIEWED, + payload: { id: 'a', seen: true }, + }, + { + type: types.SET_DIFF_FILE_VIEWED, + payload: { id: 'y', seen: true }, + }, ], [], done, @@ -190,10 +208,10 @@ describe('DiffsStoreActions', () => { { type: types.SET_RETRIEVING_BATCHES, payload: true }, { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } }, { type: types.SET_BATCH_LOADING_STATE, payload: 'loaded' }, - { type: types.VIEW_DIFF_FILE, payload: 'test' }, + { type: types.SET_CURRENT_DIFF_FILE, payload: 'test' }, { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res2.diff_files } }, { type: types.SET_BATCH_LOADING_STATE, payload: 'loaded' }, - { type: types.VIEW_DIFF_FILE, payload: 'test2' }, + { type: types.SET_CURRENT_DIFF_FILE, payload: 'test2' }, { type: types.SET_RETRIEVING_BATCHES, payload: false }, { type: types.SET_BATCH_LOADING_STATE, payload: 'error' }, ], @@ -307,7 +325,7 @@ describe('DiffsStoreActions', () => { it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => { testAction(setHighlightedRow, 'ABC_123', {}, [ { type: types.SET_HIGHLIGHTED_ROW, payload: 'ABC_123' }, - { type: types.VIEW_DIFF_FILE, payload: 'ABC' }, + { type: types.SET_CURRENT_DIFF_FILE, payload: 'ABC' }, ]); }); }); @@ -895,7 +913,7 @@ describe('DiffsStoreActions', () => { expect(document.location.hash).toBe('#test'); }); - it('commits VIEW_DIFF_FILE', () => { + it('commits SET_CURRENT_DIFF_FILE', () => { const state = { treeEntries: { path: { @@ -906,7 +924,7 @@ describe('DiffsStoreActions', () => { scrollToFile({ state, commit, getters }, { path: 'path' }); - expect(commit).toHaveBeenCalledWith(types.VIEW_DIFF_FILE, 'test'); + expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, 'test'); }); }); @@ -1428,7 +1446,7 @@ describe('DiffsStoreActions', () => { }); describe('setCurrentDiffFileIdFromNote', () => { - it('commits VIEW_DIFF_FILE', () => { + it('commits SET_CURRENT_DIFF_FILE', () => { const commit = jest.fn(); const state = { diffFiles: [{ file_hash: '123' }] }; const rootGetters = { @@ -1438,10 +1456,10 @@ describe('DiffsStoreActions', () => { setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1'); - expect(commit).toHaveBeenCalledWith(types.VIEW_DIFF_FILE, '123'); + expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, '123'); }); - it('does not commit VIEW_DIFF_FILE when discussion has no diff_file', () => { + it('does not commit SET_CURRENT_DIFF_FILE when discussion has no diff_file', () => { const commit = jest.fn(); const state = { diffFiles: [{ file_hash: '123' }] }; const rootGetters = { @@ -1454,7 +1472,7 @@ describe('DiffsStoreActions', () => { expect(commit).not.toHaveBeenCalled(); }); - it('does not commit VIEW_DIFF_FILE when diff file does not exist', () => { + it('does not commit SET_CURRENT_DIFF_FILE when diff file does not exist', () => { const commit = jest.fn(); const state = { diffFiles: [{ file_hash: '123' }] }; const rootGetters = { @@ -1469,12 +1487,12 @@ describe('DiffsStoreActions', () => { }); describe('navigateToDiffFileIndex', () => { - it('commits VIEW_DIFF_FILE', (done) => { + it('commits SET_CURRENT_DIFF_FILE', (done) => { testAction( navigateToDiffFileIndex, 0, { diffFiles: [{ file_hash: '123' }] }, - [{ type: types.VIEW_DIFF_FILE, payload: '123' }], + [{ type: types.SET_CURRENT_DIFF_FILE, payload: '123' }], [], done, ); @@ -1523,13 +1541,14 @@ describe('DiffsStoreActions', () => { describe('reviewFile', () => { const file = { id: '123', + file_hash: 'xyz', file_identifier_hash: 'abc', load_collapsed_diff_url: 'gitlab-org/gitlab-test/-/merge_requests/1/diffs', }; it.each` - reviews | diffFile | reviewed - ${{ abc: ['123'] }} | ${file} | ${true} - ${{}} | ${file} | ${false} + reviews | diffFile | reviewed + ${{ abc: ['123', 'hash:xyz'] }} | ${file} | ${true} + ${{}} | ${file} | ${false} `( 'sets reviews ($reviews) to localStorage and state for file $file if it is marked reviewed=$reviewed', ({ reviews, diffFile, reviewed }) => { diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js index fc9ba223d5a..c104fcd5fb9 100644 --- a/spec/frontend/diffs/store/mutations_spec.js +++ b/spec/frontend/diffs/store/mutations_spec.js @@ -633,16 +633,36 @@ describe('DiffsStoreMutations', () => { }); }); - describe('VIEW_DIFF_FILE', () => { + describe('SET_CURRENT_DIFF_FILE', () => { it('updates currentDiffFileId', () => { const state = createState(); - mutations[types.VIEW_DIFF_FILE](state, 'somefileid'); + mutations[types.SET_CURRENT_DIFF_FILE](state, 'somefileid'); expect(state.currentDiffFileId).toBe('somefileid'); }); }); + describe('SET_DIFF_FILE_VIEWED', () => { + let state; + + beforeEach(() => { + state = { + viewedDiffFileIds: { 123: true }, + }; + }); + + it.each` + id | bool | outcome + ${'abc'} | ${true} | ${{ 123: true, abc: true }} + ${'123'} | ${false} | ${{ 123: false }} + `('sets the viewed files list to $bool for the id $id', ({ id, bool, outcome }) => { + mutations[types.SET_DIFF_FILE_VIEWED](state, { id, seen: bool }); + + expect(state.viewedDiffFileIds).toEqual(outcome); + }); + }); + describe('Set highlighted row', () => { it('sets highlighted row', () => { const state = createState(); diff --git a/spec/frontend/diffs/utils/file_reviews_spec.js b/spec/frontend/diffs/utils/file_reviews_spec.js index 230ec12409c..ccd27a5ae3e 100644 --- a/spec/frontend/diffs/utils/file_reviews_spec.js +++ b/spec/frontend/diffs/utils/file_reviews_spec.js @@ -11,14 +11,14 @@ import { function getDefaultReviews() { return { - abc: ['123', '098'], + abc: ['123', 'hash:xyz', '098', 'hash:uvw'], }; } describe('File Review(s) utilities', () => { const mrPath = 'my/fake/mr/42'; const storageKey = `${mrPath}-file-reviews`; - const file = { id: '123', file_identifier_hash: 'abc' }; + const file = { id: '123', file_hash: 'xyz', file_identifier_hash: 'abc' }; const storedValue = JSON.stringify(getDefaultReviews()); let reviews; @@ -44,14 +44,14 @@ describe('File Review(s) utilities', () => { }); describe('reviewStatuses', () => { - const file1 = { id: '123', file_identifier_hash: 'abc' }; - const file2 = { id: '098', file_identifier_hash: 'abc' }; + const file1 = { id: '123', hash: 'xyz', file_identifier_hash: 'abc' }; + const file2 = { id: '098', hash: 'uvw', file_identifier_hash: 'abc' }; it.each` mrReviews | files | fileReviews ${{}} | ${[file1, file2]} | ${{ 123: false, '098': false }} - ${{ abc: ['123'] }} | ${[file1, file2]} | ${{ 123: true, '098': false }} - ${{ abc: ['098'] }} | ${[file1, file2]} | ${{ 123: false, '098': true }} + ${{ abc: ['123', 'hash:xyz'] }} | ${[file1, file2]} | ${{ 123: true, '098': false }} + ${{ abc: ['098', 'hash:uvw'] }} | ${[file1, file2]} | ${{ 123: false, '098': true }} ${{ def: ['123'] }} | ${[file1, file2]} | ${{ 123: false, '098': false }} ${{ abc: ['123'], def: ['098'] }} | ${[]} | ${{}} `( @@ -128,7 +128,7 @@ describe('File Review(s) utilities', () => { describe('markFileReview', () => { it("adds a review when there's nothing that already exists", () => { - expect(markFileReview(null, file)).toStrictEqual({ abc: ['123'] }); + expect(markFileReview(null, file)).toStrictEqual({ abc: ['123', 'hash:xyz'] }); }); it("overwrites an existing review if it's for the same file (identifier hash)", () => { @@ -136,15 +136,15 @@ describe('File Review(s) utilities', () => { }); it('removes a review from the list when `reviewed` is `false`', () => { - expect(markFileReview(reviews, file, false)).toStrictEqual({ abc: ['098'] }); + expect(markFileReview(reviews, file, false)).toStrictEqual({ abc: ['098', 'hash:uvw'] }); }); it('adds a new review if the file ID is new', () => { - const updatedFile = { ...file, id: '098' }; - const allReviews = markFileReview({ abc: ['123'] }, updatedFile); + const updatedFile = { ...file, id: '098', file_hash: 'uvw' }; + const allReviews = markFileReview({ abc: ['123', 'hash:xyz'] }, updatedFile); expect(allReviews).toStrictEqual(getDefaultReviews()); - expect(allReviews.abc).toStrictEqual(['123', '098']); + expect(allReviews.abc).toStrictEqual(['123', 'hash:xyz', '098', 'hash:uvw']); }); it.each` @@ -158,7 +158,7 @@ describe('File Review(s) utilities', () => { it('removes the file key if there are no more reviews for it', () => { let updated = markFileReview(reviews, file, false); - updated = markFileReview(updated, { ...file, id: '098' }, false); + updated = markFileReview(updated, { ...file, id: '098', file_hash: 'uvw' }, false); expect(updated).toStrictEqual({}); }); diff --git a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb new file mode 100644 index 00000000000..b9256ece78b --- /dev/null +++ b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml' do + subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Jobs/SAST-IaC.latest') } + + describe 'the created pipeline' do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { project.owner } + + let(:default_branch) { 'main' } + let(:pipeline_ref) { default_branch } + let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) } + let(:pipeline) { service.execute!(:push).payload } + let(:build_names) { pipeline.builds.pluck(:name) } + + before do + stub_ci_pipeline_yaml_file(template.content) + allow_next_instance_of(Ci::BuildScheduleWorker) do |instance| + allow(instance).to receive(:perform).and_return(true) + end + allow(project).to receive(:default_branch).and_return(default_branch) + end + + context 'on feature branch' do + let(:pipeline_ref) { 'feature' } + + it 'creates the kics-iac-sast job' do + expect(build_names).to contain_exactly('kics-iac-sast') + end + end + + context 'on merge request' do + let(:service) { MergeRequests::CreatePipelineService.new(project: project, current_user: user) } + let(:merge_request) { create(:merge_request, :simple, source_project: project) } + let(:pipeline) { service.execute(merge_request).payload } + + it 'has no jobs' do + expect(pipeline).to be_merge_request_event + expect(build_names).to be_empty + end + end + + context 'SAST_DISABLED is set' do + before do + create(:ci_variable, key: 'SAST_DISABLED', value: 'true', project: project) + end + + context 'on default branch' do + it 'has no jobs' do + expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) + end + end + + context 'on feature branch' do + let(:pipeline_ref) { 'feature' } + + it 'has no jobs' do + expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) + end + end + end + end +end |