Commit 87b05b06 authored by Frédéric Caplette's avatar Frédéric Caplette Committed by Sarah Groff Hennigh-Palermo

Update Pipeline editor getBlobContent to use graphQL

parent 7705f554
query getBlobContent($projectPath: ID!, $path: String, $ref: String!) {
blobContent(projectPath: $projectPath, path: $path, ref: $ref) @client {
rawData
query getBlobContent($projectPath: ID!, $path: String!, $ref: String) {
project(fullPath: $projectPath) {
repository {
blobs(paths: [$path], ref: $ref) {
nodes {
rawBlob
}
}
}
}
}
import produce from 'immer';
import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
import getCurrentBranchQuery from './queries/client/current_branch.graphql';
import getLastCommitBranchQuery from './queries/client/last_commit_branch.query.graphql';
export const resolvers = {
Query: {
blobContent(_, { projectPath, path, ref }) {
return {
__typename: 'BlobContent',
rawData: Api.getRawFile(projectPath, path, { ref }).then(({ data }) => {
return data;
}),
};
},
},
Mutation: {
lintCI: (_, { endpoint, content, dry_run }) => {
return axios.post(endpoint, { content, dry_run }).then(({ data }) => ({
......
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { fetchPolicies } from '~/lib/graphql';
import httpStatusCodes from '~/lib/utils/http_status';
import { s__ } from '~/locale';
import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils';
......@@ -76,22 +75,40 @@ export default {
};
},
update(data) {
return data?.blobContent?.rawData;
return data?.project?.repository?.blobs?.nodes[0]?.rawBlob;
},
result({ data }) {
const fileContent = data?.blobContent?.rawData ?? '';
const nodes = data?.project?.repository?.blobs?.nodes;
if (!nodes) {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
} else {
const rawBlob = nodes[0]?.rawBlob;
const fileContent = rawBlob ?? '';
this.lastCommittedContent = fileContent;
this.currentCiFileContent = fileContent;
// make sure to reset the start screen flag during a refetch
// If rawBlob is defined and returns a string, it means that there is
// a CI config file with empty content. If `rawBlob` is not defined
// at all, it means there was no file found.
const hasCIFile = rawBlob === '' || fileContent.length > 0;
if (!fileContent.length) {
this.setAppStatus(EDITOR_APP_STATUS_EMPTY);
}
if (!hasCIFile) {
this.showStartScreen = true;
} else if (fileContent.length) {
// If the file content is > 0, then we make sure to reset the
// start screen flag during a refetch
// e.g. when switching branches
if (fileContent.length) {
this.showStartScreen = false;
}
}
},
error(error) {
this.handleBlobContentError(error);
error() {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
},
watchLoading(isLoading) {
if (isLoading) {
......@@ -187,22 +204,6 @@ export default {
},
},
methods: {
handleBlobContentError(error = {}) {
const { networkError } = error;
const { response } = networkError;
// 404 for missing CI file
// 400 for blank projects with no repository
if (
response?.status === httpStatusCodes.NOT_FOUND ||
response?.status === httpStatusCodes.BAD_REQUEST
) {
this.setAppStatus(EDITOR_APP_STATUS_EMPTY);
this.showStartScreen = true;
} else {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
}
},
hideFailure() {
this.showFailure = false;
},
......
import MockAdapter from 'axios-mock-adapter';
import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
import { resolvers } from '~/pipeline_editor/graphql/resolvers';
import {
mockCiConfigPath,
mockCiYml,
mockDefaultBranch,
mockLintResponse,
mockProjectFullPath,
} from '../mock_data';
import { mockLintResponse } from '../mock_data';
jest.mock('~/api', () => {
return {
......@@ -18,36 +11,6 @@ jest.mock('~/api', () => {
});
describe('~/pipeline_editor/graphql/resolvers', () => {
describe('Query', () => {
describe('blobContent', () => {
beforeEach(() => {
Api.getRawFile.mockResolvedValue({
data: mockCiYml,
});
});
afterEach(() => {
Api.getRawFile.mockReset();
});
it('resolves lint data with type names', async () => {
const result = resolvers.Query.blobContent(null, {
projectPath: mockProjectFullPath,
path: mockCiConfigPath,
ref: mockDefaultBranch,
});
expect(Api.getRawFile).toHaveBeenCalledWith(mockProjectFullPath, mockCiConfigPath, {
ref: mockDefaultBranch,
});
// eslint-disable-next-line no-underscore-dangle
expect(result.__typename).toBe('BlobContent');
await expect(result.rawData).resolves.toBe(mockCiYml);
});
});
});
describe('Mutation', () => {
describe('lintCI', () => {
let mock;
......
......@@ -35,6 +35,23 @@ job_build:
- echo "build"
needs: ["job_test_2"]
`;
export const mockBlobContentQueryResponse = {
data: {
project: { repository: { blobs: { nodes: [{ rawBlob: mockCiYml }] } } },
},
};
export const mockBlobContentQueryResponseNoCiFile = {
data: {
project: { repository: { blobs: { nodes: [] } } },
},
};
export const mockBlobContentQueryResponseEmptyCiFile = {
data: {
project: { repository: { blobs: { nodes: [{ rawBlob: '' }] } } },
},
};
const mockJobFields = {
beforeScript: [],
......
......@@ -3,7 +3,6 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import httpStatusCodes from '~/lib/utils/http_status';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import TextEditor from '~/pipeline_editor/components/editor/text_editor.vue';
......@@ -11,15 +10,19 @@ import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tab
import PipelineEditorEmptyState from '~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue';
import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_editor_messages.vue';
import { COMMIT_SUCCESS, COMMIT_FAILURE } from '~/pipeline_editor/constants';
import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.graphql';
import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue';
import {
mockCiConfigPath,
mockCiConfigQueryResponse,
mockCiYml,
mockBlobContentQueryResponse,
mockBlobContentQueryResponseEmptyCiFile,
mockBlobContentQueryResponseNoCiFile,
mockDefaultBranch,
mockProjectFullPath,
mockCiYml,
} from './mock_data';
const localVue = createLocalVue();
......@@ -75,19 +78,12 @@ describe('Pipeline editor app component', () => {
};
const createComponentWithApollo = async ({ props = {}, provide = {} } = {}) => {
const handlers = [[getCiConfigData, mockCiConfigData]];
const resolvers = {
Query: {
blobContent() {
return {
__typename: 'BlobContent',
rawData: mockBlobContentData(),
};
},
},
};
const handlers = [
[getBlobContent, mockBlobContentData],
[getCiConfigData, mockCiConfigData],
];
mockApollo = createMockApollo(handlers, resolvers);
mockApollo = createMockApollo(handlers);
const options = {
localVue,
......@@ -133,7 +129,7 @@ describe('Pipeline editor app component', () => {
describe('when queries are called', () => {
beforeEach(() => {
mockBlobContentData.mockResolvedValue(mockCiYml);
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse);
});
......@@ -159,35 +155,14 @@ describe('Pipeline editor app component', () => {
});
describe('when no CI config file exists', () => {
describe('in a project without a repository', () => {
it('shows an empty state and does not show editor home component', async () => {
mockBlobContentData.mockRejectedValueOnce({
response: {
status: httpStatusCodes.BAD_REQUEST,
},
});
await createComponentWithApollo();
expect(findEmptyState().exists()).toBe(true);
expect(findAlert().exists()).toBe(false);
expect(findEditorHome().exists()).toBe(false);
});
});
describe('in a project with a repository', () => {
it('shows an empty state and does not show editor home component', async () => {
mockBlobContentData.mockRejectedValueOnce({
response: {
status: httpStatusCodes.NOT_FOUND,
},
});
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseNoCiFile);
await createComponentWithApollo();
expect(findEmptyState().exists()).toBe(true);
expect(findAlert().exists()).toBe(false);
expect(findEditorHome().exists()).toBe(false);
});
});
describe('because of a fetching error', () => {
it('shows a unkown error message', async () => {
......@@ -204,14 +179,29 @@ describe('Pipeline editor app component', () => {
});
});
describe('when landing on the empty state with feature flag on', () => {
it('user can click on CTA button and see an empty editor', async () => {
mockBlobContentData.mockRejectedValueOnce({
response: {
status: httpStatusCodes.NOT_FOUND,
describe('with an empty CI config file', () => {
describe('with empty state feature flag on', () => {
it('does not show the empty screen state', async () => {
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseEmptyCiFile);
await createComponentWithApollo({
provide: {
glFeatures: {
pipelineEditorEmptyStateAction: true,
},
},
});
expect(findEmptyState().exists()).toBe(false);
expect(findTextEditor().exists()).toBe(true);
});
});
});
describe('when landing on the empty state with feature flag on', () => {
it('user can click on CTA button and see an empty editor', async () => {
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseNoCiFile);
await createComponentWithApollo({
provide: {
glFeatures: {
......@@ -315,17 +305,13 @@ describe('Pipeline editor app component', () => {
});
it('hides start screen when refetch fetches CI file', async () => {
mockBlobContentData.mockRejectedValue({
response: {
status: httpStatusCodes.NOT_FOUND,
},
});
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseNoCiFile);
await createComponentWithApollo();
expect(findEmptyState().exists()).toBe(true);
expect(findEditorHome().exists()).toBe(false);
mockBlobContentData.mockResolvedValue(mockCiYml);
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
await wrapper.vm.$apollo.queries.initialCiFileContent.refetch();
expect(findEmptyState().exists()).toBe(false);
......
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