mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* [Code] makes Code space aware. For APIs related to repository, file, and search, user should only see contents from repositories belongs to his/her space. When the same repository is added in different spaces, they should point to the same underlying repository. We use references from spaces to repositories to represent this relationship. This patch implements the 1:1 model, which means a repository can only be imported in on space.
This commit is contained in:
parent
f448c41163
commit
763098d809
22 changed files with 373 additions and 77 deletions
|
@ -6,3 +6,4 @@
|
|||
|
||||
export const APP_TITLE = 'Code (Beta)';
|
||||
export const APP_USAGE_TYPE = 'code';
|
||||
export const SAVED_OBJ_REPO = 'code-repo';
|
||||
|
|
|
@ -10,9 +10,10 @@ import { Legacy } from 'kibana';
|
|||
import { resolve } from 'path';
|
||||
import { CoreSetup } from 'src/core/server';
|
||||
|
||||
import { APP_TITLE } from './common/constants';
|
||||
import { APP_TITLE, SAVED_OBJ_REPO } from './common/constants';
|
||||
import { codePlugin } from './server';
|
||||
import { PluginSetupContract } from '../../../plugins/code/server';
|
||||
import { mappings } from './mappings';
|
||||
|
||||
export type RequestFacade = Legacy.Request;
|
||||
export type RequestQueryFacade = RequestQuery;
|
||||
|
@ -43,6 +44,12 @@ export const code = (kibana: any) =>
|
|||
};
|
||||
},
|
||||
hacks: ['plugins/code/hacks/toggle_app_link_in_nav'],
|
||||
savedObjectSchemas: {
|
||||
[SAVED_OBJ_REPO]: {
|
||||
isNamespaceAgnostic: false,
|
||||
},
|
||||
},
|
||||
mappings,
|
||||
},
|
||||
config(Joi: typeof JoiNamespace) {
|
||||
return Joi.object({
|
||||
|
|
17
x-pack/legacy/plugins/code/mappings.ts
Normal file
17
x-pack/legacy/plugins/code/mappings.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SAVED_OBJ_REPO } from './common/constants';
|
||||
|
||||
export const mappings = {
|
||||
[SAVED_OBJ_REPO]: {
|
||||
properties: {
|
||||
uri: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -231,3 +231,64 @@ export interface SearchOptions {
|
|||
defaultRepoScopeOn: boolean;
|
||||
defaultRepoScope?: Repository;
|
||||
}
|
||||
|
||||
export function emptySearchResult(): SearchResult {
|
||||
return {
|
||||
total: 0,
|
||||
took: 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function emptyRepositorySearchResult(): RepositorySearchResult {
|
||||
return {
|
||||
...emptySearchResult(),
|
||||
repositories: [],
|
||||
from: 0,
|
||||
page: 0,
|
||||
totalPage: 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function emptySymbolSearchResult(): SymbolSearchResult {
|
||||
return {
|
||||
...emptySearchResult(),
|
||||
symbols: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function emptyDocumentSearchResult(query: string): DocumentSearchResult {
|
||||
return {
|
||||
...emptySearchResult(),
|
||||
query,
|
||||
from: 0,
|
||||
page: 0,
|
||||
totalPage: 0,
|
||||
stats: {
|
||||
total: 0,
|
||||
from: 0,
|
||||
to: 0,
|
||||
page: 0,
|
||||
totalPage: 0,
|
||||
repoStats: [],
|
||||
languageStats: [],
|
||||
},
|
||||
results: [],
|
||||
repoAggregations: [],
|
||||
langAggregations: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function emptyCommitSearchResult(query: string): CommitSearchResult {
|
||||
return {
|
||||
...emptyDocumentSearchResult(query),
|
||||
commits: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function emptyIntegrationsSearchResult(): IntegrationsSearchResult {
|
||||
return {
|
||||
...emptySearchResult(),
|
||||
results: [],
|
||||
fallback: false,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { EsClientWithRequest } from '../utils/esclient_with_request';
|
|||
import { decodeRevisionString } from '../../common/uri_util';
|
||||
import { CodeServices } from '../distributed/code_services';
|
||||
import { GitServiceDefinition } from '../distributed/apis';
|
||||
import { getReferenceHelper } from '../utils/repository_reference_helper';
|
||||
|
||||
export function fileRoute(router: CodeServerRouter, codeServices: CodeServices) {
|
||||
const gitService = codeServices.serviceFor(GitServiceDefinition);
|
||||
|
@ -26,6 +27,7 @@ export function fileRoute(router: CodeServerRouter, codeServices: CodeServices)
|
|||
|
||||
try {
|
||||
const repo = await repoObjectClient.getRepository(repoUri);
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repo.uri);
|
||||
return repo.uri;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
|
|
|
@ -26,6 +26,8 @@ import { RequestFacade, ResponseToolkitFacade } from '../..';
|
|||
import { CodeServices } from '../distributed/code_services';
|
||||
import { GitServiceDefinition, LspServiceDefinition } from '../distributed/apis';
|
||||
import { findTitleFromHover, groupFiles } from '../utils/lsp_utils';
|
||||
import { getReferenceHelper } from '../utils/repository_reference_helper';
|
||||
import { SymbolSearchResult } from '../../model';
|
||||
|
||||
const LANG_SERVER_ERROR = 'language server error';
|
||||
|
||||
|
@ -48,6 +50,7 @@ export function lspRoute(
|
|||
const params = (req.payload as unknown) as any;
|
||||
const uri = params.textDocument.uri;
|
||||
const { repoUri } = parseLspUrl(uri)!;
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri);
|
||||
const endpoint = await codeServices.locate(req, repoUri);
|
||||
const requestPromise = lspService.sendRequest(endpoint, {
|
||||
method: `textDocument/${method}`,
|
||||
|
@ -95,7 +98,9 @@ export function lspRoute(
|
|||
// @ts-ignore
|
||||
const { textDocument, position } = req.payload;
|
||||
const { uri } = textDocument;
|
||||
const endpoint = await codeServices.locate(req, parseLspUrl(uri).repoUri);
|
||||
const { repoUri } = parseLspUrl(uri);
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri);
|
||||
const endpoint = await codeServices.locate(req, repoUri);
|
||||
const response: ResponseMessage = await promiseTimeout(
|
||||
serverOptions.lsp.requestTimeoutMs,
|
||||
lspService.sendRequest(endpoint, {
|
||||
|
@ -115,11 +120,12 @@ export function lspRoute(
|
|||
|
||||
const locators = response.result as SymbolLocator[];
|
||||
const locations = [];
|
||||
const repoScope = await getReferenceHelper(req.getSavedObjectsClient()).findReferences();
|
||||
for (const locator of locators) {
|
||||
if (locator.location) {
|
||||
locations.push(locator.location);
|
||||
} else if (locator.qname) {
|
||||
const searchResults = await symbolSearchClient.findByQname(req.params.qname);
|
||||
} else if (locator.qname && repoScope.length > 0) {
|
||||
const searchResults = await symbolSearchClient.findByQname(req.params.qname, repoScope);
|
||||
for (const symbol of searchResults.symbols) {
|
||||
locations.push(symbol.symbolInformation.location);
|
||||
}
|
||||
|
@ -141,7 +147,9 @@ export function lspRoute(
|
|||
// @ts-ignore
|
||||
const { textDocument, position } = req.payload;
|
||||
const { uri } = textDocument;
|
||||
const endpoint = await codeServices.locate(req, parseLspUrl(uri).repoUri);
|
||||
const { repoUri } = parseLspUrl(uri);
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri);
|
||||
const endpoint = await codeServices.locate(req, repoUri);
|
||||
const response: ResponseMessage = await promiseTimeout(
|
||||
serverOptions.lsp.requestTimeoutMs,
|
||||
lspService.sendRequest(endpoint, {
|
||||
|
@ -189,7 +197,15 @@ export function symbolByQnameRoute(router: CodeServerRouter, log: Logger) {
|
|||
async handler(req: RequestFacade) {
|
||||
try {
|
||||
const symbolSearchClient = new SymbolSearchClient(new EsClientWithRequest(req), log);
|
||||
return await symbolSearchClient.findByQname(req.params.qname);
|
||||
const repoScope = await getReferenceHelper(req.getSavedObjectsClient()).findReferences();
|
||||
if (repoScope.length === 0) {
|
||||
return {
|
||||
symbols: [],
|
||||
total: 0,
|
||||
took: 0,
|
||||
} as SymbolSearchResult;
|
||||
}
|
||||
return await symbolSearchClient.findByQname(req.params.qname, repoScope);
|
||||
} catch (error) {
|
||||
return Boom.internal(`Search Exception`);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import Boom from 'boom';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { RequestFacade, ResponseToolkitFacade } from '../..';
|
||||
import { validateGitUrl } from '../../common/git_url_utils';
|
||||
import { RepositoryUtils } from '../../common/repository_utils';
|
||||
|
@ -19,6 +20,7 @@ import { EsClientWithRequest } from '../utils/esclient_with_request';
|
|||
import { CodeServerRouter } from '../security';
|
||||
import { CodeServices } from '../distributed/code_services';
|
||||
import { RepositoryServiceDefinition } from '../distributed/apis';
|
||||
import { getReferenceHelper } from '../utils/repository_reference_helper';
|
||||
|
||||
export function repositoryRoute(
|
||||
router: CodeServerRouter,
|
||||
|
@ -56,12 +58,32 @@ export function repositoryRoute(
|
|||
try {
|
||||
// Check if the repository already exists
|
||||
await repoObjectClient.getRepository(repo.uri);
|
||||
// distinguish between that the repository exists in the current space and that the repository exists in
|
||||
// another space, and return the default message if error happens during reference checking.
|
||||
try {
|
||||
const hasRef = await getReferenceHelper(req.getSavedObjectsClient()).hasReference(
|
||||
repo.uri
|
||||
);
|
||||
if (!hasRef) {
|
||||
return Boom.conflict(
|
||||
i18n.translate('xpack.code.repositoryManagement.repoOtherSpaceImportedMessage', {
|
||||
defaultMessage: 'The repository has already been imported in another space!',
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(`Failed to check reference for ${repo.uri} in current space`);
|
||||
}
|
||||
const msg = `Repository ${repoUrl} already exists. Skip clone.`;
|
||||
log.info(msg);
|
||||
return h.response(msg).code(304); // Not Modified
|
||||
} catch (error) {
|
||||
log.info(`Repository ${repoUrl} does not exist. Go ahead with clone.`);
|
||||
try {
|
||||
// create the reference first, and make the creation idempotent, to avoid potential dangling repositories
|
||||
// which have no references from any space, in case the writes to ES may fail independently
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).createReference(repo.uri);
|
||||
|
||||
// Create the index for the repository
|
||||
const initializer = (await repoIndexInitializerFactory.create(
|
||||
repo.uri,
|
||||
|
@ -106,6 +128,9 @@ export function repositoryRoute(
|
|||
const repoUri: string = req.params.uri as string;
|
||||
const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req));
|
||||
try {
|
||||
// make sure the repo belongs to the current space
|
||||
getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri);
|
||||
|
||||
// Check if the repository already exists. If not, an error will be thrown.
|
||||
await repoObjectClient.getRepository(repoUri);
|
||||
|
||||
|
@ -129,6 +154,9 @@ export function repositoryRoute(
|
|||
};
|
||||
const endpoint = await codeServices.locate(req, repoUri);
|
||||
await repositoryService.delete(endpoint, payload);
|
||||
|
||||
// delete the reference last to avoid dangling repositories
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).deleteReference(repoUri);
|
||||
return {};
|
||||
} catch (error) {
|
||||
const msg = `Issue repository delete request for ${repoUri} error`;
|
||||
|
@ -146,6 +174,7 @@ export function repositoryRoute(
|
|||
async handler(req: RequestFacade) {
|
||||
const repoUri = req.params.uri as string;
|
||||
try {
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri);
|
||||
const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req));
|
||||
return await repoObjectClient.getRepository(repoUri);
|
||||
} catch (error) {
|
||||
|
@ -164,25 +193,30 @@ export function repositoryRoute(
|
|||
const repoUri = req.params.uri as string;
|
||||
try {
|
||||
const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req));
|
||||
|
||||
let gitStatus = null;
|
||||
try {
|
||||
gitStatus = await repoObjectClient.getRepositoryGitStatus(repoUri);
|
||||
} catch (error) {
|
||||
log.debug(`Get repository git status ${repoUri} error: ${error}`);
|
||||
}
|
||||
|
||||
let indexStatus = null;
|
||||
try {
|
||||
indexStatus = await repoObjectClient.getRepositoryIndexStatus(repoUri);
|
||||
} catch (error) {
|
||||
log.debug(`Get repository index status ${repoUri} error: ${error}`);
|
||||
}
|
||||
|
||||
let deleteStatus = null;
|
||||
try {
|
||||
deleteStatus = await repoObjectClient.getRepositoryDeleteStatus(repoUri);
|
||||
} catch (error) {
|
||||
log.debug(`Get repository delete status ${repoUri} error: ${error}`);
|
||||
const hasRef = await getReferenceHelper(req.getSavedObjectsClient()).hasReference(repoUri);
|
||||
|
||||
if (hasRef) {
|
||||
try {
|
||||
gitStatus = await repoObjectClient.getRepositoryGitStatus(repoUri);
|
||||
} catch (error) {
|
||||
log.debug(`Get repository git status ${repoUri} error: ${error}`);
|
||||
}
|
||||
|
||||
try {
|
||||
indexStatus = await repoObjectClient.getRepositoryIndexStatus(repoUri);
|
||||
} catch (error) {
|
||||
log.debug(`Get repository index status ${repoUri} error: ${error}`);
|
||||
}
|
||||
|
||||
try {
|
||||
deleteStatus = await repoObjectClient.getRepositoryDeleteStatus(repoUri);
|
||||
} catch (error) {
|
||||
log.debug(`Get repository delete status ${repoUri} error: ${error}`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
gitStatus,
|
||||
|
@ -204,8 +238,9 @@ export function repositoryRoute(
|
|||
method: 'GET',
|
||||
async handler(req: RequestFacade) {
|
||||
try {
|
||||
const uris = await getReferenceHelper(req.getSavedObjectsClient()).findReferences();
|
||||
const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req));
|
||||
return await repoObjectClient.getAllRepositories();
|
||||
return await repoObjectClient.getRepositories(uris);
|
||||
} catch (error) {
|
||||
const msg = `Get all repositories error`;
|
||||
log.error(msg);
|
||||
|
@ -226,6 +261,7 @@ export function repositoryRoute(
|
|||
const repoUri = req.params.uri as string;
|
||||
const reindex: boolean = (req.payload as any).reindex;
|
||||
try {
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri);
|
||||
const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req));
|
||||
const cloneStatus = await repoObjectClient.getRepositoryGitStatus(repoUri);
|
||||
|
||||
|
@ -258,6 +294,7 @@ export function repositoryRoute(
|
|||
|
||||
try {
|
||||
// Check if the repository exists
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri);
|
||||
await repoObjectClient.getRepository(repoUri);
|
||||
} catch (error) {
|
||||
return Boom.badRequest(`Repository not existed for ${repoUri}`);
|
||||
|
@ -284,6 +321,7 @@ export function repositoryRoute(
|
|||
async handler(req: RequestFacade) {
|
||||
const repoUri = req.params.uri as string;
|
||||
try {
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri);
|
||||
const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req));
|
||||
return await repoObjectClient.getRepositoryConfig(repoUri);
|
||||
} catch (error) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from '../search';
|
||||
import { EsClientWithRequest } from '../utils/esclient_with_request';
|
||||
import { CodeServerRouter } from '../security';
|
||||
import { getReferenceHelper } from '../utils/repository_reference_helper';
|
||||
|
||||
export function repositorySearchRoute(router: CodeServerRouter, log: Logger) {
|
||||
router.route({
|
||||
|
@ -38,15 +39,10 @@ export function repositorySearchRoute(router: CodeServerRouter, log: Logger) {
|
|||
page = parseInt(p as string, 10);
|
||||
}
|
||||
|
||||
let scope: string[] = [];
|
||||
if (typeof repoScope === 'string') {
|
||||
scope = repoScope.split(',');
|
||||
}
|
||||
|
||||
const searchReq: RepositorySearchRequest = {
|
||||
query: q as string,
|
||||
page,
|
||||
repoScope: scope,
|
||||
repoScope: await getScope(req, repoScope),
|
||||
};
|
||||
try {
|
||||
const repoSearchClient = new RepositorySearchClient(new EsClientWithRequest(req), log);
|
||||
|
@ -68,15 +64,10 @@ export function repositorySearchRoute(router: CodeServerRouter, log: Logger) {
|
|||
page = parseInt(p as string, 10);
|
||||
}
|
||||
|
||||
let scope: string[] = [];
|
||||
if (typeof repoScope === 'string') {
|
||||
scope = repoScope.split(',');
|
||||
}
|
||||
|
||||
const searchReq: RepositorySearchRequest = {
|
||||
query: q as string,
|
||||
page,
|
||||
repoScope: scope,
|
||||
repoScope: await getScope(req, repoScope),
|
||||
};
|
||||
try {
|
||||
const repoSearchClient = new RepositorySearchClient(new EsClientWithRequest(req), log);
|
||||
|
@ -100,19 +91,12 @@ export function documentSearchRoute(router: CodeServerRouter, log: Logger) {
|
|||
page = parseInt(p as string, 10);
|
||||
}
|
||||
|
||||
let scope: string[] = [];
|
||||
if (typeof repoScope === 'string') {
|
||||
scope = [repoScope];
|
||||
} else if (Array.isArray(repoScope)) {
|
||||
scope = repoScope;
|
||||
}
|
||||
|
||||
const searchReq: DocumentSearchRequest = {
|
||||
query: q as string,
|
||||
page,
|
||||
langFilters: langs ? (langs as string).split(',') : [],
|
||||
repoFilters: repos ? decodeURIComponent(repos as string).split(',') : [],
|
||||
repoScope: scope,
|
||||
repoScope: await getScope(req, repoScope),
|
||||
};
|
||||
try {
|
||||
const docSearchClient = new DocumentSearchClient(new EsClientWithRequest(req), log);
|
||||
|
@ -134,15 +118,10 @@ export function documentSearchRoute(router: CodeServerRouter, log: Logger) {
|
|||
page = parseInt(p as string, 10);
|
||||
}
|
||||
|
||||
let scope: string[] = [];
|
||||
if (typeof repoScope === 'string') {
|
||||
scope = repoScope.split(',');
|
||||
}
|
||||
|
||||
const searchReq: DocumentSearchRequest = {
|
||||
query: q as string,
|
||||
page,
|
||||
repoScope: scope,
|
||||
repoScope: await getScope(req, repoScope),
|
||||
};
|
||||
try {
|
||||
const docSearchClient = new DocumentSearchClient(new EsClientWithRequest(req), log);
|
||||
|
@ -166,13 +145,17 @@ export function documentSearchRoute(router: CodeServerRouter, log: Logger) {
|
|||
method: 'POST',
|
||||
async handler(req: RequestFacade) {
|
||||
const reqs: StackTraceSnippetsRequest[] = (req.payload as any).requests;
|
||||
const scopes = new Set(
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).findReferences()
|
||||
);
|
||||
return await Promise.all(
|
||||
reqs.map((stacktraceReq: StackTraceSnippetsRequest) => {
|
||||
const integClient = new IntegrationsSearchClient(new EsClientWithRequest(req), log);
|
||||
return Promise.all(
|
||||
stacktraceReq.stacktraceItems.map((stacktrace: StackTraceItem) => {
|
||||
const repoUris = stacktraceReq.repoUris.filter(uri => scopes.has(uri));
|
||||
const integrationReq: ResolveSnippetsRequest = {
|
||||
repoUris: stacktraceReq.repoUris,
|
||||
repoUris,
|
||||
revision: stacktraceReq.revision,
|
||||
filePath: stacktrace.filePath,
|
||||
lineNumStart: stacktrace.lineNumStart,
|
||||
|
@ -195,15 +178,10 @@ export function symbolSearchRoute(router: CodeServerRouter, log: Logger) {
|
|||
page = parseInt(p as string, 10);
|
||||
}
|
||||
|
||||
let scope: string[] = [];
|
||||
if (typeof repoScope === 'string') {
|
||||
scope = repoScope.split(',');
|
||||
}
|
||||
|
||||
const searchReq: SymbolSearchRequest = {
|
||||
query: q as string,
|
||||
page,
|
||||
repoScope: scope,
|
||||
repoScope: await getScope(req, repoScope),
|
||||
};
|
||||
try {
|
||||
const symbolSearchClient = new SymbolSearchClient(new EsClientWithRequest(req), log);
|
||||
|
@ -238,18 +216,11 @@ export function commitSearchRoute(router: CodeServerRouter, log: Logger) {
|
|||
page = parseInt(p as string, 10);
|
||||
}
|
||||
|
||||
let scope: string[] = [];
|
||||
if (typeof repoScope === 'string') {
|
||||
scope = [repoScope];
|
||||
} else if (Array.isArray(repoScope)) {
|
||||
scope = repoScope;
|
||||
}
|
||||
|
||||
const searchReq: CommitSearchRequest = {
|
||||
query: q as string,
|
||||
page,
|
||||
repoFilters: repos ? decodeURIComponent(repos as string).split(',') : [],
|
||||
repoScope: scope,
|
||||
repoScope: await getScope(req, repoScope),
|
||||
};
|
||||
try {
|
||||
const commitSearchClient = new CommitSearchClient(new EsClientWithRequest(req), log);
|
||||
|
@ -261,3 +232,15 @@ export function commitSearchRoute(router: CodeServerRouter, log: Logger) {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function getScope(req: RequestFacade, repoScope: string | string[]): Promise<string[]> {
|
||||
let scope: string[] = await getReferenceHelper(req.getSavedObjectsClient()).findReferences();
|
||||
if (typeof repoScope === 'string') {
|
||||
const uriSet = new Set(repoScope.split(','));
|
||||
scope = scope.filter(uri => uriSet.has(uri));
|
||||
} else if (Array.isArray(repoScope)) {
|
||||
const uriSet = new Set(repoScope);
|
||||
scope = scope.filter(uri => uriSet.has(uri));
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { EsClientWithRequest } from '../utils/esclient_with_request';
|
|||
import { CodeServices } from '../distributed/code_services';
|
||||
import { GitServiceDefinition, LspServiceDefinition } from '../distributed/apis';
|
||||
import { Endpoint } from '../distributed/resource_locator';
|
||||
import { getReferenceHelper } from '../utils/repository_reference_helper';
|
||||
|
||||
export function statusRoute(router: CodeServerRouter, codeServices: CodeServices) {
|
||||
const gitService = codeServices.serviceFor(GitServiceDefinition);
|
||||
|
@ -115,7 +116,8 @@ export function statusRoute(router: CodeServerRouter, codeServices: CodeServices
|
|||
|
||||
try {
|
||||
// Check if the repository already exists
|
||||
await repoObjectClient.getRepository(uri);
|
||||
const repo = await repoObjectClient.getRepository(uri);
|
||||
await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repo.uri);
|
||||
} catch (e) {
|
||||
return Boom.notFound(`repo ${uri} not found`);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import { ServerOptions } from '../server_options';
|
|||
import { CodeServerRouter } from '../security';
|
||||
import { CodeServices } from '../distributed/code_services';
|
||||
import { WorkspaceDefinition } from '../distributed/apis';
|
||||
import { getReferenceHelper } from '../utils/repository_reference_helper';
|
||||
|
||||
export function workspaceRoute(
|
||||
router: CodeServerRouter,
|
||||
|
@ -33,6 +34,7 @@ export function workspaceRoute(
|
|||
method: 'POST',
|
||||
async handler(req: RequestFacade) {
|
||||
const repoUri = req.params.uri as string;
|
||||
getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri);
|
||||
const revision = req.params.revision as string;
|
||||
const repoConfig = serverOptions.repoConfigs[repoUri];
|
||||
const force = !!(req.query as RequestQueryFacade).force;
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
CommitSearchRequest,
|
||||
CommitSearchResult,
|
||||
CommitSearchResultItem,
|
||||
emptyCommitSearchResult,
|
||||
} from '../../model';
|
||||
import { CommitSearchIndexWithScope, CommitIndexNamePrefix } from '../indexer/schema';
|
||||
import { EsClient } from '../lib/esqueue';
|
||||
|
@ -30,6 +31,9 @@ export class CommitSearchClient extends AbstractSearchClient {
|
|||
const index = req.repoScope
|
||||
? CommitSearchIndexWithScope(req.repoScope)
|
||||
: `${CommitIndexNamePrefix}*`;
|
||||
if (index.length === 0) {
|
||||
return emptyCommitSearchResult(req.query);
|
||||
}
|
||||
|
||||
// Post filters for repository
|
||||
let repositoryPostFilters: object[] = [];
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
SearchResultItem,
|
||||
SourceHit,
|
||||
SourceRange,
|
||||
emptyDocumentSearchResult,
|
||||
} from '../../model';
|
||||
import { DocumentIndexNamePrefix, DocumentSearchIndexWithScope } from '../indexer/schema';
|
||||
import { EsClient } from '../lib/esqueue';
|
||||
|
@ -104,6 +105,9 @@ export class DocumentSearchClient extends AbstractSearchClient {
|
|||
const index = req.repoScope
|
||||
? DocumentSearchIndexWithScope(req.repoScope)
|
||||
: `${DocumentIndexNamePrefix}*`;
|
||||
if (index.length === 0) {
|
||||
return emptyDocumentSearchResult(req.query);
|
||||
}
|
||||
|
||||
const rawRes = await this.client.search({
|
||||
index,
|
||||
|
@ -241,6 +245,9 @@ export class DocumentSearchClient extends AbstractSearchClient {
|
|||
const index = req.repoScope
|
||||
? DocumentSearchIndexWithScope(req.repoScope)
|
||||
: `${DocumentIndexNamePrefix}*`;
|
||||
if (index.length === 0) {
|
||||
return emptyDocumentSearchResult(req.query);
|
||||
}
|
||||
|
||||
const queryStr = req.query.toLowerCase();
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
ResolveSnippetsRequest,
|
||||
SearchResultItem,
|
||||
SourceHit,
|
||||
emptyIntegrationsSearchResult,
|
||||
} from '../../model';
|
||||
import { DocumentSearchIndexWithScope } from '../indexer/schema';
|
||||
import { EsClient } from '../lib/esqueue';
|
||||
|
@ -24,6 +25,9 @@ export class IntegrationsSearchClient extends DocumentSearchClient {
|
|||
public async resolveSnippets(req: ResolveSnippetsRequest): Promise<IntegrationsSearchResult> {
|
||||
const { repoUris, filePath, lineNumStart, lineNumEnd } = req;
|
||||
const index = DocumentSearchIndexWithScope(repoUris);
|
||||
if (index.length === 0) {
|
||||
return emptyIntegrationsSearchResult();
|
||||
}
|
||||
|
||||
let fallback = false;
|
||||
let rawRes = await this.client.search({
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
RepositoryIndexStatusReservedField,
|
||||
RepositoryRandomPathReservedField,
|
||||
RepositoryReservedField,
|
||||
RepositorySearchIndexWithScope,
|
||||
} from '../indexer/schema';
|
||||
import { EsClient } from '../lib/esqueue';
|
||||
|
||||
|
@ -54,9 +55,20 @@ export class RepositoryObjectClient {
|
|||
return await this.getRepositoryObject(repoUri, RepositoryRandomPathReservedField);
|
||||
}
|
||||
|
||||
public async getRepositories(uris: string[]): Promise<Repository[]> {
|
||||
if (uris.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return this.getRepositoriesInternal(RepositorySearchIndexWithScope(uris));
|
||||
}
|
||||
|
||||
public async getAllRepositories(): Promise<Repository[]> {
|
||||
return await this.getRepositoriesInternal(`${RepositoryIndexNamePrefix}*`);
|
||||
}
|
||||
|
||||
private async getRepositoriesInternal(index: string) {
|
||||
const res = await this.esClient.search({
|
||||
index: `${RepositoryIndexNamePrefix}*`,
|
||||
index,
|
||||
body: {
|
||||
query: {
|
||||
exists: {
|
||||
|
|
|
@ -4,7 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Repository, RepositorySearchRequest, RepositorySearchResult } from '../../model';
|
||||
import {
|
||||
Repository,
|
||||
RepositorySearchRequest,
|
||||
RepositorySearchResult,
|
||||
emptyRepositorySearchResult,
|
||||
} from '../../model';
|
||||
import {
|
||||
RepositoryIndexNamePrefix,
|
||||
RepositoryReservedField,
|
||||
|
@ -28,6 +33,10 @@ export class RepositorySearchClient extends AbstractSearchClient {
|
|||
? RepositorySearchIndexWithScope(req.repoScope)
|
||||
: `${RepositoryIndexNamePrefix}*`;
|
||||
|
||||
if (index.length === 0) {
|
||||
return emptyRepositorySearchResult();
|
||||
}
|
||||
|
||||
const queryStr = req.query.toLowerCase();
|
||||
|
||||
const rawRes = await this.client.search({
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { DetailSymbolInformation } from '@elastic/lsp-extension';
|
||||
|
||||
import { SymbolSearchRequest, SymbolSearchResult } from '../../model';
|
||||
import { SymbolSearchRequest, SymbolSearchResult, emptySymbolSearchResult } from '../../model';
|
||||
import { SymbolIndexNamePrefix, SymbolSearchIndexWithScope } from '../indexer/schema';
|
||||
import { EsClient } from '../lib/esqueue';
|
||||
import { Logger } from '../log';
|
||||
|
@ -17,10 +17,10 @@ export class SymbolSearchClient extends AbstractSearchClient {
|
|||
super(client, log);
|
||||
}
|
||||
|
||||
public async findByQname(qname: string): Promise<SymbolSearchResult> {
|
||||
public async findByQname(qname: string, repoScope: string[]): Promise<SymbolSearchResult> {
|
||||
const [from, size] = [0, 1];
|
||||
const rawRes = await this.client.search({
|
||||
index: `${SymbolIndexNamePrefix}*`,
|
||||
index: SymbolSearchIndexWithScope(repoScope),
|
||||
body: {
|
||||
from,
|
||||
size,
|
||||
|
@ -42,6 +42,9 @@ export class SymbolSearchClient extends AbstractSearchClient {
|
|||
const index = req.repoScope
|
||||
? SymbolSearchIndexWithScope(req.repoScope)
|
||||
: `${SymbolIndexNamePrefix}*`;
|
||||
if (index.length === 0) {
|
||||
return emptySymbolSearchResult();
|
||||
}
|
||||
|
||||
const rawRes = await this.client.search({
|
||||
index,
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import Boom from 'boom';
|
||||
import { SAVED_OBJ_REPO } from '../../common/constants';
|
||||
|
||||
export interface RepositoryReferenceHelper {
|
||||
/**
|
||||
* Creates a reference from the current namespace to the given repository.
|
||||
*
|
||||
* @param uri The uri of the repository.
|
||||
* @returns true if there is no other references for the repository, false otherwise.
|
||||
*/
|
||||
createReference(uri: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Checks whether there is a reference from the current namespace to the given repository.
|
||||
*
|
||||
* @param uri The uri of the repository.
|
||||
* @returns true if there is a reference from the current namespace to the given repository, false otherwise.
|
||||
*/
|
||||
hasReference(uri: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Throws an error if there is no reference from the current namespace to the given repository.
|
||||
* there is none.
|
||||
*
|
||||
* @param uri The uri of the repository.
|
||||
*/
|
||||
ensureReference(uri: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes the reference from the current namespace to the given repository.
|
||||
*
|
||||
* @param uri The uri of the repository.
|
||||
* @returns True if there is no more references to the repository after the deletion.
|
||||
*/
|
||||
deleteReference(uri: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Finds all repository uris of which the current namespace has references.
|
||||
*
|
||||
* @returns uris the current namespace references to.
|
||||
*/
|
||||
findReferences(): Promise<string[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory function helps to create a appropriate helper instance.
|
||||
*
|
||||
* @param client A saved objects client instance.
|
||||
* @returns An helper instance.
|
||||
*/
|
||||
export function getReferenceHelper(client: SavedObjectsClientContract): RepositoryReferenceHelper {
|
||||
return new DefaultReferenceHelper(client);
|
||||
}
|
||||
|
||||
class DefaultReferenceHelper implements RepositoryReferenceHelper {
|
||||
constructor(private readonly client: SavedObjectsClientContract) {}
|
||||
|
||||
async createReference(uri: string): Promise<boolean> {
|
||||
try {
|
||||
await this.client.create(
|
||||
SAVED_OBJ_REPO,
|
||||
{
|
||||
uri,
|
||||
},
|
||||
{
|
||||
id: uri,
|
||||
}
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (Boom.isBoom(e) && e.output.statusCode === 409) {
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteReference(uri: string): Promise<boolean> {
|
||||
await this.client.delete(SAVED_OBJ_REPO, uri);
|
||||
return true;
|
||||
}
|
||||
|
||||
async ensureReference(uri: string): Promise<void> {
|
||||
await this.client.get(SAVED_OBJ_REPO, uri);
|
||||
}
|
||||
|
||||
async hasReference(uri: string): Promise<boolean> {
|
||||
try {
|
||||
await this.ensureReference(uri);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (Boom.isBoom(e) && e.output.statusCode === 404) {
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async findReferences(): Promise<string[]> {
|
||||
const resp = await this.client.find({
|
||||
type: SAVED_OBJ_REPO,
|
||||
});
|
||||
return resp.saved_objects.map(obj => obj.attributes.uri as string);
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import {
|
|||
import { deepFreeze } from '../../../../src/core/utils';
|
||||
import { PluginSetupContract as FeaturesSetupContract } from '../../features/server';
|
||||
import { CodeConfigSchema } from './config';
|
||||
import { SAVED_OBJ_REPO } from '../../../legacy/plugins/code/common/constants';
|
||||
|
||||
/**
|
||||
* Describes public Code plugin contract returned at the `setup` stage.
|
||||
|
@ -57,7 +58,7 @@ export class CodePlugin {
|
|||
excludeFromBasePrivileges: true,
|
||||
api: ['code_user', 'code_admin'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
all: [SAVED_OBJ_REPO],
|
||||
read: ['config'],
|
||||
},
|
||||
ui: ['show', 'user', 'admin'],
|
||||
|
@ -66,7 +67,7 @@ export class CodePlugin {
|
|||
api: ['code_user'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['config'],
|
||||
read: ['config', SAVED_OBJ_REPO],
|
||||
},
|
||||
ui: ['show', 'user'],
|
||||
},
|
||||
|
|
|
@ -4073,6 +4073,7 @@
|
|||
"xpack.code.repoItem.reindexConfirmTitle": "重新索引此存储库?",
|
||||
"xpack.code.repoItem.settingsButtonLabel": "设置",
|
||||
"xpack.code.repositoryManagement.repoImportedMessage": "此存储库已导入!",
|
||||
"xpack.code.repositoryManagement.repoOtherSpaceImportedMessage": "此存储库已在其他空间导入!",
|
||||
"xpack.code.repositoryManagement.repoSubmittedMessage": "{name} 已成功提交!",
|
||||
"xpack.code.repoFileStatus.langugageServerIsInitializitingMessage": "语言服务器正在初始化。",
|
||||
"xpack.code.repoFileStatus.languageServerInitializedMessage": "语言服务器已初始化。",
|
||||
|
|
|
@ -12,7 +12,6 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
|
|||
const supertest = getService('supertestWithoutAuth');
|
||||
const security: SecurityService = getService('security');
|
||||
const spaces: SpacesService = getService('spaces');
|
||||
const log = getService('log');
|
||||
|
||||
const expect404 = (result: any) => {
|
||||
expect(result.error).to.be(undefined);
|
||||
|
@ -38,12 +37,14 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
|
|||
url: `/api/code/repo/github.com/elastic/code-examples_empty-file`,
|
||||
expectForbidden: expect404,
|
||||
expectResponse: expect200,
|
||||
isRepoApi: true,
|
||||
},
|
||||
{
|
||||
// Get the status of one repository.
|
||||
url: `/api/code/repo/status/github.com/elastic/code-examples_empty-file`,
|
||||
expectForbidden: expect404,
|
||||
expectResponse: expect200,
|
||||
isRepoApi: true,
|
||||
},
|
||||
{
|
||||
// Get all language server installation status.
|
||||
|
@ -104,10 +105,13 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
|
|||
username: string,
|
||||
password: string,
|
||||
spaceId: string,
|
||||
expectation: 'forbidden' | 'response'
|
||||
expectation: 'forbidden' | 'response',
|
||||
skipRepoApi: boolean = false
|
||||
) {
|
||||
for (const endpoint of endpoints) {
|
||||
log.debug(`hitting ${endpoint}`);
|
||||
if (skipRepoApi && endpoint.isRepoApi === true) {
|
||||
continue;
|
||||
}
|
||||
const result = await executeRequest(endpoint.url, username, password, spaceId);
|
||||
if (expectation === 'forbidden') {
|
||||
endpoint.expectForbidden(result);
|
||||
|
@ -337,6 +341,12 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
|
|||
});
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
code: ['read'],
|
||||
},
|
||||
spaces: ['default'],
|
||||
},
|
||||
{
|
||||
feature: {
|
||||
code: ['read'],
|
||||
|
@ -364,8 +374,12 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
|
|||
await security.user.delete(username);
|
||||
});
|
||||
|
||||
it('user_1 can access APIs in space_1', async () => {
|
||||
await executeRequests(username, password, space1Id, 'response');
|
||||
it('user_1 can access all APIs in default space', async () => {
|
||||
await executeRequests(username, password, '', 'response');
|
||||
});
|
||||
|
||||
it('user_1 can access code admin APIs in space_1', async () => {
|
||||
await executeRequests(username, password, space1Id, 'response', true);
|
||||
});
|
||||
|
||||
it(`user_1 cannot access APIs in space_2`, async () => {
|
||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue