Commit 72bcb95d authored by Frederic Caplette's avatar Frederic Caplette

Remove visualization tab on CI configs

This experiment is now over and we are preparing to move this
CI config visualization to the pipeline authoring home page.
Removing all of this will allow us to more easily port it over.
parent a10b9c9a
......@@ -6,30 +6,6 @@ import GpgBadges from '~/gpg_badges';
import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
import '~/sourcegraph/load';
import PipelineTourSuccessModal from '~/blob/pipeline_tour_success_modal.vue';
import { parseBoolean } from '~/lib/utils/common_utils';
const createGitlabCiYmlVisualization = (containerId = '#js-blob-toggle-graph-preview') => {
const el = document.querySelector(containerId);
const { isCiConfigFile, blobData } = el?.dataset;
if (el && parseBoolean(isCiConfigFile)) {
// eslint-disable-next-line no-new
new Vue({
el,
components: {
GitlabCiYamlVisualization: () =>
import('~/pipelines/components/pipeline_graph/gitlab_ci_yaml_visualization.vue'),
},
render(createElement) {
return createElement('gitlabCiYamlVisualization', {
props: {
blobData,
},
});
},
});
}
};
document.addEventListener('DOMContentLoaded', () => {
new BlobViewer(); // eslint-disable-line no-new
......@@ -88,8 +64,4 @@ document.addEventListener('DOMContentLoaded', () => {
},
});
}
if (gon?.features?.gitlabCiYmlPreview) {
createGitlabCiYmlVisualization();
}
});
<script>
import { GlTab, GlTabs } from '@gitlab/ui';
import jsYaml from 'js-yaml';
import PipelineGraph from './pipeline_graph.vue';
import { preparePipelineGraphData } from '../../utils';
export default {
FILE_CONTENT_SELECTOR: '#blob-content',
EMPTY_FILE_SELECTOR: '.nothing-here-block',
components: {
GlTab,
GlTabs,
PipelineGraph,
},
props: {
blobData: {
required: true,
type: String,
},
},
data() {
return {
selectedTabIndex: 0,
pipelineData: {},
};
},
computed: {
isVisualizationTab() {
return this.selectedTabIndex === 1;
},
},
async created() {
if (this.blobData) {
// The blobData in this case represents the gitlab-ci.yml data
const json = await jsYaml.load(this.blobData);
this.pipelineData = preparePipelineGraphData(json);
}
},
methods: {
// This is used because the blob page still uses haml, and we can't make
// our haml hide the unused section so we resort to a standard query here.
toggleFileContent({ isFileTab }) {
const el = document.querySelector(this.$options.FILE_CONTENT_SELECTOR);
const emptySection = document.querySelector(this.$options.EMPTY_FILE_SELECTOR);
const elementToHide = el || emptySection;
if (!elementToHide) {
return;
}
// Checking for the current style display prevents user
// from toggling visiblity on and off when clicking on the tab
if (!isFileTab && elementToHide.style.display !== 'none') {
elementToHide.style.display = 'none';
}
if (isFileTab && elementToHide.style.display === 'none') {
elementToHide.style.display = 'block';
}
},
},
};
</script>
<template>
<div>
<div>
<gl-tabs v-model="selectedTabIndex">
<gl-tab :title="__('File')" @click="toggleFileContent({ isFileTab: true })" />
<gl-tab :title="__('Visualization')" @click="toggleFileContent({ isFileTab: false })" />
</gl-tabs>
</div>
<pipeline-graph v-if="isVisualizationTab" :pipeline-data="pipelineData" />
</div>
</template>
......@@ -7,63 +7,6 @@ export const validateParams = params => {
export const createUniqueJobId = (stageName, jobName) => `${stageName}-${jobName}`;
/**
* This function takes a json payload that comes from a yml
* file converted to json through `jsyaml` library. Because we
* naively convert the entire yaml to json, some keys (like `includes`)
* are irrelevant to rendering the graph and must be removed. We also
* restructure the data to have the structure from an API response for the
* pipeline data.
* @param {Object} jsonData
* @returns {Array} - Array of stages containing all jobs
*/
export const preparePipelineGraphData = jsonData => {
const jsonKeys = Object.keys(jsonData);
const jobNames = jsonKeys.filter(job => jsonData[job]?.stage);
// Creates an object with only the valid jobs
const jobs = jsonKeys.reduce((acc, val) => {
if (jobNames.includes(val)) {
return {
...acc,
[val]: { ...jsonData[val], id: createUniqueJobId(jsonData[val].stage, val) },
};
}
return { ...acc };
}, {});
// We merge both the stages from the "stages" key in the yaml and the stage associated
// with each job to show the user both the stages they explicitly defined, and those
// that they added under jobs. We also remove duplicates.
const jobStages = jobNames.map(job => jsonData[job].stage);
const userDefinedStages = jsonData?.stages ?? [];
// The order is important here. We always show the stages in order they were
// defined in the `stages` key first, and then stages that are under the jobs.
const stages = Array.from(new Set([...userDefinedStages, ...jobStages]));
const arrayOfJobsByStage = stages.map(val => {
return jobNames.filter(job => {
return jsonData[job].stage === val;
});
});
const pipelineData = stages.map((stage, index) => {
const stageJobs = arrayOfJobsByStage[index];
return {
name: stage,
groups: stageJobs.map(job => {
return {
name: job,
jobs: [{ ...jsonData[job] }],
id: createUniqueJobId(stage, job),
};
}),
};
});
return { stages: pipelineData, jobs };
};
export const generateJobNeedsDict = ({ jobs }) => {
const arrOfJobNames = Object.keys(jobs);
......
......@@ -32,10 +32,6 @@ class Projects::BlobController < Projects::ApplicationController
before_action :validate_diff_params, only: :diff
before_action :set_last_commit_sha, only: [:edit, :update]
before_action only: :show do
push_frontend_feature_flag(:gitlab_ci_yml_preview, @project, default_enabled: false)
end
track_redis_hll_event :create, :update, name: 'g_edit_by_sfe', feature: :track_editor_edit_actions, feature_default_enabled: true
feature_category :source_code_management
......
- simple_viewer = blob.simple_viewer
- rich_viewer = blob.rich_viewer
- rich_viewer_active = rich_viewer && params[:viewer] != 'simple'
- blob_data = defined?(@blob) ? @blob.data : {}
- is_ci_config_file = defined?(@blob) && defined?(@project) ? editing_ci_config?.to_s : 'false'
#js-blob-toggle-graph-preview{ data: { blob_data: blob_data, is_ci_config_file: is_ci_config_file } }
= render 'projects/blob/viewer', viewer: simple_viewer, hidden: rich_viewer_active
......
---
name: gitlab_ci_yml_preview
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40880
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/244905
milestone: '13.4'
type: development
group: group::ci
default_enabled: false
......@@ -30112,9 +30112,6 @@ msgstr ""
msgid "VisualReviewApp|Steps 1 and 2 (and sometimes 3) are performed once by the developer before requesting feedback. Steps 3 (if necessary), 4 is performed by the reviewer each time they perform a review."
msgstr ""
msgid "Visualization"
msgstr ""
msgid "Vulnerabilities"
msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import { GlTab } from '@gitlab/ui';
import { yamlString } from './mock_data';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import GitlabCiYamlVisualization from '~/pipelines/components/pipeline_graph/gitlab_ci_yaml_visualization.vue';
describe('gitlab yaml visualization component', () => {
const defaultProps = { blobData: yamlString };
let wrapper;
const createComponent = props => {
return shallowMount(GitlabCiYamlVisualization, {
propsData: {
...defaultProps,
...props,
},
});
};
const findGlTabComponents = () => wrapper.findAll(GlTab);
const findPipelineGraph = () => wrapper.find(PipelineGraph);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('tabs component', () => {
beforeEach(() => {
wrapper = createComponent();
});
it('renders the file and visualization tabs', () => {
expect(findGlTabComponents()).toHaveLength(2);
});
});
describe('graph component', () => {
beforeEach(() => {
wrapper = createComponent();
});
it('is hidden by default', () => {
expect(findPipelineGraph().exists()).toBe(false);
});
});
});
import {
preparePipelineGraphData,
createUniqueJobId,
generateJobNeedsDict,
} from '~/pipelines/utils';
import { createUniqueJobId, generateJobNeedsDict } from '~/pipelines/utils';
describe('utils functions', () => {
const emptyResponse = { stages: [], jobs: {} };
const jobName1 = 'build_1';
const jobName2 = 'build_2';
const jobName3 = 'test_1';
......@@ -66,115 +61,6 @@ describe('utils functions', () => {
},
};
describe('preparePipelineGraphData', () => {
describe('returns an empty array of stages and empty job objects if', () => {
it('no data is passed', () => {
expect(preparePipelineGraphData({})).toEqual(emptyResponse);
});
it('no stages are found', () => {
expect(preparePipelineGraphData({ includes: 'template/myTemplate.gitlab-ci.yml' })).toEqual(
emptyResponse,
);
});
});
describe('returns the correct array of stages and object of jobs', () => {
it('when multiple jobs are in the same stage', () => {
const expectedData = {
stages: [
{
name: job1.stage,
groups: [
{
name: jobName1,
jobs: [{ ...job1 }],
id: createUniqueJobId(job1.stage, jobName1),
},
{
name: jobName2,
jobs: [{ ...job2 }],
id: createUniqueJobId(job2.stage, jobName2),
},
],
},
],
jobs: {
[jobName1]: { ...job1, id: createUniqueJobId(job1.stage, jobName1) },
[jobName2]: { ...job2, id: createUniqueJobId(job2.stage, jobName2) },
},
};
expect(
preparePipelineGraphData({ [jobName1]: { ...job1 }, [jobName2]: { ...job2 } }),
).toEqual(expectedData);
});
it('when stages are defined by the user', () => {
const userDefinedStage2 = 'myStage2';
const expectedData = {
stages: [
{
name: userDefinedStage,
groups: [],
},
{
name: userDefinedStage2,
groups: [],
},
],
jobs: {},
};
expect(preparePipelineGraphData({ stages: [userDefinedStage, userDefinedStage2] })).toEqual(
expectedData,
);
});
it('by combining user defined stage and job stages, it preserves user defined order', () => {
const userDefinedStageThatOverlaps = 'deploy';
expect(
preparePipelineGraphData({
stages: [userDefinedStage, userDefinedStageThatOverlaps],
[jobName1]: { ...job1 },
[jobName2]: { ...job2 },
[jobName3]: { ...job3 },
[jobName4]: { ...job4 },
}),
).toEqual(pipelineGraphData);
});
it('with only unique values', () => {
const expectedData = {
stages: [
{
name: job1.stage,
groups: [
{
name: jobName1,
jobs: [{ ...job1 }],
id: createUniqueJobId(job1.stage, jobName1),
},
],
},
],
jobs: {
[jobName1]: { ...job1, id: createUniqueJobId(job1.stage, jobName1) },
},
};
expect(
preparePipelineGraphData({
stages: ['build'],
[jobName1]: { ...job1 },
[jobName1]: { ...job1 },
}),
).toEqual(expectedData);
});
});
});
describe('generateJobNeedsDict', () => {
it('generates an empty object if it receives no jobs', () => {
expect(generateJobNeedsDict({ jobs: {} })).toEqual({});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment