Remove experimental code app (#49404)

* Fix erroneous code plugin import in apm

* Stop running legacy code plugin

* Stop testing code app and apis

* Remove code plugin source and deprecate config

* Remove code plugin docs

* Remove xpack.code translations

* Remove code import on api_integration

* Remove code privilege from security test

* remove two more mentions of code

* remove code es_archives

* remove code doc images
This commit is contained in:
Court Ewing 2019-10-28 11:40:27 -04:00 committed by GitHub
parent d9a5acf971
commit 0b82cfeae7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
519 changed files with 55 additions and 51353 deletions

View file

@ -1,25 +0,0 @@
[[code-basic-nav]]
== Basic navigation
[float]
==== View file structure and information
The *File* tree on the left is the primary way to navigate through your folder structure. When you are in a directory, *Code* also presents a finder-like view on the right. Additionally, a file path breadcrumb makes it easy to go back to any parent directory.
[float]
==== Git History and Blame
The *Directory* view shows the most recent commits. Clicking *View More* or *History* shows the complete commit history of the current folder or file.
[role="screenshot"]
image::images/code-history.png[]
Clicking *Blame* shows the most recent commit per line.
[role="screenshot"]
image::images/code-blame.png[]
[float]
==== Branch selector
You can use the Branch selector to view different branches of a repo. Note that code intelligence and search index are not available for any branch other than the master branch.
include::code-semantic-nav.asciidoc[]

View file

@ -1,48 +0,0 @@
[[code-import-first-repo]]
== Import your first repo
The easiest way to get started with *Code* is to import a real-world repository.
[float]
==== Before you begin
You must have a {kib} instance up and running.
If you are in an environment where you have multiple {kib} instances in a cluster, see the <<code-multiple-kibana-instances-config, config instructions for multiple Kibana instances>>.
[float]
==== Enable Code app
While in beta, you can turn on *Code* by adding the following line to `kibana.yaml`:
[source,yaml]
----
xpack.code.ui.enabled: true
----
[float]
==== Import your first repository
. In {Kib}, navigate to *Code*.
. In the *Repository URL* field, paste the following GitHub clone URL:
+
[source,bash]
----
https://github.com/Microsoft/TypeScript-Node-Starter
----
`https` is recommend for cloning most git repositories. To clone a private repository, <<code-repo-management, use SSH>>.
. Click *Import*.
+
A new item in the list displays the cloning and indexing progress of the `TypeScript-Node-Starter` repo.
+
[role="screenshot"]
image::images/code-import-repo.png[]
. After the indexing is complete, navigate to the repo by clicking its name in the list.
+
[role="screenshot"]
image::images/code-starter-root.png[]
+
Congratulations! You just imported your first repo into *Code*.
include::code-repo-management.asciidoc[]

View file

@ -1,25 +0,0 @@
[[code-install-lang-server]]
== Install language server
*Code* comes with built-in language support for TypeScript. You can install additional languages as a {kib} plugin. Plugin's reduce the distribution size to run Code inside {kib}. Install only the languages needed for your indexed repositories.
[role="screenshot"]
image::images/code-lang-server-tab.png[]
For the current version, *Code* supports the following languages in addition to TypeScript:
* `Java`
* `GO`
You can check the status of the language servers and get installation instructions on the *Language Servers* tab. Make sure the status of the language server is `INSTALLED` or `RUNNING` after you restart the {kib} instance.
[role="screenshot"]
image::images/code-lang-server-status.png[]
////
[float]
=== Ctag language server
*Code* also uses a Ctag language server to generate symbol information and code intelligence when a dedicated language server is not available. The code intelligence information generated by the Ctag language server is less accurate but covers more languages.
////
include::code-basic-nav.asciidoc[]

View file

@ -1,10 +0,0 @@
[[code-multiple-kibana-instances-config]]
== Config for multiple {kib} instances
If you are using multiple instances of {kib}, you must manually assign at least one {kib} instance as a *Code* `node`. Add the following line of code to your `kibana.yml` file for each {kib} instance you wish to use and restart the instances:
[source,yaml]
----
xpack.code.codeNodeUrl: 'http://$YourCodeNodeAddress'
----
`$YourCodeNoteAddress` is the URL of your assigned *Code* node accessible by other {kib} instances.

View file

@ -1,76 +0,0 @@
[[code-repo-management]]
== Repo management
Code starts with an overview of your repositories. You can then use the UI to add, delete, and reindex a repo.
[role="screenshot"]
image::images/code-repo-management.png[]
[float]
[[add-delete-a-repo]]
==== Add and delete a repo
The <<code-import-first-repo, Import your first repository>> page provides step-by-step instructions for adding a GitHub repo to *Code*. You can fine-tune the hostname of the git clone URL in your `kibana.yml` file.
For security reasons, Code allows only a few <<clone-url-management,trusted hostnames>>, such as github.com. To clone private repositories see <<clone-private-repo,add an SSH key>>.
To delete a repository, go to the **Repositories** tab, find the name of the repo, and click *Delete*.
[float]
[[clone-private-repo]]
==== Clone private repo with an SSH key
Clones of private repos require an SSH key for authentication. The username associated with your host must have write access to the repository you want to clone.
The following section provides links for generating your ssh key through GitHub. If you already have an SSH key added through your host, skip to step 4.
1. https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#generating-a-new-ssh-key[Generate an ssh key].
2. https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#adding-your-ssh-key-to-the-ssh-agent[Add the ssh key to your ssh-agent.]
3. https://help.github.com/en/articles/adding-a-new-ssh-key-to-your-github-account[Add the ssh key to your host].
4. Copy the ssh key into `data/code/credentials/` under the {kib} folder.
You can now copy private Git repositories into Code.
To delete a repository, find the go to the **Repositories** tab, find the name of the repo and click *Delete*.
[float]
[[reindex-a-repo]]
==== Reindex a repo
*Code* automatically reindexes an imported repo at set intervals, but in some cases, you might need to refresh the index manually. For example, you might refresh an index after a new language server installs. Or, you might want to update the index to the HEAD revision immediately. Click *Reindex* to initiate a reindex.
[float]
[[clone-url-management]]
==== Clone URL management
For security reasons, *Code* only allows the following hostnames in the git clone URL by default:
- `github.com`
- `gitlab.com`
- `bitbucket.org`
- `gitbox.apache.org`
- `eclipse.org`
You can add your own hostname (for example, acme.com) to the whitelist by adding the following line to your `config/kibana.yaml` file:
[source,yaml]
----
xpack.code.security.gitHostWhitelist: [ "github.com", "gitlab.com", "bitbucket.org", "gitbox.apache.org", "eclipse.org", "acme.com" ]
----
Set `xpack.code.security.gitHostWhitelist` to [] (empty list) allow any hostname.
You can also control the protocol to use for the clone address. By default, the following protocols are supported: `[ 'https', 'git', 'ssh' ]`. You can change this value by adding the following line to your `config/kibana.yaml` file. In this example, the user only wants to support the `https` protocol:
[source,yaml]
----
xpack.code.security.gitProtocolWhitelist: [ "https" ]
----
[float]
[[repo-limitations]]
==== Limitations
Consider the following limitations before cloning repositories:
- Only Git is supported. Other version control systems, such as Mercurial and SVN, are not supported.
- Your disk might not have enough space to clone repositories due to {kib} watermark settings. To update your watermark settings, contact your system administrator and request additional disk space.
include::code-install-lang-server.asciidoc[]

View file

@ -1,24 +0,0 @@
[[code-search]]
== Search
[float]
==== Typeahead search
The search bar is designed to help you find what you're looking for as quickly as possible. It shows `Symbols`, `Files`, and `Repos` results as you type. Clicking on any result takes you to the definition. You can use the search type dropdown to show all types of results or limit it to a specific search type.
[role="screenshot"]
image::images/code-quick-search.png[]
[float]
==== Full-text search
If the quick search results dont contain what you are looking for, you can press Enter to conduct a full-text search.
[role="screenshot"]
image::images/code-full-text-search.png[]
You can further refine the results by using the repo and language filters on the left.
[float]
==== Search filter
You can also use the Search Filters to limit the search scope to certain repos before issuing a query. To search across all repos, remove any applied repo filters. By default, search results are limited to the repo you're currently viewing.
[role="screenshot"]
image::images/code-search-filter.png[]
include::code-multiple-kibana-instances-config.asciidoc[]

View file

@ -1,28 +0,0 @@
[[code-semantic-nav]]
== Semantic code navigation
You can navigate a file with semantic code navigation features if:
- *Code* supports the file's <<code-install-lang-server, language>>
- You have installed the corresponding <<code-install-lang-server, language server>>
[float]
==== Goto definition and find reference
Hovering your cursor over a symbol in a file opens information about the symbol, including its qualified name and documentation, when available. You can perform two actions:
* *Goto Definition* navigates to the symbol definition. Definitions defined in a different repo can be found, provided that you have imported the repo with the definition.
* *Find Reference* opens a panel that lists all references to the symbol.
[role="screenshot"]
image::images/code-semantic-nav.png[]
[float]
==== View symbol table
From the *Structure* tab, you can open a symbol table that details the structure of the current class. Clicking on a member function or variable jumps to its definition.
[role="screenshot"]
image::images/code-symbol-table.png[]
include::code-search.asciidoc[]

View file

@ -1,21 +0,0 @@
[[code]]
= Code
[partintro]
--
beta[]
Interaction with source code is pervasive and essential for any technology company. Speed of innovation is limited by how easy it is to search, navigate, and gain insight into your source code.
Elastic *Code* provides an easy-to-use code search solution that scales with your organization and empowers your team to understand your codebase faster than ever before.
*Code* offers the following functions:
* Find references and definitions for any object or symbol
* Typeahead search for symbol definition, file, and repo
* Symbol table
* Full-text search with repo and language filters
<<code-import-first-repo, Get Started>> with *Code* by importing your first repo.
--
include::code-import-first-repo.asciidoc[]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 901 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 698 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 KiB

View file

@ -1,51 +0,0 @@
[role="xpack"]
[[code-settings-kibana]]
=== Code Settings in Kibana
++++
<titleabbrev>Code settings</titleabbrev>
++++
Unless you are running multiple Kibana instances as a cluster, you do not need to change any settings to use *Code* by default. If youd like to change any of the default values, copy and paste the relevant settings below into your `kibana.yml` configuration file.
`xpack.code.updateRepoFrequencyMs`::
Repo update frequency in milliseconds. Defaults to `300000`.
`xpack.code.indexRepoFrequencyMs`::
Repo index frequency in milliseconds. Defaults to `86400000`.
`xpack.code.lsp.verbose`::
Whether to show more verbose log for language servers. Defaults to `false`.
`xpack.code.security.enableMavenImport`::
Whether to support maven. Defaults to `true`.
`xpack.code.security.enableGradleImport`::
Whether to support gradle. Defaults to `false`.
`xpack.code.security.installNodeDependency`::
Whether to enable node dependency download. Defaults to `true`.
`xpack.code.security.gitHostWhitelist`::
Whitelist of hostnames for git clone address. Defaults to `[ 'github.com', 'gitlab.com', 'bitbucket.org', 'gitbox.apache.org', 'eclipse.org',]`.
`xpack.code.security.gitProtocolWhitelist`::
Whitelist of protocols for git clone address. Defaults to `[ 'https', 'git', 'ssh' ]`.
`xpack.code.security.enableGitCertCheck`::
Whether enable HTTPS certificate check when clone from HTTPS URL.
`xpack.code.security.enableJavaSecurityManager`::
Whether enable Java security manager for Java langserver. Defaults to `true`.
`xpack.code.security.extraJavaRepositoryWhitelist`::
Whitelist of extra repository to download dependencies for Java language. Defaults to `[]`.
`xpack.code.maxWorkspace`::
Maximal number of workspaces each language server allows to span. Defaults to `5`.
`xpack.code.codeNodeUrl`::
URL of the Code node. This config is only needed when multiple Kibana instances are set up as a cluster. Defaults to ``
`xpack.code.verbose`::
Set this config to `true` to log all events. Defaults to `false`

View file

@ -277,8 +277,8 @@ setting specifies the port to use.
`server.rewriteBasePath:`:: *Default: deprecated* Specifies whether Kibana should
rewrite requests that are prefixed with `server.basePath` or require that they
are rewritten by your reverse proxy. In Kibana 6.3 and earlier, the default is
`false`. In Kibana 7.x, the setting is deprecated. In Kibana 8.0 and later, the
are rewritten by your reverse proxy. In Kibana 6.3 and earlier, the default is
`false`. In Kibana 7.x, the setting is deprecated. In Kibana 8.0 and later, the
default is `true`.
`server.socketTimeout:`:: *Default: "120000"* The number of milliseconds to wait before closing an
@ -327,7 +327,6 @@ Rollup user interface.
include::{docdir}/settings/apm-settings.asciidoc[]
include::{docdir}/settings/code-settings.asciidoc[]
include::{docdir}/settings/dev-settings.asciidoc[]
include::{docdir}/settings/graph-settings.asciidoc[]
include::{docdir}/settings/infrastructure-ui-settings.asciidoc[]

View file

@ -20,8 +20,6 @@ include::extend.asciidoc[]
include::{kib-repo-dir}/maps/index.asciidoc[]
include::{kib-repo-dir}/code/index.asciidoc[]
include::{kib-repo-dir}/infrastructure/index.asciidoc[]
include::{kib-repo-dir}/logs/index.asciidoc[]

View file

@ -7,7 +7,6 @@
"xpack.apm": "legacy/plugins/apm",
"xpack.beatsManagement": "legacy/plugins/beats_management",
"xpack.canvas": "legacy/plugins/canvas",
"xpack.code": ["legacy/plugins/code", "plugins/code"],
"xpack.crossClusterReplication": "legacy/plugins/cross_cluster_replication",
"xpack.dashboardMode": "legacy/plugins/dashboard_mode",
"xpack.features": "plugins/features",

View file

@ -18,7 +18,6 @@ import { dashboardMode } from './legacy/plugins/dashboard_mode';
import { logstash } from './legacy/plugins/logstash';
import { beats } from './legacy/plugins/beats_management';
import { apm } from './legacy/plugins/apm';
import { code } from './legacy/plugins/code';
import { maps } from './legacy/plugins/maps';
import { licenseManagement } from './legacy/plugins/license_management';
import { cloud } from './legacy/plugins/cloud';
@ -62,7 +61,6 @@ module.exports = function (kibana) {
logstash(kibana),
beats(kibana),
apm(kibana),
code(kibana),
maps(kibana),
canvas(kibana),
licenseManagement(kibana),

View file

@ -6,7 +6,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import { px } from '../../../../../code/public/style/variables';
import { px } from '../../../../public/style/variables';
import { ErrorCountBadge } from '../../app/TransactionDetails/WaterfallWithSummmary/ErrorCountBadge';
import { units } from '../../../style/variables';

View file

@ -1,33 +0,0 @@
/*
* 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 { parseCommitMessage } from './commit_utils';
describe('Commit Utils', () => {
describe('parseCommitMessage', () => {
it('parses the summary from a commit message', () => {
const message = "This is a summary\n\nAnd here's the body";
const { summary } = parseCommitMessage(message);
expect(summary).toEqual('This is a summary');
});
it('parses the body from a commit message', () => {
const message = "This is a summary\n\nAnd here's the body";
const { body } = parseCommitMessage(message);
expect(body).toEqual("And here's the body");
});
it('includes the second line as body, if erroneously present', () => {
const messageWithSecondLine = 'summary\nsecond\ndescription';
const { summary, body } = parseCommitMessage(messageWithSecondLine);
expect(summary).toEqual('summary');
expect(body).toEqual('second\ndescription');
});
});
});

View file

@ -1,14 +0,0 @@
/*
* 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.
*/
const parseCommitMessage: (message: string) => { summary: string; body: string } = message => {
const [summary, ...rest] = message.split('\n');
const body = rest.join('\n').trim();
return { summary, body };
};
export { parseCommitMessage };

View file

@ -1,9 +0,0 @@
/*
* 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.
*/
export const APP_TITLE = 'Code (Beta)';
export const APP_USAGE_TYPE = 'code';
export const SAVED_OBJ_REPO = 'code-repo';

View file

@ -1,7 +0,0 @@
/*
* 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.
*/
export const TEXT_FILE_LIMIT = 1024 * 1024; // 1mb

View file

@ -1,19 +0,0 @@
/*
* 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.
*/
export interface GitBlame {
committer: {
name: string;
email: string;
};
startLine: number;
lines: number;
commit: {
id: string;
message: string;
date: string;
};
}

View file

@ -1,35 +0,0 @@
/*
* 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 { CommitInfo } from '../model/commit';
export interface Diff {
additions: number;
deletions: number;
files: FileDiff[];
}
export interface CommitDiff extends Diff {
commit: CommitInfo;
}
export interface FileDiff {
path: string;
originPath?: string;
kind: DiffKind;
originCode?: string;
modifiedCode?: string;
language?: string;
additions: number;
deletions: number;
}
export enum DiffKind {
ADDED = 'ADDED',
DELETED = 'DELETED',
MODIFIED = 'MODIFIED',
RENAMED = 'RENAMED',
}

View file

@ -1,60 +0,0 @@
/*
* 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 { validateGitUrl } from './git_url_utils';
test('Git url validation', () => {
// An url ends with .git
expect(validateGitUrl('https://github.com/elastic/elasticsearch.git')).toBeTruthy();
// An url ends without .git
expect(validateGitUrl('https://github.com/elastic/elasticsearch')).toBeTruthy();
// An url with http://
expect(validateGitUrl('http://github.com/elastic/elasticsearch')).toBeTruthy();
// An url with ssh://
expect(validateGitUrl('ssh://elastic@github.com/elastic/elasticsearch.git')).toBeTruthy();
// An url with ssh:// and port
expect(validateGitUrl('ssh://elastic@github.com:9999/elastic/elasticsearch.git')).toBeTruthy();
// An url with git://
expect(validateGitUrl('git://elastic@github.com/elastic/elasticsearch.git')).toBeTruthy();
// An url with an invalid protocol
expect(() => {
validateGitUrl('file:///Users/elastic/elasticsearch', [], ['ssh', 'https', 'git']);
}).toThrow('Git url protocol is not whitelisted.');
// An url without protocol
expect(() => {
validateGitUrl('/Users/elastic/elasticsearch', [], ['ssh', 'https', 'git']);
}).toThrow('Git url protocol is not whitelisted.');
expect(() => {
validateGitUrl('github.com/elastic/elasticsearch', [], ['ssh', 'https', 'git']);
}).toThrow('Git url protocol is not whitelisted.');
// An valid git url but without whitelisted host
expect(() => {
validateGitUrl('https://github.com/elastic/elasticsearch.git', ['gitlab.com']);
}).toThrow('Git url host is not whitelisted.');
// An valid git url but without whitelisted protocol
expect(() => {
validateGitUrl('https://github.com/elastic/elasticsearch.git', [], ['ssh']);
}).toThrow('Git url protocol is not whitelisted.');
// An valid git url but contains invalid content
expect(() => {
validateGitUrl('https://github.com/elastic/../../elasticsearch.git', [], ['ssh']);
}).toThrow('Git url contains invalid content.');
// An valid git url with both whitelisted host and protocol
expect(
validateGitUrl('https://github.com/elastic/elasticsearch.git', ['github.com'], ['https'])
).toBeTruthy();
});

View file

@ -1,49 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
import GitUrlParse from 'git-url-parse';
// return true if the git url is valid, otherwise throw Error with
// exact reasons.
export function validateGitUrl(
url: string,
hostWhitelist?: string[],
protocolWhitelist?: string[]
): boolean {
if (url.split('/').includes('..')) {
throw new Error(
i18n.translate('xpack.code.gitUrlUtil.urlContainInvalidContent', {
defaultMessage: 'Git url contains invalid content.',
})
);
}
const repo = GitUrlParse(url);
if (hostWhitelist && hostWhitelist.length > 0) {
const hostSet = new Set(hostWhitelist);
if (!hostSet.has(repo.source)) {
throw new Error(
i18n.translate('xpack.code.gitUrlUtil.urlNotWhitelistedMessage', {
defaultMessage: 'Git url host is not whitelisted.',
})
);
}
}
if (protocolWhitelist && protocolWhitelist.length > 0) {
const protocolSet = new Set(protocolWhitelist);
if (!protocolSet.has(repo.protocol)) {
throw new Error(
i18n.translate('xpack.code.gitUrlUtil.protocolNotWhitelistedMessage', {
defaultMessage: 'Git url protocol is not whitelisted.',
})
);
}
}
return true;
}

View file

@ -1,10 +0,0 @@
/*
* 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.
*/
export enum InstallationType {
Embed,
Plugin,
}

View file

@ -1,56 +0,0 @@
/*
* 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 { UnknownErrorCode } from './lsp_error_codes';
// Since vscode-jsonrpc can't be used under IE, we copy some type definitions from
// https://github.com/Microsoft/vscode-languageserver-node/blob/8801c20b/jsonrpc/src/messages.ts
/**
* An error object return in a response in case a request
* has failed.
*/
export class ResponseError extends Error {
public readonly code: number;
public readonly data: any | undefined;
constructor(code: number, message: string, data?: any) {
super(message);
this.code = Number.isInteger(code) ? code : UnknownErrorCode;
this.data = data;
Object.setPrototypeOf(this, ResponseError.prototype);
}
public toJson() {
return {
code: this.code,
message: this.message,
data: this.data,
};
}
}
export interface Message {
jsonrpc: string;
}
/**
* A response message.
*/
export interface ResponseMessage extends Message {
/**
* The request id.
*/
id: number | string | null;
/**
* The result of a request. This can be omitted in
* the case of an error.
*/
result?: any;
/**
* The error object in case a request fails.
*/
error?: ResponseError;
}

View file

@ -1,56 +0,0 @@
/*
* 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 { InstallationType } from './installation';
export enum LanguageServerStatus {
NOT_INSTALLED,
INSTALLING,
READY, // installed but not running
RUNNING,
LAUNCH_FAILED,
}
export interface LanguageServer {
name: string;
languages: string[];
installationType: InstallationType;
version?: string;
build?: string;
status?: LanguageServerStatus;
downloadUrl?: any;
pluginName?: string;
}
export const CTAGS_SUPPORT_LANGS = [
'c',
'cpp',
'clojure',
'csharp',
'css',
'go',
'html',
'ini',
'kotlin',
'lua',
'json',
'objective-c',
'pascal',
'perl',
'php',
'python',
'r',
'ruby',
'rust',
'scheme',
'shell',
'sql',
'swift',
'tcl',
'typescript',
'java',
'javascript',
];

View file

@ -1,37 +0,0 @@
/*
* 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 _ from 'lodash';
import { SourceLocation } from '../model';
export class LineMapper {
private lines: string[];
private acc: number[];
constructor(content: string) {
this.lines = content.split('\n');
this.acc = [0];
this.getLocation = this.getLocation.bind(this);
for (let i = 0; i < this.lines.length - 1; i++) {
this.acc[i + 1] = this.acc[i] + this.lines[i].length + 1;
}
}
public getLocation(offset: number): SourceLocation {
let line = _.sortedIndex(this.acc, offset);
if (offset !== this.acc[line]) {
line -= 1;
}
const column = offset - this.acc[line];
return { line, column, offset };
}
public getLines(): string[] {
return this.lines;
}
}

View file

@ -1,43 +0,0 @@
/*
* 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 { npStart } from 'ui/new_platform';
import { ResponseError, ResponseMessage } from './jsonrpc';
export { TextDocumentMethods } from './text_document_methods';
export interface LspClient {
sendRequest(method: string, params: any, singal?: AbortSignal): Promise<ResponseMessage>;
}
export class LspRestClient implements LspClient {
private baseUri: string;
constructor(baseUri: string) {
this.baseUri = baseUri;
}
public async sendRequest(
method: string,
params: any,
signal?: AbortSignal
): Promise<ResponseMessage> {
try {
const response = await npStart.core.http.post(`${this.baseUri}/${method}`, {
body: JSON.stringify(params),
signal,
});
return response as ResponseMessage;
} catch (e) {
let error = e;
if (error.body && error.body.error) {
error = error.body.error;
}
throw new ResponseError(error.code, error.message, error.data);
}
}
}

View file

@ -1,14 +0,0 @@
/*
* 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.
*/
export const ServerNotInitialized: number = -32002;
export const UnknownErrorCode: number = -32001;
export const RequestCancelled: number = -32800;
export const InternalError: number = -32603;
export const UnknownFileLanguage: number = -42404;
export const LanguageServerNotInstalled: number = -42403;
export const LanguageDisabled: number = -42402;
export const LanguageServerStartFailed: number = -42405;

View file

@ -1,39 +0,0 @@
/*
* 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 { AsyncTask } from '../public/monaco/computer';
import { LspClient } from './lsp_client';
export class LspMethod<INPUT, OUTPUT> {
private client: LspClient;
private method: string;
constructor(method: string, client: LspClient) {
this.client = client;
this.method = method;
}
public asyncTask(input: INPUT): AsyncTask<OUTPUT> {
const abortController = new AbortController();
const promise = () => {
return this.client
.sendRequest(this.method, input, abortController.signal)
.then(result => result.result as OUTPUT);
};
return {
cancel() {
abortController.abort();
},
promise,
};
}
public async send(input: INPUT): Promise<OUTPUT> {
return await this.client
.sendRequest(this.method, input)
.then(result => result.result as OUTPUT);
}
}

View file

@ -1,136 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
export enum RepoFileStatus {
LANG_SERVER_IS_INITIALIZING = 'Language server is initializing.',
LANG_SERVER_INITIALIZED = 'Language server initialized.',
INDEXING = 'Indexing in progress',
FILE_NOT_SUPPORTED = 'Current file is not of a supported language.',
REVISION_NOT_INDEXED = 'Current revision is not indexed.',
LANG_SERVER_NOT_INSTALLED = 'Install additional language server to support current file.',
FILE_IS_TOO_BIG = 'Current file is too big.',
LANG_SERVER_LAUNCH_FAILED = 'Language server failed to launch',
}
export enum Severity {
NONE,
NOTICE,
WARNING,
ERROR,
}
export enum LangServerType {
NONE = 'Current file is not supported by any language server',
GENERIC = 'Current file is only covered by generic language server',
DEDICATED = 'Current file is covered by dedicated language server',
}
export const RepoFileStatusText = {
[RepoFileStatus.LANG_SERVER_IS_INITIALIZING]: i18n.translate(
'xpack.code.repoFileStatus.langugageServerIsInitializitingMessage',
{
defaultMessage: 'Language server is initializing.',
}
),
[RepoFileStatus.LANG_SERVER_INITIALIZED]: i18n.translate(
'xpack.code.repoFileStatus.languageServerInitializedMessage',
{
defaultMessage: 'Language server initialized.',
}
),
[RepoFileStatus.INDEXING]: i18n.translate('xpack.code.repoFileStatus.IndexingInProgressMessage', {
defaultMessage: 'Indexing in progress.',
}),
[RepoFileStatus.FILE_NOT_SUPPORTED]: i18n.translate(
'xpack.code.repoFileStatus.fileNotSupportedMessage',
{
defaultMessage: 'Current file is not of a supported language.',
}
),
[RepoFileStatus.REVISION_NOT_INDEXED]: i18n.translate(
'xpack.code.repoFileStatus.revisionNotIndexedMessage',
{
defaultMessage: 'Current revision is not indexed.',
}
),
[RepoFileStatus.LANG_SERVER_NOT_INSTALLED]: i18n.translate(
'xpack.code.repoFileStatus.langServerNotInstalledMessage',
{
defaultMessage: 'Install additional language server to support current file.',
}
),
[RepoFileStatus.FILE_IS_TOO_BIG]: i18n.translate(
'xpack.code.repoFileStatus.fileIsTooBigMessage',
{
defaultMessage: 'Current file is too big.',
}
),
[LangServerType.NONE]: i18n.translate('xpack.code.repoFileStatus.langserverType.noneMessage', {
defaultMessage: 'Current file is not supported by any language server.',
}),
[LangServerType.GENERIC]: i18n.translate(
'xpack.code.repoFileStatus.langserverType.genericMessage',
{
defaultMessage: 'Current file is only covered by generic language server.',
}
),
[LangServerType.DEDICATED]: i18n.translate(
'xpack.code.repoFileStatus.langserverType.dedicatedMessage',
{
defaultMessage: 'Current file is covered by dedicated language server.',
}
),
[RepoFileStatus.LANG_SERVER_LAUNCH_FAILED]: i18n.translate(
'xpack.code.repoFileStatus.langServerLaunchFailed',
{
defaultMessage: 'Language server launch failed.',
}
),
};
export enum CTA {
SWITCH_TO_HEAD,
GOTO_LANG_MANAGE_PAGE,
}
export const REPO_FILE_STATUS_SEVERITY = {
[RepoFileStatus.LANG_SERVER_IS_INITIALIZING]: {
severity: Severity.NOTICE,
},
[RepoFileStatus.INDEXING]: {
severity: Severity.NOTICE,
},
[RepoFileStatus.FILE_NOT_SUPPORTED]: {
severity: Severity.NOTICE,
},
[LangServerType.GENERIC]: {
severity: Severity.NOTICE,
},
[RepoFileStatus.REVISION_NOT_INDEXED]: {
severity: Severity.WARNING,
fix: CTA.SWITCH_TO_HEAD,
},
[RepoFileStatus.LANG_SERVER_NOT_INSTALLED]: {
severity: Severity.WARNING,
fix: CTA.GOTO_LANG_MANAGE_PAGE,
},
[RepoFileStatus.FILE_IS_TOO_BIG]: {
severity: Severity.NOTICE,
},
};
export interface StatusReport {
repoStatus?: RepoFileStatus.INDEXING | RepoFileStatus.REVISION_NOT_INDEXED;
fileStatus?: RepoFileStatus.FILE_NOT_SUPPORTED | RepoFileStatus.FILE_IS_TOO_BIG;
langServerType?: LangServerType;
langServerStatus?:
| RepoFileStatus.LANG_SERVER_IS_INITIALIZING
| RepoFileStatus.LANG_SERVER_NOT_INSTALLED
| RepoFileStatus.LANG_SERVER_LAUNCH_FAILED
| RepoFileStatus.LANG_SERVER_INITIALIZED;
}

View file

@ -1,253 +0,0 @@
/*
* 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 { FileTreeItemType } from '../model';
import { RepositoryUtils } from './repository_utils';
test('Repository url parsing', () => {
// Valid git url without .git suffix.
const repo1 = RepositoryUtils.buildRepository('https://github.com/apache/sqoop');
expect(repo1).toEqual({
uri: 'github.com/apache/sqoop',
url: 'https://github.com/apache/sqoop',
name: 'sqoop',
org: 'apache',
protocol: 'https',
});
// Valid git url with .git suffix.
const repo2 = RepositoryUtils.buildRepository('https://github.com/apache/sqoop.git');
expect(repo2).toEqual({
uri: 'github.com/apache/sqoop',
url: 'https://github.com/apache/sqoop.git',
name: 'sqoop',
protocol: 'https',
org: 'apache',
});
// An invalid git url
const repo3 = RepositoryUtils.buildRepository('github.com/apache/sqoop');
expect(repo3).toMatchObject({
uri: 'github.com/apache/sqoop',
url: 'github.com/apache/sqoop',
});
const repo4 = RepositoryUtils.buildRepository('git://a/b');
expect(repo4).toEqual({
uri: 'a/_/b',
url: 'git://a/b',
name: 'b',
org: '_',
protocol: 'git',
});
const repo5 = RepositoryUtils.buildRepository('git://a/b/c');
expect(repo5).toEqual({
uri: 'a/b/c',
url: 'git://a/b/c',
name: 'c',
org: 'b',
protocol: 'git',
});
const repo6 = RepositoryUtils.buildRepository('git@github.com:foo/bar.git');
expect(repo6).toEqual({
uri: 'github.com/foo/bar',
url: 'git@github.com:foo/bar.git',
name: 'bar',
protocol: 'ssh',
org: 'foo',
});
const repo7 = RepositoryUtils.buildRepository('ssh://git@github.com:foo/bar.git');
expect(repo7).toEqual({
uri: 'github.com/foo/bar',
url: 'ssh://git@github.com:foo/bar.git',
name: 'bar',
org: 'foo',
protocol: 'ssh',
});
const repo8 = RepositoryUtils.buildRepository('https://github.com/apache/sqoop/a/b');
expect(repo8).toEqual({
uri: 'github.com/apache_sqoop_a/b',
url: 'https://github.com/apache/sqoop/a/b',
name: 'b',
org: 'apache_sqoop_a',
protocol: 'https',
});
const repo9 = RepositoryUtils.buildRepository('https://github.com/apache/sqoop/tree/master');
expect(repo9).toEqual({
uri: 'github.com/apache_sqoop_tree/master',
url: 'https://github.com/apache/sqoop/tree/master',
name: 'master',
org: 'apache_sqoop_tree',
protocol: 'https',
});
});
test('Repository url parsing with non standard segments', () => {
const repo1 = RepositoryUtils.buildRepository('git://a/b/c/d');
expect(repo1).toEqual({
uri: 'a/b_c/d',
url: 'git://a/b/c/d',
name: 'd',
org: 'b_c',
protocol: 'git',
});
const repo2 = RepositoryUtils.buildRepository('git://a/b/c/d/e');
expect(repo2).toEqual({
uri: 'a/b_c_d/e',
url: 'git://a/b/c/d/e',
name: 'e',
org: 'b_c_d',
protocol: 'git',
});
const repo3 = RepositoryUtils.buildRepository('git://a');
expect(repo3).toEqual({
uri: 'a/_/_',
url: 'git://a',
name: '_',
protocol: 'git',
org: '_',
});
});
test('Repository url parsing with port', () => {
const repo1 = RepositoryUtils.buildRepository('ssh://mine@mydomain.com:27017/gitolite-admin');
expect(repo1).toEqual({
uri: 'mydomain.com:27017/_/gitolite-admin',
url: 'ssh://mine@mydomain.com:27017/gitolite-admin',
name: 'gitolite-admin',
org: '_',
protocol: 'ssh',
});
const repo2 = RepositoryUtils.buildRepository(
'ssh://mine@mydomain.com:27017/elastic/gitolite-admin'
);
expect(repo2).toEqual({
uri: 'mydomain.com:27017/elastic/gitolite-admin',
url: 'ssh://mine@mydomain.com:27017/elastic/gitolite-admin',
name: 'gitolite-admin',
protocol: 'ssh',
org: 'elastic',
});
});
test('Normalize repository index name', () => {
// index name with repository URI should be case insensitive even after attaching a hash at the end.
const indexName1 = RepositoryUtils.normalizeRepoUriToIndexName('github.com/elastic/Kibana');
const indexName2 = RepositoryUtils.normalizeRepoUriToIndexName('github.com/elastic/kibana');
expect(indexName1 === indexName2).toBeTruthy();
expect(indexName1).toEqual('github.com-elastic-kibana-7bf00473');
expect(indexName2).toEqual('github.com-elastic-kibana-7bf00473');
const indexName3 = RepositoryUtils.normalizeRepoUriToIndexName('github.com/elastic-kibana/code');
const indexName4 = RepositoryUtils.normalizeRepoUriToIndexName('github.com/elastic/kibana-code');
expect(indexName3 === indexName4).toBeFalsy();
// illegal characters should be replaced
const indexName5 = RepositoryUtils.normalizeRepoUriToIndexName(
'github.com:1234/elastic-kibana/code'
);
const indexName6 = RepositoryUtils.normalizeRepoUriToIndexName(
'github.com/1234?elastic/kibana-code'
);
expect(indexName5 === indexName6).toBeFalsy();
expect(indexName5).toEqual('github.com-1234-elastic-kibana-code-2a9788f8');
expect(indexName6).toEqual('github.com-1234-elastic-kibana-code-2b342309');
// the normalized index name must not exceed RepositoryUtils.MAX_NORMALIZED_NAME_LEN
const uniqNames = new Set<string>();
let repoName: string = '';
for (let i = 0; i < 1024; i++) {
const normalized = RepositoryUtils.normalizeRepoUriToIndexName(repoName);
expect(normalized.length).toBeLessThanOrEqual(RepositoryUtils.MAX_NORMALIZED_NAME_LEN);
if (
repoName.length >=
RepositoryUtils.MAX_NORMALIZED_NAME_LEN - RepositoryUtils.MAX_HASH_LEN - 1
) {
expect(normalized.length).toEqual(RepositoryUtils.MAX_NORMALIZED_NAME_LEN);
}
// a weak confliction test
expect(uniqNames.has(normalized)).toBeFalsy();
uniqNames.add(normalized);
repoName = repoName + String.fromCharCode(97 + (i % 26));
}
});
test('Parse repository uri', () => {
expect(RepositoryUtils.orgNameFromUri('github.com/elastic/kibana')).toEqual('elastic');
expect(RepositoryUtils.repoNameFromUri('github.com/elastic/kibana')).toEqual('kibana');
expect(RepositoryUtils.repoFullNameFromUri('github.com/elastic/kibana')).toEqual(
'elastic/kibana'
);
// For invalid repository uri
expect(() => {
RepositoryUtils.orgNameFromUri('foo/bar');
}).toThrowError('Invalid repository uri.');
expect(() => {
RepositoryUtils.repoNameFromUri('foo/bar');
}).toThrowError('Invalid repository uri.');
expect(() => {
RepositoryUtils.repoFullNameFromUri('foo/bar');
}).toThrowError('Invalid repository uri.');
});
test('Repository local path', () => {
expect(RepositoryUtils.repositoryLocalPath('/tmp', 'github.com/elastic/kibana')).toEqual(
'/tmp/github.com/elastic/kibana'
);
expect(RepositoryUtils.repositoryLocalPath('tmp', 'github.com/elastic/kibana')).toEqual(
'tmp/github.com/elastic/kibana'
);
});
test('Parse location to url', () => {
expect(
RepositoryUtils.locationToUrl({
uri: 'git://github.com/elastic/eui/blob/master/generator-eui/app/component.js',
range: {
start: {
line: 4,
character: 17,
},
end: {
line: 27,
character: 1,
},
},
})
).toEqual('/github.com/elastic/eui/blob/master/generator-eui/app/component.js!L5:17');
});
test('Get all files from a repository file tree', () => {
expect(
RepositoryUtils.getAllFiles({
name: 'foo',
type: FileTreeItemType.Directory,
path: '/foo',
children: [
{
name: 'bar',
type: FileTreeItemType.File,
path: '/foo/bar',
},
{
name: 'boo',
type: FileTreeItemType.File,
path: '/foo/boo',
},
],
childrenCount: 2,
})
).toEqual(['/foo/bar', '/foo/boo']);
});

View file

@ -1,146 +0,0 @@
/*
* 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 crypto from 'crypto';
import GitUrlParse from 'git-url-parse';
import path from 'path';
import { Location } from 'vscode-languageserver';
import { CloneProgress, FileTree, FileTreeItemType, Repository, RepositoryUri } from '../model';
import { parseLspUrl, toCanonicalUrl } from './uri_util';
export class RepositoryUtils {
// visible for tests
// the max length of the hash part used to normalize the repository URI
static readonly MAX_HASH_LEN = 8;
// as the normalized uri is used to create the name of code indices, and a valid index name cannot be longer than 255,
// see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html
// the utf8 encoded max normalized name length cannot be greater than (255 - len(index_prefix) - len(index_suffix))
// the index prefixes currently in use are '.code-document-', '.code-reference-', '.code-symbol-',
// the index suffix currently in use is `-${version}`,
// and also leave some room for future extensions
static readonly MAX_NORMALIZED_NAME_LEN = 200;
static readonly MAX_NORMALIZED_URI_LEN =
RepositoryUtils.MAX_NORMALIZED_NAME_LEN - RepositoryUtils.MAX_HASH_LEN - 1;
// Generate a Repository instance by parsing repository remote url
public static buildRepository(remoteUrl: string): Repository {
const repo = GitUrlParse(remoteUrl.trim());
let host = repo.source ? repo.source : '';
if (repo.port !== null) {
host = host + ':' + repo.port;
}
// Remove the leading `/` and tailing `.git` if necessary.
const pathname =
repo.pathname && repo.git_suffix
? repo.pathname.substr(1, repo.pathname.length - 5)
: repo.pathname.substr(1);
// The pathname should look like `a/b/c` now.
const segs = pathname.split('/').filter(s => s.length > 0);
const org = segs.length >= 2 ? segs.slice(0, segs.length - 1).join('_') : '_';
const name = segs.length >= 1 ? segs[segs.length - 1] : '_';
const uri: RepositoryUri = host ? `${host}/${org}/${name}` : repo.full_name;
return {
uri,
url: repo.href as string,
name,
org,
protocol: repo.protocol,
};
}
// From uri 'origin/org/name' to 'org'
public static orgNameFromUri(repoUri: RepositoryUri): string {
const segs = repoUri.split('/');
if (segs && segs.length === 3) {
return segs[1];
}
throw new Error('Invalid repository uri.');
}
// From uri 'origin/org/name' to 'name'
public static repoNameFromUri(repoUri: RepositoryUri): string {
const segs = repoUri.split('/');
if (segs && segs.length === 3) {
return segs[2];
}
throw new Error('Invalid repository uri.');
}
// From uri 'origin/org/name' to 'org/name'
public static repoFullNameFromUri(repoUri: RepositoryUri): string {
const segs = repoUri.split('/');
if (segs && segs.length === 3) {
return segs[1] + '/' + segs[2];
}
throw new Error('Invalid repository uri.');
}
// Return the local data path of a given repository.
public static repositoryLocalPath(repoPath: string, repoUri: RepositoryUri) {
return path.join(repoPath, repoUri);
}
public static normalizeRepoUriToIndexName(repoUri: RepositoryUri) {
// the return value should be <capped readable repository name>-<hash after lowercased uri>
// the repoUri is encoded in case there is non-ascii character
const lcRepoUri = encodeURI(repoUri).toLowerCase();
// Following the unit test here in Elasticsearch repository:
// https://github.com/elastic/elasticsearch/blob/c75773745cd048cd81a58c7d8a74272b45a25cc6/server/src/test/java/org/elasticsearch/cluster/metadata/MetaDataCreateIndexServiceTests.java#L404
// the hash is calculated from the lowercased repoUri to make it case insensitive
const hash = crypto
.createHash('md5')
.update(lcRepoUri)
.digest('hex')
.substring(0, RepositoryUtils.MAX_HASH_LEN);
// Invalid chars in index can be found here:
// https://github.com/elastic/elasticsearch/blob/237650e9c054149fd08213b38a81a3666c1868e5/server/src/main/java/org/elasticsearch/common/Strings.java#L376
const normalizedUri = lcRepoUri.replace(/[/\\?%*:|"<> ,]/g, '-');
const cappedNormalizedUri = normalizedUri.substr(0, RepositoryUtils.MAX_NORMALIZED_URI_LEN);
// Elasticsearch index name is case insensitive
return `${cappedNormalizedUri}-${hash}`.toLowerCase();
}
public static locationToUrl(loc: Location) {
const url = parseLspUrl(loc.uri);
const { repoUri, file, revision } = url;
if (repoUri && file && revision) {
return toCanonicalUrl({ repoUri, file, revision, position: loc.range.start });
}
return '';
}
public static getAllFiles(fileTree: FileTree): string[] {
if (!fileTree) {
return [];
}
let result: string[] = [];
switch (fileTree.type) {
case FileTreeItemType.File:
result.push(fileTree.path!);
break;
case FileTreeItemType.Directory:
for (const node of fileTree.children!) {
result = result.concat(RepositoryUtils.getAllFiles(node));
}
break;
default:
break;
}
return result;
}
public static hasFullyCloned(cloneProgress?: CloneProgress | null): boolean {
return !!cloneProgress && cloneProgress.isCloned !== undefined && cloneProgress.isCloned;
}
}

View file

@ -1,36 +0,0 @@
/*
* 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 { SymbolLocator } from '@elastic/lsp-extension';
import { TextDocumentPositionParams } from 'vscode-languageserver';
import {
Definition,
DocumentSymbolParams,
Hover,
Location,
SymbolInformation,
} from 'vscode-languageserver-types';
import { LspClient } from './lsp_client';
import { LspMethod } from './lsp_method';
export class TextDocumentMethods {
public documentSymbol: LspMethod<DocumentSymbolParams, SymbolInformation[]>;
public hover: LspMethod<TextDocumentPositionParams, Hover>;
public definition: LspMethod<TextDocumentPositionParams, Definition>;
public edefinition: LspMethod<TextDocumentPositionParams, SymbolLocator[]>;
public references: LspMethod<TextDocumentPositionParams, Location[]>;
private readonly client: LspClient;
constructor(client: LspClient) {
this.client = client;
this.documentSymbol = new LspMethod('textDocument/documentSymbol', this.client);
this.hover = new LspMethod('textDocument/hover', this.client);
this.definition = new LspMethod('textDocument/definition', this.client);
this.edefinition = new LspMethod('textDocument/edefinition', this.client);
this.references = new LspMethod('textDocument/references', this.client);
}
}

View file

@ -1,86 +0,0 @@
/*
* 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 { RepositoryUri } from '../model';
import { parseLspUrl, toCanonicalUrl, toRepoName, toRepoNameWithOrg } from './uri_util';
test('parse a complete uri', () => {
const fullUrl =
'git://github.com/Microsoft/vscode/blob/f2e49a2/src/vs/base/parts/ipc/test/node/ipc.net.test.ts';
const result = parseLspUrl(fullUrl);
expect(result).toEqual({
uri:
'/github.com/Microsoft/vscode/blob/f2e49a2/src/vs/base/parts/ipc/test/node/ipc.net.test.ts',
repoUri: 'github.com/Microsoft/vscode',
pathType: 'blob',
revision: 'f2e49a2',
file: 'src/vs/base/parts/ipc/test/node/ipc.net.test.ts',
schema: 'git:',
});
});
test('parseLspUrl a uri without schema', () => {
const url =
'github.com/Microsoft/vscode/blob/f2e49a2/src/vs/base/parts/ipc/test/node/ipc.net.test.ts';
const result = parseLspUrl(url);
expect(result).toEqual({
uri:
'/github.com/Microsoft/vscode/blob/f2e49a2/src/vs/base/parts/ipc/test/node/ipc.net.test.ts',
repoUri: 'github.com/Microsoft/vscode',
pathType: 'blob',
revision: 'f2e49a2',
file: 'src/vs/base/parts/ipc/test/node/ipc.net.test.ts',
});
});
test('parseLspUrl a tree uri', () => {
const uri = 'github.com/Microsoft/vscode/tree/head/src';
const result = parseLspUrl(uri);
expect(result).toEqual({
uri: '/github.com/Microsoft/vscode/tree/head/src',
repoUri: 'github.com/Microsoft/vscode',
pathType: 'tree',
revision: 'head',
file: 'src',
});
});
test('touri', () => {
const uri =
'git://github.com/Microsoft/vscode/blob/f2e49a2/src/vs/base/parts/ipc/test/node/ipc.net.test.ts';
const result = parseLspUrl(uri);
expect(result).toEqual({
uri:
'/github.com/Microsoft/vscode/blob/f2e49a2/src/vs/base/parts/ipc/test/node/ipc.net.test.ts',
repoUri: 'github.com/Microsoft/vscode',
pathType: 'blob',
revision: 'f2e49a2',
file: 'src/vs/base/parts/ipc/test/node/ipc.net.test.ts',
schema: 'git:',
});
const convertBack = toCanonicalUrl(result!);
expect(convertBack).toEqual(uri);
});
test('toRepoName', () => {
const uri: RepositoryUri = 'github.com/elastic/elasticsearch';
expect(toRepoName(uri)).toEqual('elasticsearch');
const invalidUri: RepositoryUri = 'github.com/elastic/elasticsearch/invalid';
expect(() => {
toRepoName(invalidUri);
}).toThrow();
});
test('toRepoNameWithOrg', () => {
const uri: RepositoryUri = 'github.com/elastic/elasticsearch';
expect(toRepoNameWithOrg(uri)).toEqual('elastic/elasticsearch');
const invalidUri: RepositoryUri = 'github.com/elastic/elasticsearch/invalid';
expect(() => {
toRepoNameWithOrg(invalidUri);
}).toThrow();
});

View file

@ -1,139 +0,0 @@
/*
* 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 { Uri } from 'monaco-editor';
import pathToRegexp from 'path-to-regexp';
import { Position } from 'vscode-languageserver-types';
import { RepositoryUri } from '../model';
import { MAIN, MAIN_ROOT } from '../public/components/routes';
const mainRe = pathToRegexp(MAIN);
const mainRootRe = pathToRegexp(MAIN_ROOT);
export interface ParsedUrl {
schema?: string;
uri?: string;
}
export interface CompleteParsedUrl extends ParsedUrl {
repoUri: string;
revision: string;
pathType?: string;
file?: string;
schema?: string;
position?: Position;
}
export function parseSchema(url: string): { uri: string; schema?: string } {
let [schema, uri] = url.toString().split('//');
if (!uri) {
uri = schema;
// @ts-ignore
schema = undefined;
}
if (!uri.startsWith('/')) {
uri = '/' + uri;
}
return { uri, schema };
}
export function parseGoto(goto: string): Position | undefined {
const regex = /L(\d+)(:\d+)?$/;
const m = regex.exec(goto);
if (m) {
const line = parseInt(m[1], 10);
let character = 0;
if (m[2]) {
character = parseInt(m[2].substring(1), 10);
}
return {
line,
character,
};
}
}
export function parseLspUrl(url: Uri | string): CompleteParsedUrl {
const { schema, uri } = parseSchema(url.toString());
const mainParsed = mainRe.exec(uri);
const mainRootParsed = mainRootRe.exec(uri);
if (mainParsed) {
const [resource, org, repo, pathType, revision, file, goto] = mainParsed.slice(1);
let position;
if (goto) {
position = parseGoto(goto);
}
return {
uri: uri.replace(goto, ''),
repoUri: `${resource}/${org}/${repo}`,
pathType,
revision,
file,
schema,
position,
};
} else if (mainRootParsed) {
const [resource, org, repo, pathType, revision] = mainRootParsed.slice(1);
return {
uri,
repoUri: `${resource}/${org}/${repo}`,
pathType,
revision,
schema,
};
} else {
throw new Error('invalid url ' + url);
}
}
/*
* From RepositoryUri to repository name.
* e.g. github.com/elastic/elasticsearch -> elasticsearch
*/
export function toRepoName(uri: RepositoryUri): string {
const segs = uri.split('/');
if (segs.length !== 3) {
throw new Error(`Invalid repository uri ${uri}`);
}
return segs[2];
}
/*
* From RepositoryUri to repository name with organization prefix.
* e.g. github.com/elastic/elasticsearch -> elastic/elasticsearch
*/
export function toRepoNameWithOrg(uri: RepositoryUri): string {
const segs = uri.split('/');
if (segs.length !== 3) {
throw new Error(`Invalid repository uri ${uri}`);
}
return `${segs[1]}/${segs[2]}`;
}
const compiled = pathToRegexp.compile(MAIN);
export function toCanonicalUrl(lspUrl: CompleteParsedUrl) {
const [resource, org, repo] = lspUrl.repoUri!.split('/');
if (!lspUrl.pathType) {
lspUrl.pathType = 'blob';
}
let goto;
if (lspUrl.position) {
goto = `!L${lspUrl.position.line + 1}:${lspUrl.position.character}`;
}
const data = { resource, org, repo, path: lspUrl.file, goto, ...lspUrl };
const uri = decodeURIComponent(compiled(data));
return lspUrl.schema ? `${lspUrl.schema}/${uri}` : uri;
}
export const decodeRevisionString = (revision: string) => {
return revision.replace(/:/g, '/');
};
export const encodeRevisionString = (revision: string) => {
return revision.replace(/\//g, ':');
};

View file

@ -1,97 +0,0 @@
/*
* 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 { RequestQuery, ResponseToolkit, RouteOptions, ServerRoute } from 'hapi';
import JoiNamespace from 'joi';
import { Legacy } from 'kibana';
import { resolve } from 'path';
import { CoreSetup } from 'src/core/server';
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;
export type ResponseToolkitFacade = ResponseToolkit;
export type RouteOptionsFacade = RouteOptions;
export type ServerFacade = Legacy.Server;
export type ServerRouteFacade = ServerRoute;
export const code = (kibana: any) =>
new kibana.Plugin({
require: ['kibana', 'elasticsearch'],
id: 'code',
configPrefix: 'xpack.code',
publicDir: resolve(__dirname, 'public'),
uiExports: {
app: {
title: APP_TITLE,
main: 'plugins/code/index',
euiIconType: 'codeApp',
},
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
injectDefaultVars(server: ServerFacade) {
const config = server.config();
return {
codeUiEnabled: config.get('xpack.code.ui.enabled'),
codeIntegrationsEnabled: config.get('xpack.code.integrations.enabled'),
codeDiffPageEnabled: config.get('xpack.code.diffPage.enabled'),
};
},
hacks: ['plugins/code/hacks/toggle_app_link_in_nav'],
savedObjectSchemas: {
[SAVED_OBJ_REPO]: {
isNamespaceAgnostic: false,
},
},
mappings,
},
config(Joi: typeof JoiNamespace) {
return Joi.object({
// Still keep this config item here for the injectDefaultVars
// in line 40 here.
ui: Joi.object({
enabled: Joi.boolean().default(true),
}).default(),
integrations: Joi.object({
enabled: Joi.boolean().default(false),
}).default(),
diffPage: Joi.object({
enabled: Joi.boolean().default(false),
}).default(),
enabled: Joi.boolean().default(true),
})
.default()
.unknown(true);
},
async init(server: ServerFacade) {
const initializerContext = server.newPlatform.setup.plugins.code as PluginSetupContract;
if (!initializerContext.legacy.config.ui.enabled) {
return;
}
const coreSetup = ({
http: { server },
} as any) as CoreSetup;
// Set up with the new platform plugin lifecycle API.
const plugin = codePlugin(initializerContext);
await plugin.setup(coreSetup, initializerContext.legacy.http);
// @ts-ignore
const kbnServer = this.kbnServer;
kbnServer.ready().then(async () => {
await plugin.start(coreSetup);
});
server.events.on('stop', async () => {
await plugin.stop();
});
},
});

View file

@ -1,7 +0,0 @@
{
"id": "code",
"version": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["elasticsearch"]
}

View file

@ -1,17 +0,0 @@
/*
* 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',
},
},
},
};

View file

@ -1,31 +0,0 @@
/*
* 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.
*/
export interface CommitInfo {
updated: Date;
message: string;
committer: string;
committerEmail?: string;
author: string;
authorEmail?: string;
id: string;
parents: string[];
treeId: string;
}
export interface ReferenceInfo {
name: string;
reference: string;
commit?: CommitInfo;
type: ReferenceType;
}
export enum ReferenceType {
BRANCH = 'BRANCH',
TAG = 'TAG',
REMOTE_BRANCH = 'REMOTE_BRANCH',
OTHER = 'OTHER',
}

View file

@ -1,19 +0,0 @@
/*
* 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.
*/
export type CodeLine = Token[];
export interface Token {
value: string;
scopes: string[];
range?: Range;
}
export interface Range {
start: number; // start pos in line
end: number;
pos?: number; // position in file
}

View file

@ -1,12 +0,0 @@
/*
* 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.
*/
export * from './highlight';
export * from './search';
export * from './repository';
export * from './task';
export * from './lsp';
export * from './workspace';

View file

@ -1,15 +0,0 @@
/*
* 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.
*/
export interface LspRequest {
method: string;
params: any;
documentUri?: string; // assert there is only one uri per request for now.
resolvedFilePath?: string;
workspacePath?: string;
workspaceRevision?: string;
isNotification?: boolean; // if this is a notification request that doesn't need response
}

View file

@ -1,182 +0,0 @@
/*
* 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 { IndexRequest } from './search';
import { CancellationReason } from '../server/queue/cancellation_service';
export type RepositoryUri = string;
export interface Repository {
/** In the form of git://github.com/lambdalab/lambdalab */
uri: RepositoryUri;
/** Original Clone Url */
url: string;
name?: string;
org?: string;
defaultBranch?: string;
revision?: string;
protocol?: string;
// The timestamp of next update for this repository.
nextUpdateTimestamp?: Date;
// The timestamp of next index for this repository.
nextIndexTimestamp?: Date;
// The current indexed revision in Elasticsearch.
indexedRevision?: string;
}
export interface RepositoryConfig {
uri: RepositoryUri;
disableGo?: boolean;
disableJava?: boolean;
disableTypescript?: boolean;
}
export interface FileTree {
name: string;
type: FileTreeItemType;
/** Full Path of the tree, don't need to be set by the server */
path?: string;
/**
* Children of the file tree, if it is undefined, then it's a file, if it is null, it means it is a
* directory and its children haven't been evaluated.
*/
children?: FileTree[];
/**
* count of children nodes for current node, use this for pagination
*/
childrenCount?: number;
sha1?: string;
/**
* current repo uri
*/
repoUri?: string;
}
export function sortFileTree(a: FileTree, b: FileTree) {
// consider Link and File are the same type, Submodule and Directory are the same type when sorting.
// Submodule and Directory are before Link and File
const types1 = [FileTreeItemType.File, FileTreeItemType.Link];
const types2 = [FileTreeItemType.Directory, FileTreeItemType.Submodule];
if (types1.includes(a.type)) {
if (types1.includes(b.type)) {
return a.name.localeCompare(b.name);
} else {
return 1;
}
} else if (types2.includes(a.type)) {
if (types2.includes(b.type)) {
return a.name.localeCompare(b.name);
} else {
return -1;
}
}
return a.name.localeCompare(b.name);
}
export enum FileTreeItemType {
File,
Directory,
Submodule,
Link,
}
export interface WorkerResult {
uri: string;
cancelled?: boolean;
cancelledReason?: CancellationReason;
}
// TODO(mengwei): create a AbstractGitWorkerResult since we now have an
// AbstractGitWorker now.
export interface CloneWorkerResult extends WorkerResult {
repo?: Repository;
}
export interface DeleteWorkerResult extends WorkerResult {
res: boolean;
}
export interface UpdateWorkerResult extends WorkerResult {
branch: string;
revision: string;
}
export enum IndexStatsKey {
Commit = 'commit-added-count',
CommitDeleted = 'commit-deleted-count',
File = 'file-added-count',
FileDeleted = 'file-deleted-count',
Symbol = 'symbol-added-count',
SymbolDeleted = 'symbol-deleted-count',
Reference = 'reference-added-count',
ReferenceDeleted = 'reference-deleted-count',
}
export type IndexStats = Map<IndexStatsKey, number>;
export interface IndexWorkerResult extends WorkerResult {
revision: string;
stats: IndexStats;
}
export enum WorkerReservedProgress {
INIT = 0,
COMPLETED = 100,
ERROR = -100,
TIMEOUT = -200,
}
export interface WorkerProgress {
// Job payload repository uri.
uri: string;
progress: number;
timestamp: Date;
revision?: string;
errorMessage?: string;
}
export interface CloneProgress {
isCloned?: boolean;
receivedObjects: number;
indexedObjects: number;
totalObjects: number;
localObjects: number;
totalDeltas: number;
indexedDeltas: number;
receivedBytes: number;
}
export interface CloneWorkerProgress extends WorkerProgress {
cloneProgress?: CloneProgress;
}
export interface IndexProgress {
type: string;
total: number;
success: number;
fail: number;
percentage: number;
checkpoint?: IndexRequest;
}
export interface IndexWorkerProgress extends WorkerProgress {
// Index progress for LSP indexing.
indexProgress?: IndexProgress;
// Index progress for commit indexing.
commitIndexProgress?: IndexProgress;
}
export enum RepoState {
CLONING,
UPDATING,
DELETING,
INDEXING,
READY,
CLONE_ERROR,
DELETE_ERROR,
INDEX_ERROR,
UNKNOWN,
}

View file

@ -1,294 +0,0 @@
/*
* 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 { DetailSymbolInformation } from '@elastic/lsp-extension';
import { IRange } from 'monaco-editor';
import { DiffKind } from '../common/git_diff';
import { Repository } from '../model';
import { RepositoryUri } from './repository';
export interface Document {
repoUri: RepositoryUri;
path: string;
content: string;
qnames: string[];
language?: string;
sha1?: string;
}
export interface Commit {
repoUri: RepositoryUri;
id: string;
message: string;
body: string;
date: Date;
parents: string[];
author: {
name: string;
email: string;
};
committer: {
name: string;
email: string;
};
}
// The base interface of indexer requests
export interface IndexRequest {
repoUri: RepositoryUri;
}
export enum IndexerType {
LSP = 'LSP',
LSP_INC = 'LSP_INCREMENTAL',
COMMIT = 'COMMIT',
COMMIT_INC = 'COMMIT_INCREMENTAL',
REPOSITORY = 'REPOSITORY',
UNKNOWN = 'UNKNOWN',
}
// The request for LspIndexer
export interface LspIndexRequest extends IndexRequest {
filePath: string; // The file path within the repository
revision: string; // The revision of the current repository
}
// The request for CommitIndexer
export interface CommitIndexRequest extends IndexRequest {
// The git ref as the starting point for the entire commit index job.
revision: string;
commit: Commit;
}
export interface LspIncIndexRequest extends LspIndexRequest {
originPath?: string;
kind: DiffKind;
originRevision: string;
}
// The request for RepositoryIndexer
export interface RepositoryIndexRequest extends IndexRequest {
repoUri: RepositoryUri;
}
// The base interface of any kind of search requests.
export interface SearchRequest {
query: string;
page: number;
resultsPerPage?: number;
}
export interface RepositorySearchRequest extends SearchRequest {
query: string;
repoScope?: RepositoryUri[];
}
export interface DocumentSearchRequest extends SearchRequest {
// repoFilters is used for search within these repos but return
// search stats across all repositories.
repoFilters?: string[];
// repoScope hard limit the search coverage only to these repositories.
repoScope?: RepositoryUri[];
langFilters?: string[];
}
export interface CommitSearchRequest extends SearchRequest {
// repoFilters is used for search within these repos but return
// search stats across all repositories.
repoFilters?: string[];
// repoScope hard limit the search coverage only to these repositories.
repoScope?: RepositoryUri[];
}
export interface SymbolSearchRequest extends SearchRequest {
query: string;
repoScope?: RepositoryUri[];
}
export interface StackTraceItem {
filePath: string;
lineNumStart: number;
lineNumEnd?: number;
// We could add more in here in the future, e.g. qname.
}
export interface StackTraceSnippetsRequest {
repoUris: RepositoryUri[];
revision?: string; // Not used for now.
stacktraceItems: StackTraceItem[];
}
export interface ResolveSnippetsRequest {
repoUris: RepositoryUri[];
revision?: string; // Not used for now.
filePath: string;
lineNumStart: number;
lineNumEnd?: number;
}
// The base interface of any kind of search result.
export interface SearchResult {
total: number;
took: number;
}
export interface RepositorySearchResult extends SearchResult {
repositories: Repository[];
from?: number;
page?: number;
totalPage?: number;
}
export interface SymbolSearchResult extends SearchResult {
// TODO: we might need an additional data structure for symbol search result.
symbols: DetailSymbolInformation[];
}
// All the interfaces for search page
// The item of the search result stats. e.g. Typescript -> 123
export interface SearchResultStatsItem {
name: string;
value: number;
}
export interface SearchResultStats {
total: number; // Total number of results
from: number; // The beginning of the result range
to: number; // The end of the result range
page: number; // The page number
totalPage: number; // The total number of pages
repoStats: SearchResultStatsItem[];
languageStats: SearchResultStatsItem[];
}
export interface CompositeSourceContent {
content: string;
lineMapping: string[];
ranges: IRange[];
}
export interface SearchResultItem {
uri: string;
hits: number;
filePath: string;
language: string;
compositeContent: CompositeSourceContent;
}
export interface DocumentSearchResult extends SearchResult {
query: string;
from?: number;
page?: number;
totalPage?: number;
stats?: SearchResultStats;
results?: SearchResultItem[];
repoAggregations?: any[];
langAggregations?: any[];
}
export type CommitSearchResultItem = Commit;
export interface CommitSearchResult extends DocumentSearchResult {
commits: CommitSearchResultItem[];
}
export interface IntegrationsSearchResult extends SearchResult {
results?: SearchResultItem[];
fallback: boolean;
}
export interface SourceLocation {
line: number;
column: number;
offset: number;
}
export interface SourceRange {
startLoc: SourceLocation;
endLoc: SourceLocation;
}
export interface SourceHit {
range: SourceRange;
score: number;
term: string;
}
export enum SearchScope {
DEFAULT = 'default', // Search everything
SYMBOL = 'symbol', // Only search symbols
REPOSITORY = 'repository', // Only search repositories
FILE = 'file', // Only search files
}
export interface SearchOptions {
repoScope: Repository[];
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,
};
}

View file

@ -1,25 +0,0 @@
/*
* 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 { RepositoryUri } from './repository';
/** Time consuming task that should be queued and executed seperately */
export interface Task {
repoUri: RepositoryUri;
type: TaskType;
/** Percentage of the task, 100 means task completed */
progress: number;
/** Revision of the repo that the task run on. May only apply to Index task */
revision?: string;
}
export enum TaskType {
Import,
Update,
Delete,
Index,
}

View file

@ -1,21 +0,0 @@
/*
* 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.
*/
export interface Repo {
url: string;
path: string;
language: string;
}
export interface TestConfig {
repos: Repo[];
}
export enum RequestType {
INITIALIZE,
HOVER,
FULL,
}

View file

@ -1,22 +0,0 @@
/*
* 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.
*/
export enum CodeUsageMetrics {
ENABLED = 'enabled',
REPOSITORIES = 'repositories',
LANGUAGE_SERVERS = 'langserver',
}
export enum CodeUIUsageMetrics {
ADMIN_PAGE_LOAD_COUNT = 'adminPageLoadCount',
SOURCE_VIEW_PAGE_LOAD_COUNT = 'sourceViewPageLoadCount',
SEARCH_PAGE_LOAD_COUNT = 'searchPageLoadCount',
FILE_TREE_CLICK_COUNT = 'fileTreeClickCount',
BREADCRUMB_CLICK_COUNT = 'breadcrumbClickCount',
LINE_NUMBER_CLICK_COUNT = 'lineNumberClickCount',
STRUCTURE_TREE_CLICK_COUNT = 'structureTreeClickCount',
LSP_DATA_AVAILABLE_PAGE_VIEW_COUNT = 'lspDataAvailablePageViewCount',
}

View file

@ -1,15 +0,0 @@
/*
* 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.
*/
export type RepoCmd = string | string[];
export interface RepoConfig {
repo: string;
init: RepoCmd;
}
export interface RepoConfigs {
[repoUri: string]: RepoConfig;
}

View file

@ -1,18 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
import { GitBlame } from '../../common/git_blame';
export interface LoadBlamePayload {
repoUri: string;
revision: string;
path: string;
}
export const loadBlame = createAction<LoadBlamePayload>('LOAD BLAME');
export const loadBlameSuccess = createAction<GitBlame[]>('LOAD BLAME SUCCESS');
export const loadBlameFailed = createAction<Error>('LOAD BLAME FAILED');

View file

@ -1,12 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
import { CommitDiff } from '../../common/git_diff';
export const loadCommit = createAction<string>('LOAD COMMIT');
export const loadCommitSuccess = createAction<CommitDiff>('LOAD COMMIT SUCCESS');
export const loadCommitFailed = createAction<Error>('LOAD COMMIT FAILED');

View file

@ -1,41 +0,0 @@
/*
* 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 { Range } from 'monaco-editor';
import { createAction } from 'redux-actions';
import { Hover, Position, TextDocumentPositionParams } from 'vscode-languageserver';
export interface PanelResults {
repos: GroupedRepoResults[];
title: string;
}
export interface GroupedRepoResults {
repo: string;
files: GroupedFileResults[];
}
export interface GroupedFileResults {
uri: string;
file: string;
language: string;
code: string;
lineNumbers: string[];
repo: string;
revision: string;
highlights: Range[];
}
export const findReferences = createAction<TextDocumentPositionParams>('FIND REFERENCES');
export const findReferencesSuccess = createAction<PanelResults>('FIND REFERENCES SUCCESS');
export const findReferencesFailed = createAction<Error>('FIND REFERENCES ERROR');
export const closePanel = createAction<boolean>('CLOSE PANEL');
export const hoverResult = createAction<Hover>('HOVER RESULT');
export const revealPosition = createAction<Position | undefined>('REVEAL POSITION');
export const findDefinitions = createAction<TextDocumentPositionParams>('FIND DEFINITIONS');
export const findDefinitionsSuccess = createAction<PanelResults>('FIND DEFINITIONS SUCCESS');
export const findDefinitionsFailed = createAction<Error>('FIND DEFINITIONS ERROR');

View file

@ -1,86 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
import { FileTree } from '../../model';
import { CommitInfo, ReferenceInfo } from '../../model/commit';
export interface FetchRepoPayload {
uri: string;
}
export interface FetchRepoPayloadWithRevision extends FetchRepoPayload {
revision: string;
}
export interface FetchFilePayload extends FetchRepoPayloadWithRevision {
path: string;
}
export interface FetchRepoTreePayload extends FetchFilePayload {
limit?: number;
parents?: boolean;
isDir: boolean;
}
export interface FetchFileResponse {
payload: FetchFilePayload;
isNotFound?: boolean;
content?: string;
lang?: string;
isImage?: boolean;
isUnsupported?: boolean;
isOversize?: boolean;
url?: string;
}
export interface RepoTreePayload {
tree: FileTree;
path: string;
withParents: boolean | undefined;
revision: string;
}
export interface TreeCommitPayload {
path: string;
commits: CommitInfo[];
append?: boolean;
}
export const fetchRootRepoTree = createAction<FetchRepoPayloadWithRevision>('FETCH ROOT REPO TREE');
export const fetchRootRepoTreeSuccess = createAction<{ tree: FileTree; revision: string }>(
'FETCH ROOT REPO TREE SUCCESS'
);
export const fetchRootRepoTreeFailed = createAction<Error>('FETCH ROOT REPO TREE FAILED');
export const fetchRepoTree = createAction<FetchRepoTreePayload>('FETCH REPO TREE');
export const fetchRepoTreeSuccess = createAction<RepoTreePayload>('FETCH REPO TREE SUCCESS');
export const fetchRepoTreeFailed = createAction<Error>('FETCH REPO TREE FAILED');
export const fetchRepoBranches = createAction<FetchRepoPayload>('FETCH REPO BRANCHES');
export const fetchRepoBranchesSuccess = createAction<ReferenceInfo[]>(
'FETCH REPO BRANCHES SUCCESS'
);
export const fetchRepoBranchesFailed = createAction<Error>('FETCH REPO BRANCHES FAILED');
export const fetchRepoCommits = createAction<FetchRepoPayloadWithRevision>('FETCH REPO COMMITS');
export const fetchRepoCommitsSuccess = createAction<CommitInfo[]>('FETCH REPO COMMITS SUCCESS');
export const fetchRepoCommitsFailed = createAction<Error>('FETCH REPO COMMITS FAILED');
export const fetchFile = createAction<FetchFilePayload>('FETCH FILE');
export const fetchFileSuccess = createAction<FetchFileResponse>('FETCH FILE SUCCESS');
export const fetchFileFailed = createAction<Error>('FETCH FILE ERROR');
export const fetchDirectory = createAction<FetchRepoTreePayload>('FETCH REPO DIR');
export const fetchDirectorySuccess = createAction<FileTree>('FETCH REPO DIR SUCCESS');
export const fetchDirectoryFailed = createAction<Error>('FETCH REPO DIR FAILED');
export const setNotFound = createAction<boolean>('SET FILE NOT FOUND');
export const dirNotFound = createAction<string>('DIR NOT FOUND');
export const fetchTreeCommits = createAction<FetchFilePayload>('FETCH TREE COMMITS');
export const fetchTreeCommitsSuccess = createAction<TreeCommitPayload>(
'FETCH TREE COMMITS SUCCESS'
);
export const fetchTreeCommitsFailed = createAction<Error>('FETCH TREE COMMITS FAILED');
export const fetchMoreCommits = createAction<string>('FETCH MORE COMMITS');

View file

@ -1,31 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
export * from './repository';
export * from './search';
export * from './file';
export * from './structure';
export * from './editor';
export * from './commit';
export * from './status';
export * from './project_config';
export * from './shortcuts';
export * from './route';
export interface Match {
isExact?: boolean;
params: { [key: string]: string };
path: string;
url: string;
location: Location;
}
export const routeChange = createAction<Match>('CODE SEARCH ROUTE CHANGE');
export const checkSetupSuccess = createAction('SETUP CHECK SUCCESS');
export const checkSetupFailed = createAction('SETUP CHECK FAILED');

View file

@ -1,26 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
import { LanguageServer } from '../../common/language_server';
export const loadLanguageServers = createAction('LOAD LANGUAGE SERVERS');
export const loadLanguageServersSuccess = createAction<LanguageServer[]>(
'LOAD LANGUAGE SERVERS SUCCESS'
);
export const loadLanguageServersFailed = createAction<Error>('LOAD LANGUAGE SERVERS FAILED');
export const requestInstallLanguageServer = createAction('REQUEST INSTALL LANGUAGE SERVERS');
export const requestInstallLanguageServerSuccess = createAction<string>(
'REQUEST INSTALL LANGUAGE SERVERS SUCCESS'
);
export const requestInstallLanguageServerFailed = createAction<Error>(
'REQUEST INSTALL LANGUAGE SERVERS FAILED'
);
export const installLanguageServerSuccess = createAction<any>('INSTALL LANGUAGE SERVERS SUCCESS');
export const languageServerInitializing = createAction('LANGUAGE SERVER INITIALIZING');

View file

@ -1,16 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
import { RepositoryConfig } from '../../model';
export interface RepoLangserverConfigs {
[key: string]: RepositoryConfig;
}
export const loadConfigs = createAction('LOAD CONFIGS');
export const loadConfigsSuccess = createAction<RepoLangserverConfigs>('LOAD CONFIGS SUCCESS');
export const loadConfigsFailed = createAction<Error>('LOAD CONFIGS FAILED');

View file

@ -1,11 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
export const loadRecentProjects = createAction('LOAD RECENT PROJECTS');
export const loadRecentProjectsSuccess = createAction<any>('LOAD RECENT PROJECTS SUCCESS');
export const loadRecentProjectsFailed = createAction<Error>('LOAD RECENT PROJECTS FAILED');

View file

@ -1,47 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
import { Repository, RepositoryConfig } from '../../model';
import { RepoConfigs } from '../../model/workspace';
export interface RepoConfigPayload {
repoUri: string;
config: RepositoryConfig;
}
export const fetchRepos = createAction('FETCH REPOS');
export const fetchReposSuccess = createAction<Repository[]>('FETCH REPOS SUCCESS');
export const fetchReposFailed = createAction<Error>('FETCH REPOS FAILED');
export const deleteRepo = createAction<string>('DELETE REPOS');
export const deleteRepoSuccess = createAction<string>('DELETE REPOS SUCCESS');
export const deleteRepoFinished = createAction<string>('DELETE REPOS FINISHED');
export const deleteRepoFailed = createAction<Error>('DELETE REPOS FAILED');
export const indexRepo = createAction<string>('INDEX REPOS');
export const indexRepoSuccess = createAction<string>('INDEX REPOS SUCCESS');
export const indexRepoFailed = createAction<Error>('INDEX REPOS FAILED');
export const importRepo = createAction<string>('IMPORT REPO');
export const importRepoSuccess = createAction<Repository>('IMPORT REPO SUCCESS');
export const importRepoFailed = createAction<Error>('IMPORT REPO FAILED');
export const closeToast = createAction('CLOSE TOAST');
export const fetchRepoConfigs = createAction('FETCH REPO CONFIGS');
export const fetchRepoConfigSuccess = createAction<RepoConfigs>('FETCH REPO CONFIGS SUCCESS');
export const fetchRepoConfigFailed = createAction<Error>('FETCH REPO CONFIGS FAILED');
export const initRepoCommand = createAction<string>('INIT REPO CMD');
export const gotoRepo = createAction<string>('GOTO REPO');
export const gotoRepoFailed = createAction('GOTO REPO FAILED');
export const switchLanguageServer = createAction<RepoConfigPayload>('SWITCH LANGUAGE SERVER');
export const switchLanguageServerSuccess = createAction('SWITCH LANGUAGE SERVER SUCCESS');
export const switchLanguageServerFailed = createAction<Error>('SWITCH LANGUAGE SERVER FAILED');

View file

@ -1,12 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
export const routePathChange = createAction('ROUTE PATH CHANGE');
export const repoChange = createAction<string>('REPOSITORY CHANGE');
export const revisionChange = createAction('REVISION CHANGE');
export const filePathChange = createAction('FILE PATH CHANGE');

View file

@ -1,60 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
import {
DocumentSearchResult,
Repository,
SearchOptions,
SearchScope,
RepositorySearchResult,
} from '../../model';
export interface DocumentSearchPayload {
query: string;
page?: string;
languages?: string;
repositories?: string;
repoScope?: string;
}
export interface RepositorySearchPayload {
query: string;
}
// For document search page
export const documentSearch = createAction<DocumentSearchPayload>('DOCUMENT SEARCH');
export const documentSearchSuccess = createAction<DocumentSearchResult>('DOCUMENT SEARCH SUCCESS');
export const documentSearchFailed = createAction<Error>('DOCUMENT SEARCH FAILED');
// For repository search page
export const repositorySearch = createAction<RepositorySearchPayload>('REPOSITORY SEARCH');
export const repositorySearchSuccess = createAction<RepositorySearchResult>(
'REPOSITORY SEARCH SUCCESS'
);
export const repositorySearchFailed = createAction<Error>('REPOSITORY SEARCH FAILED');
export const changeSearchScope = createAction<SearchScope>('CHANGE SEARCH SCOPE');
export const suggestionSearch = createAction<string>('SUGGESTION SEARCH');
// For repository search typeahead
export const repositorySearchQueryChanged = createAction<RepositorySearchPayload>(
'REPOSITORY SEARCH QUERY CHANGED'
);
export const repositoryTypeaheadSearchSuccess = createAction<string>('REPOSITORY SEARCH SUCCESS');
export const repositoryTypeaheadSearchFailed = createAction<string>('REPOSITORY SEARCH FAILED');
export const saveSearchOptions = createAction<SearchOptions>('SAVE SEARCH OPTIONS');
export const turnOnDefaultRepoScope = createAction<Repository>('TURN ON DEFAULT REPO SCOPE');
export const turnOffDefaultRepoScope = createAction('TURN OFF DEFAULT REPO SCOPE');
export const searchReposForScope = createAction<RepositorySearchPayload>('SEARCH REPOS FOR SCOPE');
export const searchReposForScopeSuccess = createAction<RepositorySearchResult>(
'SEARCH REPOS FOR SCOPE SUCCESS'
);
export const searchReposForScopeFailed = createAction<Error>('SEARCH REPOS FOR SCOPE FAILED');

View file

@ -1,13 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
import { HotKey } from '../components/shortcuts';
export const registerShortcut = createAction<HotKey>('REGISTER SHORTCUT');
export const unregisterShortcut = createAction<HotKey>('UNREGISTER SHORTCUT');
export const toggleHelp = createAction<boolean | null>('TOGGLE SHORTCUTS HELP');

View file

@ -1,67 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
import {
CloneProgress,
CloneWorkerProgress,
Repository,
RepositoryUri,
RepoState,
WorkerProgress,
} from '../../model';
import { FetchFilePayload } from './file';
import { StatusReport } from '../../common/repo_file_status';
export interface RepoStatus {
uri: string;
progress: number;
cloneProgress?: CloneProgress;
timestamp?: Date;
state?: RepoState;
errorMessage?: string;
}
export interface StatusSuccessPayload {
[key: string]: {
gitStatus: CloneWorkerProgress | null;
indexStatus: WorkerProgress | null;
deleteStatus: WorkerProgress | null;
};
}
export const loadStatus = createAction<string>('LOAD STATUS');
export const loadStatusSuccess = createAction<StatusSuccessPayload>('LOAD STATUS SUCCESS');
export const loadStatusFailed = createAction<Error>('LOAD STATUS FAILED');
export const loadRepo = createAction<string>('LOAD REPO');
export const loadRepoSuccess = createAction<Repository[]>('LOAD REPO SUCCESS');
export const loadRepoFailed = createAction<Error>('LOAD REPO FAILED');
export const updateCloneProgress = createAction<RepoStatus>('UPDATE CLONE PROGRESS');
export const updateIndexProgress = createAction<RepoStatus>('UPDATE INDEX PROGRESS');
export const updateDeleteProgress = createAction<RepoStatus>('UPDATE DELETE PROGRESS');
export const pollRepoCloneStatusStart = createAction<string>('POLL CLONE STATUS START');
export const pollRepoIndexStatusStart = createAction<string>('POLL INDEX STATUS START');
export const pollRepoDeleteStatusStart = createAction<string>('POLL DELETE STATUS START');
export const pollRepoCloneStatusStop = createAction<RepositoryUri>('POLL CLONE STATUS STOP');
export const pollRepoIndexStatusStop = createAction<RepositoryUri>('POLL INDEX STATUS STOP');
export const pollRepoDeleteStatusStop = createAction<RepositoryUri>('POLL DELETE STATUS STOP');
export const FetchRepoFileStatus = createAction<FetchFilePayload>('FETCH REPO FILE STATUS');
export const FetchRepoFileStatusSuccess = createAction<{
path: FetchFilePayload;
statusReport: StatusReport;
}>('FETCH REPO FILE STATUS SUCCESS');
export const FetchRepoFileStatusFailed = createAction<any>('FETCH REPO FILE STATUS FAILED');
export const StatusChanged = createAction<{
prevStatus: StatusReport;
currentStatus: StatusReport;
}>('STATUS CHANGED');

View file

@ -1,26 +0,0 @@
/*
* 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 { createAction } from 'redux-actions';
import { DocumentSymbol } from 'vscode-languageserver-types';
export interface SymbolWithMembers extends DocumentSymbol {
members?: SymbolWithMembers[];
path?: string;
}
export interface SymbolsPayload {
path: string;
data: DocumentSymbol[];
structureTree: SymbolWithMembers[];
}
export const loadStructure = createAction<string>('LOAD STRUCTURE');
export const loadStructureSuccess = createAction<SymbolsPayload>('LOAD STRUCTURE SUCCESS');
export const loadStructureFailed = createAction<Error>('LOAD STRUCTURE FAILED');
export const openSymbolPath = createAction<string>('OPEN SYMBOL PATH');
export const closeSymbolPath = createAction<string>('CLOSE SYMBOL PATH');

View file

@ -1,77 +0,0 @@
/*
* 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 React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { Provider } from 'react-redux';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';
import moment from 'moment';
import { CoreStart } from 'src/core/public';
import 'ui/autoload/all';
import 'ui/autoload/styles';
import chrome from 'ui/chrome';
// @ts-ignore
import { uiModules } from 'ui/modules';
import { APP_TITLE } from '../common/constants';
import { App } from './components/app';
import { HelpMenu } from './components/help_menu';
import { store } from './stores';
export function startApp(coreStart: CoreStart) {
// `getInjected` is not currently available in new platform `coreStart.chrome`
if (chrome.getInjected('codeUiEnabled')) {
// TODO the entire Kibana uses moment, we might need to move it to a more common place
moment.locale(i18n.getLocale());
const app = uiModules.get('apps/code');
app.config(($locationProvider: any) => {
$locationProvider.html5Mode({
enabled: false,
requireBase: false,
rewriteLinks: false,
});
});
app.config((stateManagementConfigProvider: any) => stateManagementConfigProvider.disable());
function RootController($scope: any, $element: any, $http: any) {
const domNode = $element[0];
// render react to DOM
render(
<I18nProvider>
<Provider store={store}>
<App />
</Provider>
</I18nProvider>,
domNode
);
// unmount react on controller destroy
$scope.$on('$destroy', () => {
unmountComponentAtNode(domNode);
});
}
// `setRootController` is not available now in `coreStart.chrome`
chrome.setRootController('code', RootController);
coreStart.chrome.setBreadcrumbs([
{
text: APP_TITLE,
href: '#/',
},
]);
coreStart.chrome.setHelpExtension(domNode => {
render(<HelpMenu />, domNode);
return () => {
unmountComponentAtNode(domNode);
};
});
}
}

View file

@ -1,66 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { ReactNode } from 'react';
import { SearchScope } from '../../model';
export enum PathTypes {
blob = 'blob',
tree = 'tree',
blame = 'blame',
commits = 'commits',
}
export const SearchScopeText = {
[SearchScope.DEFAULT]: i18n.translate('xpack.code.searchScope.defaultDropDownOptionLabel', {
defaultMessage: 'Search Everything',
}),
[SearchScope.REPOSITORY]: i18n.translate('xpack.code.searchScope.repositoryDropDownOptionLabel', {
defaultMessage: 'Search Repositories',
}),
[SearchScope.SYMBOL]: i18n.translate('xpack.code.searchScope.symbolDropDownOptionLabel', {
defaultMessage: 'Search Symbols',
}),
[SearchScope.FILE]: i18n.translate('xpack.code.searchScope.fileDropDownOptionLabel', {
defaultMessage: 'Search Files',
}),
};
export const SearchScopePlaceholderText = {
[SearchScope.DEFAULT]: i18n.translate('xpack.code.searchScope.defaultPlaceholder', {
defaultMessage: 'Type to find anything',
}),
[SearchScope.REPOSITORY]: i18n.translate('xpack.code.searchScope.repositoryPlaceholder', {
defaultMessage: 'Type to find repositories',
}),
[SearchScope.SYMBOL]: i18n.translate('xpack.code.searchScope.symbolPlaceholder', {
defaultMessage: 'Type to find symbols',
}),
[SearchScope.FILE]: i18n.translate('xpack.code.searchScope.filePlaceholder', {
defaultMessage: 'Type to find files',
}),
};
export interface MainRouteParams {
path: string;
repo: string;
resource: string;
org: string;
revision: string;
pathType: PathTypes;
goto?: string;
}
export interface EuiSideNavItem {
id: string;
name: string;
isSelected?: boolean;
renderItem?: () => ReactNode;
forceOpen?: boolean;
items?: EuiSideNavItem[];
onClick: () => void;
}

View file

@ -1,170 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { parse as parseQuery } from 'querystring';
import React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import url from 'url';
import { EuiTab, EuiTabs } from '@elastic/eui';
import { Repository, SearchOptions, SearchScope } from '../../../model';
import { changeSearchScope } from '../../actions';
import { RootState } from '../../reducers';
import { SearchBar } from '../search_bar';
import { EmptyProject } from './empty_project';
import { LanguageSeverTab } from './language_server_tab';
import { ProjectTab } from './project_tab';
import { METRIC_TYPE, trackCodeUiMetric } from '../../services/ui_metric';
import { CodeUIUsageMetrics } from '../../../model/usage_telemetry_metrics';
enum AdminTabs {
projects = '0',
languageServers = '1',
}
interface Props extends RouteComponentProps {
searchOptions: SearchOptions;
query: string;
onSearchScopeChanged: (s: SearchScope) => void;
repositories: Repository[];
repositoryLoading: boolean;
}
interface State {
tab: AdminTabs;
}
class AdminPage extends React.PureComponent<Props, State> {
public static getDerivedStateFromProps(props: Props) {
const getTab = () => {
const { search } = props.location;
let qs = search;
if (search.charAt(0) === '?') {
qs = search.substr(1);
}
return parseQuery(qs).tab || AdminTabs.projects;
};
return {
tab: getTab() as AdminTabs,
};
}
public searchBar: any = null;
public tabs = [
{
id: AdminTabs.projects,
name: i18n.translate('xpack.code.adminPage.repoTabLabel', { defaultMessage: 'Repositories' }),
disabled: false,
},
{
id: AdminTabs.languageServers,
name: i18n.translate('xpack.code.adminPage.langserverTabLabel', {
defaultMessage: 'Language servers',
}),
disabled: false,
},
];
constructor(props: Props) {
super(props);
const getTab = () => {
const { search } = props.location;
let qs = search;
if (search.charAt(0) === '?') {
qs = search.substr(1);
}
return parseQuery(qs).tab || AdminTabs.projects;
};
this.state = {
tab: getTab() as AdminTabs,
};
}
componentDidMount() {
// track admin page load count
trackCodeUiMetric(METRIC_TYPE.LOADED, CodeUIUsageMetrics.ADMIN_PAGE_LOAD_COUNT);
}
public getAdminTabClickHandler = (tab: AdminTabs) => () => {
this.setState({ tab });
this.props.history.push(url.format({ pathname: '/admin', query: { tab } }));
};
public renderTabs() {
const tabs = this.tabs.map(tab => (
<EuiTab
onClick={this.getAdminTabClickHandler(tab.id)}
isSelected={tab.id === this.state.tab}
disabled={tab.disabled}
key={tab.id}
>
{tab.name}
</EuiTab>
));
return <EuiTabs>{tabs}</EuiTabs>;
}
public filterRepos = () => {
return this.props.repositories;
};
public renderTabContent = () => {
switch (this.state.tab) {
case AdminTabs.languageServers: {
return <LanguageSeverTab />;
}
case AdminTabs.projects:
default: {
const repositoriesCount = this.props.repositories.length;
const showEmpty = repositoriesCount === 0 && !this.props.repositoryLoading;
if (showEmpty) {
return <EmptyProject />;
}
return <ProjectTab />;
}
}
};
public render() {
return (
<main className="codeContainer__root">
<div className="codeContainer__rootInner">
<div className="codeContainer__adminWrapper">
<SearchBar
searchOptions={this.props.searchOptions}
query={this.props.query}
onSearchScopeChanged={this.props.onSearchScopeChanged}
enableSubmitWhenOptionsChanged={false}
ref={element => (this.searchBar = element)}
/>
<div className="codeContainer__adminMain">
{this.renderTabs()}
{this.renderTabContent()}
</div>
</div>
</div>
</main>
);
}
}
const mapStateToProps = (state: RootState) => ({
...state.search,
repositories: state.repositoryManagement.repositories,
repositoryLoading: state.repositoryManagement.loading,
});
const mapDispatchToProps = {
onSearchScopeChanged: changeSearchScope,
};
export const Admin = withRouter(
connect(
mapStateToProps,
mapDispatchToProps
)(AdminPage)
);

View file

@ -1,56 +0,0 @@
/*
* 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 { get } from 'lodash';
import React from 'react';
import { Link } from 'react-router-dom';
import { EuiButton, EuiFlexGroup, EuiSpacer, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { npStart } from 'ui/new_platform';
import { ImportProject } from './import_project';
export const EmptyProject = () => {
const isAdmin = get(npStart.core.application.capabilities, 'code.admin') as boolean;
return (
<div className="codeTab__projects">
<EuiSpacer size="xl" />
<div className="codeTab__projects--emptyHeader">
<EuiText>
<h1>
<FormattedMessage
id="xpack.code.adminPage.repoTab.emptyRepo.noRepositoryText"
defaultMessage="You don't have any repos yet"
/>
</h1>
</EuiText>
<EuiText color="subdued">
{isAdmin && (
<p>
<FormattedMessage
id="xpack.code.adminPage.repoTab.emptyRepo.importFirstRepositoryText"
defaultMessage="Let's import your first one"
/>
</p>
)}
</EuiText>
</div>
{isAdmin && <ImportProject />}
<EuiSpacer />
<EuiFlexGroup justifyContent="center">
<Link to="/setup-guide">
<EuiButton>
<FormattedMessage
id="xpack.code.adminPage.repoTab.emptyRepo.viewSetupGuideButtonLabel"
defaultMessage="View the Setup Guide"
/>
</EuiButton>
</Link>
</EuiFlexGroup>
</div>
);
};

View file

@ -1,134 +0,0 @@
/*
* 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 {
EuiButton,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiGlobalToastList,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { ChangeEvent } from 'react';
import { connect } from 'react-redux';
import { closeToast, importRepo } from '../../actions';
import { RootState } from '../../reducers';
import { ToastType } from '../../reducers/repository_management';
import { isImportRepositoryURLInvalid } from '../../utils/url';
class CodeImportProject extends React.PureComponent<
{
importRepo: (p: string) => void;
importLoading: boolean;
toastMessage?: string;
showToast: boolean;
toastType?: ToastType;
closeToast: () => void;
},
{ value: string; isInvalid: boolean }
> {
public state = {
value: '',
isInvalid: false,
};
public onChange = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({
value: e.target.value,
isInvalid: isImportRepositoryURLInvalid(e.target.value),
});
};
public submitImportProject = () => {
if (!isImportRepositoryURLInvalid(this.state.value)) {
this.props.importRepo(this.state.value);
} else if (!this.state.isInvalid) {
this.setState({ isInvalid: true });
}
};
public updateIsInvalid = () => {
this.setState({ isInvalid: isImportRepositoryURLInvalid(this.state.value) });
};
public render() {
const { importLoading, toastMessage, showToast, toastType } = this.props;
return (
<div className="codeContainer__import">
{showToast && (
<EuiGlobalToastList
toasts={[{ title: '', color: toastType, text: toastMessage, id: toastMessage || '' }]}
dismissToast={this.props.closeToast}
toastLifeTimeMs={6000}
/>
)}
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label={i18n.translate('xpack.code.adminPage.repoTab.repositoryUrlTitle', {
defaultMessage: 'Repository URL',
})}
helpText="e.g. https://github.com/Microsoft/TypeScript-Node-Starter"
fullWidth
isInvalid={this.state.isInvalid}
error={i18n.translate('xpack.code.adminPage.repoTab.repositoryUrlEmptyText', {
defaultMessage: "The URL shouldn't be empty.",
})}
>
<EuiFieldText
value={this.state.value}
onChange={this.onChange}
aria-label="input project url"
data-test-subj="importRepositoryUrlInputBox"
isLoading={importLoading}
fullWidth={true}
isInvalid={this.state.isInvalid}
autoFocus={true}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{/*
// @ts-ignore */}
<EuiButton
className="codeButton__projectImport"
onClick={this.submitImportProject}
data-test-subj="importRepositoryButton"
>
<FormattedMessage
id="xpack.code.adminPage.repoTab.importButtonLabel"
defaultMessage="Import"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</div>
);
}
}
const mapStateToProps = (state: RootState) => ({
importLoading: state.repositoryManagement.importLoading,
toastMessage: state.repositoryManagement.toastMessage,
toastType: state.repositoryManagement.toastType,
showToast: state.repositoryManagement.showToast,
});
const mapDispatchToProps = {
importRepo,
closeToast,
};
export const ImportProject = connect(
mapStateToProps,
mapDispatchToProps
)(CodeImportProject);

View file

@ -1,303 +0,0 @@
/*
* 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 {
EuiButton,
EuiCodeBlock,
EuiFlexGroup,
EuiFlexItem,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiOverlayMask,
EuiPanel,
EuiSpacer,
EuiTabbedContent,
EuiText,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { connect } from 'react-redux';
import { InstallationType } from '../../../common/installation';
import { LanguageServer, LanguageServerStatus } from '../../../common/language_server';
import { requestInstallLanguageServer } from '../../actions/language_server';
import { RootState } from '../../reducers';
import { JavaIcon, TypeScriptIcon, GoIcon, CtagsIcon } from '../shared/icons';
const LanguageServerLi = (props: {
languageServer: LanguageServer;
requestInstallLanguageServer: (l: string) => void;
loading: boolean;
}) => {
const { status, name } = props.languageServer;
const languageIcon = () => {
if (name === 'TypeScript') {
return <TypeScriptIcon />;
} else if (name === 'Java') {
return <JavaIcon />;
} else if (name === 'Go') {
return <GoIcon />;
} else if (name === 'Ctags') {
return <CtagsIcon />;
}
};
const onInstallClick = () => props.requestInstallLanguageServer(name);
let button = null;
let state = null;
if (status === LanguageServerStatus.RUNNING) {
state = (
<EuiText size="xs">
<FormattedMessage
id="xpack.code.adminPage.langserverTab.runningText"
defaultMessage="Running ..."
/>
</EuiText>
);
} else if (status === LanguageServerStatus.NOT_INSTALLED) {
state = (
<EuiText size="xs" color={'subdued'}>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.notInstalledText"
defaultMessage="Not Installed"
/>
</EuiText>
);
} else if (status === LanguageServerStatus.READY) {
state = (
<EuiText size="xs" color={'subdued'}>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.installedText"
defaultMessage="Installed"
/>
</EuiText>
);
}
if (props.languageServer.installationType === InstallationType.Plugin) {
button = (
<EuiButton size="s" color="secondary" onClick={onInstallClick}>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.setupButtonLabel"
defaultMessage="Setup"
/>
</EuiButton>
);
}
return (
<EuiFlexItem>
<EuiPanel>
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem grow={false}> {languageIcon()} </EuiFlexItem>
<EuiFlexItem>
<EuiText>
<strong>{name}</strong>
</EuiText>
<EuiText size="s">
<h6> {state} </h6>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}> {button} </EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
);
};
interface Props {
languageServers: LanguageServer[];
requestInstallLanguageServer: (ls: string) => void;
installLoading: { [ls: string]: boolean };
}
interface State {
showingInstruction: boolean;
name?: string;
url?: string;
pluginName?: string;
}
class AdminLanguageSever extends React.PureComponent<Props, State> {
constructor(props: Props, context: any) {
super(props, context);
this.state = { showingInstruction: false };
}
public toggleInstruction = (
showingInstruction: boolean,
name?: string,
url?: string,
pluginName?: string
) => {
this.setState({ showingInstruction, name, url, pluginName });
};
public render() {
const languageServers = this.props.languageServers.map(ls => (
<LanguageServerLi
languageServer={ls}
key={ls.name}
requestInstallLanguageServer={() =>
this.toggleInstruction(true, ls.name, ls.downloadUrl, ls.pluginName)
}
loading={this.props.installLoading[ls.name]}
/>
));
return (
<div>
<EuiSpacer />
<EuiText>
<h3>
<span>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.languageServersDescription"
defaultMessage="{serverCount} {serverCount, plural, one {Language server} other {Language servers}}"
values={{ serverCount: this.props.languageServers.length }}
/>
</span>
</h3>
</EuiText>
<EuiSpacer />
<EuiFlexGroup direction="column" gutterSize="s">
{languageServers}
</EuiFlexGroup>
<LanguageServerInstruction
show={this.state.showingInstruction}
name={this.state.name!}
pluginName={this.state.pluginName!}
url={this.state.url!}
close={() => this.toggleInstruction(false)}
/>
</div>
);
}
}
const SupportedOS = [
{ id: 'windows', name: 'Windows' },
{ id: 'linux', name: 'Linux' },
{ id: 'darwin', name: 'macOS' },
];
const LanguageServerInstruction = (props: {
name: string;
pluginName: string;
url: string;
show: boolean;
close: () => void;
}) => {
const tabs = SupportedOS.map(({ id, name }) => {
const url = props.url ? props.url.replace('$OS', id) : '';
const installCode = `bin/kibana-plugin install ${url}`;
return {
id,
name,
content: (
<div>
<EuiSpacer />
<EuiText grow={false}>
<h3>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.setup.installTitle"
defaultMessage="Install"
/>
</h3>
<ol>
<li>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.setup.stopKibanaDescription"
defaultMessage="Stop your kibana Code node."
/>
</li>
<li>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.setup.useFollowingCommandToInstallDescription"
defaultMessage="Use the following command to install the {name} language server."
values={{ name: props.name }}
/>
</li>
</ol>
<EuiCodeBlock language="shell">{installCode}</EuiCodeBlock>
<h3>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.setup.uninstallTitle"
defaultMessage="Uninstall"
/>
</h3>
<ol>
<li>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.setup.stopKibanaDescription"
defaultMessage="Stop your kibana Code node."
/>
</li>
<li>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.setup.useFollowingCommandToRemoveDescription"
defaultMessage="Use the following command to remove the {name} language server."
values={{ name: props.name }}
/>
</li>
</ol>
<EuiCodeBlock language="shell">
bin/kibana-plugin remove {props.pluginName}
</EuiCodeBlock>
</EuiText>
</div>
),
};
});
return (
<React.Fragment>
{' '}
{props.show && (
<EuiOverlayMask>
<EuiModal onClose={props.close} maxWidth={false}>
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.setup.installationInstructionTitle"
defaultMessage="Installation Instructions"
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[1]} size={'m'} />
</EuiModalBody>
<EuiModalFooter>
<EuiButton onClick={props.close} fill>
<FormattedMessage
id="xpack.code.adminPage.langserverTab.setup.closeButtonLabel"
defaultMessage="Close"
/>
</EuiButton>
</EuiModalFooter>
</EuiModal>
</EuiOverlayMask>
)}
</React.Fragment>
);
};
const mapStateToProps = (state: RootState) => ({
languageServers: state.languageServer.languageServers,
installLoading: state.languageServer.installServerLoading,
});
const mapDispatchToProps = {
requestInstallLanguageServer,
};
export const LanguageSeverTab = connect(
mapStateToProps,
mapDispatchToProps
)(AdminLanguageSever);

View file

@ -1,378 +0,0 @@
/*
* 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 {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiLink,
EuiPanel,
EuiProgress,
EuiText,
EuiTextColor,
EuiToolTip,
EuiConfirmModal,
EuiOverlayMask,
EUI_MODAL_CONFIRM_BUTTON,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import moment from 'moment';
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { Repository, RepoState, WorkerReservedProgress } from '../../../model';
import { deleteRepo, indexRepo, initRepoCommand } from '../../actions';
import { RepoStatus } from '../../actions/status';
const stateColor = {
[RepoState.CLONING]: 'secondary',
[RepoState.UPDATING]: 'secondary',
[RepoState.DELETING]: 'accent',
[RepoState.INDEXING]: 'primary',
};
class CodeProjectItem extends React.PureComponent<
{
project: Repository;
enableManagement: boolean;
showStatus: boolean;
status?: RepoStatus;
deleteRepo?: (uri: string) => void;
indexRepo?: (uri: string) => void;
initRepoCommand?: (uri: string) => void;
openSettings?: (uri: string, url: string) => void;
},
{ showReindexConfirmModal: boolean; showDeleteConfirmModal: boolean }
> {
state = {
showDeleteConfirmModal: false,
showReindexConfirmModal: false,
};
openReindexModal = () => {
this.setState({ showReindexConfirmModal: true });
};
closeReindexModal = () => {
this.setState({ showReindexConfirmModal: false });
};
openDeleteModal = () => {
this.setState({ showDeleteConfirmModal: true });
};
closeDeleteModal = () => {
this.setState({ showDeleteConfirmModal: false });
};
confirmDelete = () => {
if (this.props.deleteRepo) {
this.props.deleteRepo(this.props.project.uri);
this.closeDeleteModal();
}
};
confirmReindex = () => {
if (this.props.indexRepo) {
this.props.indexRepo(this.props.project.uri);
this.closeReindexModal();
}
};
public render() {
const { project, showStatus, status, enableManagement } = this.props;
const { name, org, uri, url } = project;
const onClickSettings = () => this.props.openSettings && this.props.openSettings(uri, url);
let footer = null;
let disableRepoLink = false;
let hasError = false;
if (!status) {
footer = (
<div className="codeFooter">
<FormattedMessage id="xpack.code.repoItem.initText" defaultMessage="INIT..." />
</div>
);
} else if (status.state === RepoState.READY) {
footer = (
<div className="codeFooter" data-test-subj="repositoryIndexDone">
<FormattedMessage
id="xpack.code.repoItem.lastUpdatedText"
defaultMessage="LAST UPDATED"
/>
:{' '}
{moment(status.timestamp)
.locale(i18n.getLocale())
.fromNow()}
</div>
);
} else if (status.state === RepoState.DELETING) {
footer = (
<div className="codeFooter">
<FormattedMessage id="xpack.code.repoItem.deletingText" defaultMessage="DELETING..." />
</div>
);
} else if (status.state === RepoState.INDEXING) {
footer = (
<div className="codeFooter" data-test-subj="repositoryIndexOngoing">
<FormattedMessage id="xpack.code.repoItem.indexingText" defaultMessage="INDEXING..." />
</div>
);
} else if (status.state === RepoState.CLONING) {
footer = (
<div className="codeFooter">
<FormattedMessage id="xpack.code.repoItem.cloningText" defaultMessage="CLONING..." />
</div>
);
} else if (status.state === RepoState.UPDATING) {
footer = (
<div className="codeFooter">
<FormattedMessage id="xpack.code.repoItem.updatingText" defaultMessage="UPDATING..." />
</div>
);
} else if (status.state === RepoState.DELETE_ERROR) {
footer = (
<div className="codeFooter codeFooter--error">
<FormattedMessage
id="xpack.code.repoItem.deleteErrorText"
defaultMessage="ERROR DELETE REPO"
/>
</div>
);
hasError = true;
} else if (status.state === RepoState.INDEX_ERROR) {
footer = (
<div className="codeFooter codeFooter--error">
<FormattedMessage
id="xpack.code.repoItem.indexErrorText"
defaultMessage="ERROR INDEX REPO"
/>
</div>
);
hasError = true;
} else if (status.state === RepoState.CLONE_ERROR) {
footer = (
<div className="codeFooter codeFooter--error">
<FormattedMessage
id="xpack.code.repoItem.cloneErrorText"
defaultMessage="ERROR CLONING REPO"
/>
&nbsp;
<EuiToolTip position="top" content={status.errorMessage}>
<EuiIcon type="iInCircle" />
</EuiToolTip>
</div>
);
// Disable repo link is clone failed.
disableRepoLink = true;
hasError = true;
}
const repoTitle = (
<EuiText data-test-subj="codeRepositoryItem">
<EuiTextColor color="subdued">{org}</EuiTextColor>/<strong>{name}</strong>
</EuiText>
);
const settingsShow =
status && status.state !== RepoState.CLONING && status.state !== RepoState.DELETING;
const settingsVisibility = settingsShow ? 'visible' : 'hidden';
const indexShow =
status &&
status.state !== RepoState.CLONING &&
status.state !== RepoState.DELETING &&
status.state !== RepoState.INDEXING &&
status.state !== RepoState.CLONE_ERROR;
const indexVisibility = indexShow ? 'visible' : 'hidden';
const deleteShow = status && status.state !== RepoState.DELETING;
const deleteVisibility = deleteShow ? 'visible' : 'hidden';
const projectManagement = (
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem grow={false} style={{ display: 'none' }}>
<div
className="codeButton__project"
data-test-subj="settingsRepositoryButton"
tabIndex={0}
onKeyPress={onClickSettings}
onClick={onClickSettings}
role="button"
style={{ visibility: settingsVisibility }}
>
<EuiIcon type="gear" />
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.code.repoItem.settingsButtonLabel"
defaultMessage="Settings"
/>
</EuiText>
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div
className="codeButton__project"
data-test-subj="indexRepositoryButton"
tabIndex={0}
onKeyPress={this.openReindexModal}
onClick={this.openReindexModal}
role="button"
style={{ visibility: indexVisibility }}
>
<EuiIcon type="indexSettings" />
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.code.repoItem.reindexButtonLabel"
defaultMessage="Reindex"
/>
</EuiText>
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div
className="codeButton__project"
data-test-subj="deleteRepositoryButton"
tabIndex={0}
onKeyPress={this.openDeleteModal}
onClick={this.openDeleteModal}
role="button"
style={{ visibility: deleteVisibility }}
>
<EuiIcon type="trash" color="danger" />
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.code.repoItem.deleteButtonLabel"
defaultMessage="Delete"
/>
</EuiText>
</div>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
const repoStatus = (
<EuiText>
<h6>
<EuiTextColor color="subdued">{footer}</EuiTextColor>
</h6>
</EuiText>
);
return (
<EuiPanel
className={hasError ? 'codePanel__project codePanel__project--error' : 'codePanel__project'}
>
{this.renderProgress()}
<EuiFlexGroup alignItems="center" justifyContent="flexStart">
<EuiFlexItem grow={3}>
{disableRepoLink ? (
repoTitle
) : (
<Link to={`/${uri}`} data-test-subj={`adminLinkTo${name}`}>
{repoTitle}
</Link>
)}
{showStatus ? repoStatus : null}
</EuiFlexItem>
<EuiFlexItem grow={3}>
<EuiText color="subdued" size="s">
<EuiLink href={'https://' + uri} target="_blank">
{uri}
</EuiLink>
</EuiText>
</EuiFlexItem>
{enableManagement && projectManagement}
</EuiFlexGroup>
{this.state.showDeleteConfirmModal && this.renderDeleteConfirmModal()}
{this.state.showReindexConfirmModal && this.renderReindexConfirmModal()}
</EuiPanel>
);
}
renderReindexConfirmModal = () => {
return (
<EuiOverlayMask>
<EuiConfirmModal
title={i18n.translate('xpack.code.repoItem.reindexConfirmTitle', {
defaultMessage: 'Reindex this repository?',
})}
onCancel={this.closeReindexModal}
onConfirm={this.confirmReindex}
cancelButtonText={i18n.translate('xpack.code.repoItem.cancelButtonText', {
defaultMessage: "No, don't do it",
})}
confirmButtonText={i18n.translate('xpack.code.repoItem.confirmButtonText', {
defaultMessage: 'Yes, do it',
})}
defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON}
/>
</EuiOverlayMask>
);
};
renderDeleteConfirmModal = () => {
return (
<EuiOverlayMask>
<EuiConfirmModal
title={i18n.translate('xpack.code.repoItem.deleteConfirmTitle', {
defaultMessage: 'Delete this repository?',
})}
onCancel={this.closeDeleteModal}
onConfirm={this.confirmDelete}
cancelButtonText={i18n.translate('xpack.code.repoItem.cancelButtonText', {
defaultMessage: "No, don't do it",
})}
confirmButtonText={i18n.translate('xpack.code.repoItem.confirmButtonText', {
defaultMessage: 'Yes, do it',
})}
buttonColor="danger"
defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON}
/>
</EuiOverlayMask>
);
};
private renderProgress() {
const { status } = this.props;
if (
status &&
(status.state === RepoState.CLONING ||
status.state === RepoState.DELETING ||
status.state === RepoState.INDEXING)
) {
const color = stateColor[status.state] as 'primary' | 'secondary' | 'accent';
if (status.progress! === WorkerReservedProgress.COMPLETED) {
return null;
} else if (status.progress! > WorkerReservedProgress.INIT) {
return (
<EuiProgress
max={100}
value={status.progress}
size="s"
color={color}
position="absolute"
/>
);
} else {
return <EuiProgress size="s" color={color} position="absolute" />;
}
}
}
}
const mapDispatchToProps = {
deleteRepo,
indexRepo,
initRepoCommand,
};
export const ProjectItem = connect(
null,
mapDispatchToProps
)(CodeProjectItem);

View file

@ -1,150 +0,0 @@
/*
* 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 {
EuiButton,
EuiButtonEmpty,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiOverlayMask,
EuiSwitch,
EuiText,
EuiTitle,
} from '@elastic/eui';
import React, { ChangeEvent } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { LanguageServer } from '../../../common/language_server';
import { RepositoryUtils } from '../../../common/repository_utils';
import { RepositoryConfig } from '../../../model';
import { RepoConfigPayload, switchLanguageServer } from '../../actions';
import { RootState } from '../../reducers';
import { JavaIcon, TypeScriptIcon } from '../shared/icons';
const defaultConfig = {
disableGo: true,
disableJava: true,
disableTypescript: true,
};
interface StateProps {
languageServers: LanguageServer[];
config: RepositoryConfig;
}
interface DispatchProps {
switchLanguageServer: (p: RepoConfigPayload) => void;
}
interface OwnProps {
repoUri: string;
url: string;
onClose: () => void;
}
interface State {
config: RepositoryConfig;
}
class ProjectSettingsModal extends React.PureComponent<
StateProps & DispatchProps & OwnProps,
State
> {
public state = {
config: this.props.config,
};
public onSwitchChange = (ls: string) => (e: ChangeEvent<HTMLInputElement>) => {
const { checked } = e.target;
this.setState((prevState: State) => ({
config: { ...prevState.config, [`disable${ls}`]: !checked },
}));
};
public saveChanges = () => {
this.props.switchLanguageServer({
repoUri: this.props.repoUri,
config: this.state.config,
});
};
public render() {
const { repoUri, languageServers, onClose } = this.props;
const { disableJava, disableTypescript } = this.state.config;
const org = RepositoryUtils.orgNameFromUri(repoUri);
const repoName = RepositoryUtils.repoNameFromUri(repoUri);
const languageServerSwitches = languageServers.map(ls => {
const checked = ls.name === 'Java' ? !disableJava : !disableTypescript;
return (
<div key={ls.name}>
<EuiSwitch
name={ls.name}
label={
<span>
{ls.name === 'Java' ? (
<div className="codeSettingsPanel__icon">
<JavaIcon />
</div>
) : (
<div className="codeSettingsPanel__icon">
<TypeScriptIcon />
</div>
)}
{ls.name}
</span>
}
checked={checked}
onChange={this.onSwitchChange(ls.name)}
/>
</div>
);
});
return (
<EuiOverlayMask>
<EuiModal onClose={onClose}>
<EuiModalHeader>
<EuiModalHeaderTitle>
<h3>Project Settings</h3>
<EuiText>
{org}/{repoName}
</EuiText>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiTitle size="xxs">
<h5>Language Servers</h5>
</EuiTitle>
{languageServerSwitches}
</EuiModalBody>
<EuiModalFooter>
<EuiButtonEmpty>
<Link to="/admin?tab=LanguageServers">Manage Language Servers</Link>
</EuiButtonEmpty>
<EuiButton onClick={this.saveChanges}>Save Changes</EuiButton>
</EuiModalFooter>
</EuiModal>
</EuiOverlayMask>
);
}
}
const mapStateToProps = (state: RootState, ownProps: { repoUri: string }) => ({
languageServers: state.languageServer.languageServers,
config: state.repositoryManagement.repoLangseverConfigs![ownProps.repoUri] || defaultConfig,
});
const mapDispatchToProps = {
switchLanguageServer,
};
export const ProjectSettings = connect<StateProps, DispatchProps, OwnProps>(
// @ts-ignore
mapStateToProps,
mapDispatchToProps
)(ProjectSettingsModal);

View file

@ -1,289 +0,0 @@
/*
* 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 {
EuiButton,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiGlobalToastList,
EuiSpacer,
EuiSuperSelect,
EuiText,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import moment from 'moment';
import { get } from 'lodash';
import React, { ChangeEvent } from 'react';
import { connect } from 'react-redux';
import { npStart } from 'ui/new_platform';
import { Repository } from '../../../model';
import { closeToast, importRepo, RepoStatus } from '../../actions';
import { RootState } from '../../reducers';
import { ToastType } from '../../reducers/repository_management';
import { isImportRepositoryURLInvalid } from '../../utils/url';
import { ProjectItem } from './project_item';
import { ProjectSettings } from './project_settings';
import { ImportModal } from '../integrations/import_modal';
enum SortOptionsValue {
AlphabeticalAsc = 'alphabetical_asc',
AlphabeticalDesc = 'alphabetical_desc',
UpdatedAsc = 'updated_asc',
UpdatedDesc = 'updated_desc',
RecentlyAdded = 'recently_added',
}
const sortFunctionsFactory = (status: { [key: string]: RepoStatus }) => {
const sortFunctions: { [k: string]: (a: Repository, b: Repository) => number } = {
[SortOptionsValue.AlphabeticalAsc]: (a: Repository, b: Repository) =>
a.name!.localeCompare(b.name!),
[SortOptionsValue.AlphabeticalDesc]: (a: Repository, b: Repository) =>
b.name!.localeCompare(a.name!),
[SortOptionsValue.UpdatedAsc]: (a: Repository, b: Repository) =>
moment(status[b.uri].timestamp).diff(moment(status[a.uri].timestamp)),
[SortOptionsValue.UpdatedDesc]: (a: Repository, b: Repository) =>
moment(status[a.uri].timestamp).diff(moment(status[b.uri].timestamp)),
[SortOptionsValue.RecentlyAdded]: () => {
return -1;
},
};
return sortFunctions;
};
const sortOptions = [
{
value: SortOptionsValue.AlphabeticalAsc,
inputDisplay: i18n.translate('xpack.code.adminPage.repoTab.sort.aToZDropDownOptionLabel', {
defaultMessage: 'A to Z',
}),
},
{
value: SortOptionsValue.AlphabeticalDesc,
inputDisplay: i18n.translate('xpack.code.adminPage.repoTab.sort.zToADropDownOptionLabel', {
defaultMessage: 'Z to A',
}),
},
{
value: SortOptionsValue.UpdatedAsc,
inputDisplay: i18n.translate(
'xpack.code.adminPage.repoTab.sort.updatedAscDropDownOptionLabel',
{
defaultMessage: 'Last Updated ASC',
}
),
},
{
value: SortOptionsValue.UpdatedDesc,
inputDisplay: i18n.translate(
'xpack.code.adminPage.repoTab.sort.updatedDescDropDownOptionLabel',
{
defaultMessage: 'Last Updated DESC',
}
),
},
// { value: SortOptionsValue.recently_added, inputDisplay: 'Recently Added' },
];
interface Props {
projects: Repository[];
status: { [key: string]: RepoStatus };
importRepo: (repoUrl: string) => void;
importLoading: boolean;
toastMessage?: string;
showToast: boolean;
toastType?: ToastType;
closeToast: () => void;
}
interface State {
showImportProjectModal: boolean;
importLoading: boolean;
settingModal: { url?: string; uri?: string; show: boolean };
repoURL: string;
isInvalid: boolean;
sortOption: SortOptionsValue;
}
class CodeProjectTab extends React.PureComponent<Props, State> {
public static getDerivedStateFromProps(props: Readonly<Props>, state: State) {
if (state.importLoading && !props.importLoading) {
return { showImportProjectModal: false, importLoading: props.importLoading, repoURL: '' };
}
return { importLoading: props.importLoading };
}
constructor(props: Props) {
super(props);
this.state = {
importLoading: false,
showImportProjectModal: false,
settingModal: { show: false },
repoURL: '',
sortOption: SortOptionsValue.AlphabeticalAsc,
isInvalid: false,
};
}
public closeModal = () => {
this.setState({ showImportProjectModal: false, repoURL: '', isInvalid: false });
};
public openModal = () => {
this.setState({ showImportProjectModal: true });
};
public openSettingModal = (uri: string, url: string) => {
this.setState({ settingModal: { uri, url, show: true } });
};
public closeSettingModal = () => {
this.setState({ settingModal: { show: false } });
};
public onChange = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({
repoURL: e.target.value,
isInvalid: isImportRepositoryURLInvalid(e.target.value),
});
};
public submitImportProject = () => {
if (!isImportRepositoryURLInvalid(this.state.repoURL)) {
this.props.importRepo(this.state.repoURL);
} else if (!this.state.isInvalid) {
this.setState({ isInvalid: true });
}
};
public renderImportModal = () => {
const { isInvalid, repoURL, showImportProjectModal } = this.state;
if (showImportProjectModal) {
return (
<ImportModal
isInvalid={isInvalid}
isLoading={this.props.importLoading}
onChange={this.onChange}
onClose={this.closeModal}
onSubmit={this.submitImportProject}
value={repoURL}
/>
);
}
};
public setSortOption = (value: string) => {
this.setState({ sortOption: value as SortOptionsValue });
};
public render() {
const { projects, status, toastMessage, showToast, toastType } = this.props;
const projectsCount = projects.length;
const sortedProjects = projects.sort(sortFunctionsFactory(status)[this.state.sortOption]);
const repoList = sortedProjects.map((repo: Repository) => (
<ProjectItem
openSettings={this.openSettingModal}
key={repo.uri}
project={repo}
showStatus={true}
status={status[repo.uri]}
enableManagement={get(npStart.core.application.capabilities, 'code.admin') as boolean}
/>
));
let settings = null;
if (this.state.settingModal.show) {
settings = (
<ProjectSettings
onClose={this.closeSettingModal}
repoUri={this.state.settingModal.uri}
url={this.state.settingModal.url}
/>
);
}
return (
<div className="code-sidebar" data-test-subj="codeRepositoryList">
{showToast && (
<EuiGlobalToastList
toasts={[{ title: '', color: toastType, text: toastMessage, id: toastMessage || '' }]}
dismissToast={this.props.closeToast}
toastLifeTimeMs={6000}
/>
)}
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label={i18n.translate('xpack.code.adminPage.repoTab.sort.sortByFormLabel', {
defaultMessage: 'Sort By',
})}
>
<EuiSuperSelect
options={sortOptions}
valueOfSelected={this.state.sortOption}
onChange={this.setSortOption}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow />
<EuiFlexItem grow />
<EuiFlexItem>
{(get(npStart.core.application.capabilities, 'code.admin') as boolean) && (
// @ts-ignore
<EuiButton
className="codeButton__projectImport"
onClick={this.openModal}
data-test-subj="newProjectButton"
>
<FormattedMessage
id="xpack.code.adminPage.repoTab.importRepoButtonLabel"
defaultMessage="Import a new repo"
/>
</EuiButton>
)}
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
<EuiText>
<h3>
<FormattedMessage
id="xpack.code.adminPage.repoTab.repoDescription"
defaultMessage="{projectsCount} {projectsCount, plural, one {Repo} other {Repos}}"
values={{ projectsCount }}
/>
</h3>
</EuiText>
<EuiSpacer />
{repoList}
{this.renderImportModal()}
{settings}
</div>
);
}
}
const mapStateToProps = (state: RootState) => ({
projects: state.repositoryManagement.repositories,
status: state.status.status,
importLoading: state.repositoryManagement.importLoading,
toastMessage: state.repositoryManagement.toastMessage,
toastType: state.repositoryManagement.toastType,
showToast: state.repositoryManagement.showToast,
});
const mapDispatchToProps = {
importRepo,
closeToast,
};
export const ProjectTab = connect(
mapStateToProps,
mapDispatchToProps
)(CodeProjectTab);

View file

@ -1,274 +0,0 @@
/*
* 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 {
EuiButton,
EuiCallOut,
EuiGlobalToastList,
EuiPanel,
EuiSpacer,
EuiSteps,
EuiText,
EuiTitle,
EuiLink,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { documentationLinks } from '../../lib/documentation_links';
import { RootState } from '../../reducers';
const steps = [
{
title: i18n.translate('xpack.code.adminPage.setupGuide.checkMultiInstanceTitle', {
defaultMessage: 'Check if multiple Kibana instances are used as a clusterURL',
}),
children: (
<EuiText>
<p>
<FormattedMessage
id="xpack.code.adminPage.setupGuide.checkMultiInstanceDescription1"
defaultMessage="If you are using single Kibana instance, you can skip this step."
/>
</p>
<p>
<FormattedMessage
id="xpack.code.adminPage.setupGuide.checkMultiInstanceDescription2"
defaultMessage="If you are using multiple Kibana instances, you need to assign one Kibana instance as `Code node`.
To do this, add the following line of code into your kibana.yml file of every
Kibana instance and restart the instances:"
/>
</p>
<pre>
<code>xpack.code.codeNodeUrl: 'http://$YourCodeNodeAddress'</code>
</pre>
<p>
<FormattedMessage
id="xpack.code.adminPage.setupGuide.checkMultiInstanceDescription3"
defaultMessage="Where `$YourCodeNoteAddress` is the URL of your assigned Code node accessible by other Kibana instances."
/>
</p>
</EuiText>
),
},
{
title: i18n.translate('xpack.code.adminPage.setupGuide.installExtraLangSupportTitle', {
defaultMessage: 'Install extra language support optionally',
}),
children: (
<EuiText>
<p>
<FormattedMessage
id="xpack.code.adminPage.setupGuide.installExtraLangSupportDescription1"
defaultMessage="Look {link} to learn more about supported languages and language server installation."
values={{
link: (
<EuiLink href={documentationLinks.codeInstallLangServer} target="_blank">
<FormattedMessage
id="xpack.code.adminPage.setupGuide.installExtraLangSupportHereLinkText"
defaultMessage="here"
/>
</EuiLink>
),
}}
/>
</p>
<p>
<FormattedMessage
id="xpack.code.adminPage.setupGuide.installExtraLangSupportDescription2"
defaultMessage="If you need Java language support, you can manage language server installation {link}."
values={{
link: (
<Link to="/admin?tab=LanguageServers">
<FormattedMessage
id="xpack.code.adminPage.setupGuide.installExtraLangSupportHereLinkText"
defaultMessage="here"
/>
</Link>
),
}}
/>
</p>
</EuiText>
),
},
{
title: i18n.translate('xpack.code.adminPage.setupGuide.addRepositoryTitle', {
defaultMessage: 'Add a repository to Code',
}),
children: (
<EuiText>
<p>
<FormattedMessage
id="xpack.code.adminPage.setupGuide.addRepositoryDescription"
defaultMessage="Import {sampleRepoLink} or {ownRepoLink}. It is as easy as copy and paste git clone URLs to Code."
values={{
sampleRepoLink: (
<EuiLink href={documentationLinks.codeGettingStarted} target="_blank">
<FormattedMessage
id="xpack.code.adminPage.setupGuide.addRepositorySampleRepoLinkText"
defaultMessage="a sample repo"
/>
</EuiLink>
),
ownRepoLink: (
<EuiLink href={documentationLinks.codeRepoManagement} target="_blank">
<FormattedMessage
id="xpack.code.adminPage.setupGuide.addRepositoryOwnRepoLinkText"
defaultMessage="your own repo"
/>
</EuiLink>
),
}}
/>
</p>
</EuiText>
),
},
{
title: i18n.translate('xpack.code.adminPage.setupGuide.verifyImportTitle', {
defaultMessage: 'Verify the repo is successfully imported',
}),
children: (
<EuiText>
<p>
<FormattedMessage
id="xpack.code.adminPage.setupGuide.verifyImportDescription"
defaultMessage="You can verify your repo is successfully imported by {searchingLink} and {navigatingLink} the repo. If language support is available to the repo, make sure {semanticNavigationLink} is available as well."
values={{
searchingLink: (
<EuiLink href={documentationLinks.codeSearch} target="_blank">
<FormattedMessage
id="xpack.code.adminPage.setupGuide.verifyImportSearchingLinkText"
defaultMessage="searching"
/>
</EuiLink>
),
navigatingLink: (
<EuiLink href={documentationLinks.codeOtherFeatures} target="_blank">
<FormattedMessage
id="xpack.code.adminPage.setupGuide.verifyImportNavigatingLinkText"
defaultMessage="navigating"
/>
</EuiLink>
),
semanticNavigationLink: (
<EuiLink href={documentationLinks.semanticNavigation} target="_blank">
<FormattedMessage
id="xpack.code.adminPage.setupGuide.verifyImportSemanticNavigatingLinkText"
defaultMessage="semantic navigation"
/>
</EuiLink>
),
}}
/>
</p>
</EuiText>
),
},
];
const toastMessage = (
<div>
<p>
<FormattedMessage
id="xpack.code.adminPage.setupGuide.permissionChangesDescription"
defaultMessage="Weve made some changes to roles and permissions in Kibana. Read more about how these changes affect your Code implementation below."
/>
</p>
<EuiButton size="s" href={documentationLinks.kibanaRoleManagement}>
<FormattedMessage
id="xpack.code.adminPage.setupGuide.learnMoreButtonLabel"
defaultMessage="Learn more"
/>
</EuiButton>
</div>
);
class SetupGuidePage extends React.PureComponent<{ setupOk?: boolean }, { hideToast: boolean }> {
constructor(props: { setupOk?: boolean }) {
super(props);
this.state = {
hideToast: false,
};
}
public render() {
let setup = null;
if (this.props.setupOk !== undefined) {
setup = (
<div>
{!this.state.hideToast && (
<EuiGlobalToastList
toasts={[
{
title: i18n.translate('xpack.code.adminPage.setupGuide.permissionChangesTitle', {
defaultMessage: 'Permission Changes',
}),
color: 'primary',
iconType: 'iInCircle',
text: toastMessage,
id: '',
},
]}
dismissToast={() => {
this.setState({ hideToast: true });
}}
toastLifeTimeMs={10000}
/>
)}
<React.Fragment>
{this.props.setupOk === false && (
<EuiCallOut title="Code instance not found." color="danger" iconType="cross">
<p>
Please follow the guide below to configure your Kibana instance. Once configured,
refresh this page.
</p>
</EuiCallOut>
)}
{this.props.setupOk === true && (
<React.Fragment>
<EuiSpacer size="s" />
<Link to="/admin">
<EuiButton iconType="sortLeft">
<FormattedMessage
id="xpack.code.adminPage.setupGuide.backToDashboardButtonLabel"
defaultMessage="Back To repository dashboard"
/>
</EuiButton>
</Link>
<EuiSpacer size="s" />
</React.Fragment>
)}
<EuiPanel>
<EuiTitle>
<h3>
<FormattedMessage
id="xpack.code.adminPage.setupGuide.getStartedTitle"
defaultMessage="Getting started in Elastic Code"
/>
</h3>
</EuiTitle>
<EuiSpacer />
<EuiSteps steps={steps} />
</EuiPanel>
</React.Fragment>
</div>
);
}
return <div className="codeContainer__setup">{setup}</div>;
}
}
const mapStateToProps = (state: RootState) => ({
setupOk: state.setup.ok,
});
export const SetupGuide = connect(mapStateToProps)(SetupGuidePage);

View file

@ -1,59 +0,0 @@
/*
* 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 React from 'react';
import { HashRouter as Router, Redirect, Switch } from 'react-router-dom';
import chrome from 'ui/chrome';
import { connect } from 'react-redux';
import { RootState } from '../reducers';
import { Admin } from './admin_page/admin';
import { SetupGuide } from './admin_page/setup_guide';
import { Diff } from './diff_page/diff';
import { Main } from './main/main';
import { NotFound } from './main/not_found';
import { Route } from './route';
import * as ROUTES from './routes';
import { Search } from './search_page/search';
import { Integrations } from './integrations';
const RooComponent = (props: { setupOk?: boolean }) => {
if (props.setupOk) {
return <Redirect to={'/admin'} />;
}
return <SetupGuide />;
};
const mapStateToProps = (state: RootState) => ({
setupOk: state.setup.ok,
});
const Root = connect(mapStateToProps)(RooComponent);
const Empty = () => null;
const integrationsEnabled = chrome.getInjected('codeIntegrationsEnabled');
export const App = () => {
return (
<Router>
<Switch>
<Route path={ROUTES.DIFF} exact={true} component={Diff} />
<Route path={ROUTES.ROOT} exact={true} component={Root} />
<Route path={ROUTES.MAIN} component={Main} exact={true} />
<Route path={ROUTES.MAIN_ROOT} component={Main} />
<Route path={ROUTES.ADMIN} component={Admin} />
<Route path={ROUTES.SEARCH} component={Search} />
<Route path={ROUTES.REPO} render={Empty} exact={true} />
<Route path={ROUTES.SETUP} component={SetupGuide} exact={true} />
{integrationsEnabled && (
<Route path={ROUTES.INTEGRATIONS} component={Integrations} exact={true} />
)}
<Route path="*" component={NotFound} />
</Switch>
</Router>
);
};

View file

@ -1,184 +0,0 @@
/*
* 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 { editor, IRange } from 'monaco-editor';
import React, { createRef } from 'react';
import { ResizeChecker } from '../shared/resize_checker';
import { monaco } from '../../monaco/monaco';
import { registerEditor } from '../../monaco/single_selection_helper';
export interface Position {
lineNumber: string;
column: number;
}
export interface Props {
lines: string[];
language: string;
/**
* Returns whether to highlight the given line.
* @param lineIndex The index of the line (0-based)
*/
highlightLine: (lineIndex: number) => boolean;
highlightRanges: IRange[];
onClick: (event: Position) => void;
folding: boolean;
/**
* Returns the line number to display for a given line.
* @param lineIndex The index of the line (0-based)
*/
lineNumber: (lineIndex: number) => string;
}
export class CodeBlock extends React.PureComponent<Props> {
static defaultProps = {
folding: false,
highlightLine: () => {},
highlightRanges: [],
language: 'text',
lineNumber: String,
onClick: () => {},
};
private el = createRef<HTMLDivElement>();
private ed?: editor.IStandaloneCodeEditor;
private resizeChecker?: ResizeChecker;
private currentDecorations: string[] = [];
public async componentDidMount() {
const { language, onClick } = this.props;
if (this.el.current) {
await this.tryLoadFile(this.text, language);
this.ed!.onMouseDown((e: editor.IEditorMouseEvent) => {
if (
onClick &&
(e.target.type === monaco.editor.MouseTargetType.GUTTER_LINE_NUMBERS ||
e.target.type === monaco.editor.MouseTargetType.CONTENT_TEXT)
) {
const position = e.target.position || { lineNumber: 0, column: 0 };
const lineNumber = this.lineNumber(position.lineNumber);
onClick({
lineNumber,
column: position.column,
});
}
});
registerEditor(this.ed!);
this.setDecorations();
this.resizeChecker = new ResizeChecker(this.el.current!);
this.resizeChecker.on('resize', () => {
setTimeout(() => {
this.ed!.layout();
});
});
}
}
private async tryLoadFile(text: string, language: string) {
try {
await monaco.editor.colorize(text, language, {});
this.loadFile(text, language);
} catch (e) {
this.loadFile(text);
}
}
private loadFile(text: string, language: string = 'text') {
this.ed = monaco.editor.create(this.el.current!, {
value: text,
language,
lineNumbers: this.lineNumber,
readOnly: true,
folding: this.props.folding,
minimap: {
enabled: false,
},
scrollbar: {
vertical: 'hidden',
handleMouseWheel: false,
verticalScrollbarSize: 0,
},
hover: {
enabled: false, // disable default hover;
},
contextmenu: false,
selectOnLineNumbers: false,
selectionHighlight: false,
renderLineHighlight: 'none',
renderIndentGuides: false,
automaticLayout: false,
});
}
public componentDidUpdate(prevProps: Readonly<Props>) {
const { highlightRanges } = this.props;
const prevText = prevProps.lines.join('\n');
if (prevText !== this.text || prevProps.highlightRanges !== highlightRanges) {
if (this.ed) {
const model = this.ed.getModel();
if (model) {
model.setValue(this.text);
this.setDecorations();
}
}
}
}
public componentWillUnmount(): void {
if (this.ed) {
this.ed.dispose();
}
}
public render() {
const height = this.props.lines.length * 18;
return <div ref={this.el} className="codeContainer__monaco" style={{ height }} />;
}
private lineNumber = (lineIndex: number) => this.props.lineNumber(lineIndex - 1);
private get text(): string {
return this.props.lines.join('\n');
}
private setDecorations() {
const decorations = this.decorations;
if (decorations.length) {
this.currentDecorations = this.ed!.deltaDecorations(this.currentDecorations, decorations);
}
}
private get decorations(): editor.IModelDeltaDecoration[] {
const { lines, highlightRanges, highlightLine } = this.props;
const rangeHighlights = highlightRanges.map(range => ({
range,
options: {
inlineClassName: 'codeSearch__highlight',
},
}));
const lineHighlights = lines
.map((line, lineIndex) => ({
range: new monaco.Range(lineIndex + 1, 0, lineIndex + 1, 0),
options: {
isWholeLine: true,
className: 'codeBlock__line--highlighted',
linesDecorationsClassName: 'codeBlock__line--highlighted',
},
}))
.filter((decorations, lineIndex) => highlightLine(lineIndex));
return [...rangeHighlights, ...lineHighlights];
}
}

View file

@ -1,24 +0,0 @@
/*
* 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 React, { ReactNode } from 'react';
import { EuiPanel } from '@elastic/eui';
import { CodeBlock, Props as CodeBlockProps } from './code_block';
export interface Props extends CodeBlockProps {
className?: string;
header?: ReactNode;
}
export const CodeBlockPanel = ({ className, header, ...rest }: Props) => (
<EuiPanel paddingSize="s" className={className}>
{header}
<CodeBlock {...rest} />
</EuiPanel>
);
CodeBlockPanel.defaultProps = CodeBlock.defaultProps;

View file

@ -1,8 +0,0 @@
/*
* 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.
*/
export { CodeBlock, Position, Props as CodeBlockProps } from './code_block';
export { CodeBlockPanel, Props as CodeBlockPanelProps } from './code_block_panel';

View file

@ -1,235 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Commit component renders correctly for a commit 1`] = `
<Commit
commit={
Object {
"author": "author",
"committer": "committer",
"id": "4ba67b8",
"message": "This is my commit message
This is the description",
"parents": Array [
"9817575",
],
"treeId": "96440ceb55e04a99d33c1c8ee021400a680fbf74",
"updated": 2222-11-11T00:00:00.000Z,
}
}
date="11/11/2222"
repoUri="github.com/elastic/code"
showRepoLink={false}
>
<EuiPanel
className="code-timeline__commit--root"
>
<div
className="euiPanel euiPanel--paddingMedium code-timeline__commit--root"
>
<EuiFlexGroup
alignItems="center"
gutterSize="none"
justifyContent="spaceBetween"
>
<div
className="euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
>
<div
className="eui-textTruncate"
>
<EuiTitle
className="eui-textTruncate"
size="xxs"
>
<h4
className="euiTitle euiTitle--xxsmall eui-textTruncate"
>
This is my commit message
</h4>
</EuiTitle>
<EuiText
className="commit__metadata"
size="xs"
>
<div
className="euiText euiText--extraSmall commit__metadata"
>
<EuiTextColor
color="subdued"
>
<span
className="euiTextColor euiTextColor--subdued"
>
<FormattedMessage
defaultMessage="Committed on {date} by {committer}"
id="xpack.code.commits.committedDescription"
values={
Object {
"committer": "committer",
"date": "11/11/2222",
}
}
>
<span>
Committed on 11/11/2222 by committer
</span>
</FormattedMessage>
</span>
</EuiTextColor>
</div>
</EuiText>
<EuiText
size="s"
>
<div
className="euiText euiText--small"
>
<p
className="eui-textTruncate"
>
This is the description
</p>
</div>
</EuiText>
</div>
<CommitActions
commitId="4ba67b8"
repoUri="github.com/elastic/code"
>
<div
className="commit__actions"
>
<EuiCopy
afterMessage="Copied"
textToCopy="4ba67b8"
>
<EuiToolTip
delay="regular"
onMouseOut={[Function]}
position="top"
>
<span
className="euiToolTipAnchor"
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<EuiButtonIcon
aria-label="Copy the full commit ID"
iconType="copyClipboard"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
>
<button
aria-label="Copy the full commit ID"
className="euiButtonIcon euiButtonIcon--primary"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
type="button"
>
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
size="m"
type="copyClipboard"
>
<EuiIconEmpty
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
focusable="false"
style={null}
>
<svg
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
focusable="false"
height={16}
style={null}
viewBox="0 0 16 16"
width={16}
xmlns="http://www.w3.org/2000/svg"
/>
</EuiIconEmpty>
</EuiIcon>
</button>
</EuiButtonIcon>
</span>
</EuiToolTip>
</EuiCopy>
<EuiButtonEmpty
aria-label="View the project at this commit"
className="commit__diff-link"
href="#/github.com/elastic/code/tree/4ba67b8"
>
<a
aria-label="View the project at this commit"
className="euiButtonEmpty euiButtonEmpty--primary commit__diff-link"
href="#/github.com/elastic/code/tree/4ba67b8"
rel="noreferrer"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
<EuiText
size="s"
>
<div
className="euiText euiText--small"
>
4ba67b8
</div>
</EuiText>
</span>
</span>
</a>
</EuiButtonEmpty>
<EuiButtonIcon
aria-label="View the project at this commit"
href="#/github.com/elastic/code/tree/4ba67b8"
iconType="editorCodeBlock"
>
<a
aria-label="View the project at this commit"
className="euiButtonIcon euiButtonIcon--primary"
href="#/github.com/elastic/code/tree/4ba67b8"
rel="noreferrer"
>
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
size="m"
type="editorCodeBlock"
>
<EuiIconEmpty
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
focusable="false"
style={null}
>
<svg
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
focusable="false"
height={16}
style={null}
viewBox="0 0 16 16"
width={16}
xmlns="http://www.w3.org/2000/svg"
/>
</EuiIconEmpty>
</EuiIcon>
</a>
</EuiButtonIcon>
</div>
</CommitActions>
</div>
</EuiFlexGroup>
</div>
</EuiPanel>
</Commit>
`;

View file

@ -1,36 +0,0 @@
/*
* 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 React from 'react';
import { mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import { Commit } from './commit';
import { CommitInfo } from '../../../model/commit';
describe('Commit component', () => {
test('renders correctly for a commit', () => {
const commitInfo: CommitInfo = {
updated: new Date(Date.UTC(2222, 10, 11)),
message: 'This is my commit message\n\nThis is the description',
author: 'author',
committer: 'committer',
id: '4ba67b8',
parents: ['9817575'],
treeId: '96440ceb55e04a99d33c1c8ee021400a680fbf74',
};
const wrapper = mount(
<Commit
showRepoLink={false}
commit={commitInfo}
date="11/11/2222"
repoUri="github.com/elastic/code"
/>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
});

View file

@ -1,129 +0,0 @@
/*
* 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 React from 'react';
import {
EuiButtonIcon,
EuiButtonEmpty,
EuiFlexGroup,
EuiLink,
EuiPanel,
EuiText,
EuiTextColor,
EuiCopy,
EuiTitle,
} from '@elastic/eui';
import chrome from 'ui/chrome';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { CommitInfo } from '../../../model/commit';
import { RepositoryUtils } from '../../../common/repository_utils';
import { parseCommitMessage } from '../../../common/commit_utils';
import { PathTypes } from '../../common/types';
const COMMIT_ID_LENGTH = 8;
const RepoLink = ({ repoUri }: { repoUri: string }) => {
const repoOrg = RepositoryUtils.orgNameFromUri(repoUri);
const repoName = RepositoryUtils.repoNameFromUri(repoUri);
const repoPath = `#/${repoUri}`;
return (
<>
<EuiLink href={repoPath}>
{repoOrg} / {repoName}
</EuiLink>
<EuiTextColor color="subdued" className="commit__inline-separator">
</EuiTextColor>
</>
);
};
interface ActionProps {
repoUri: string;
commitId: string;
}
const copyLabel = i18n.translate('xpack.code.commits.copyCommitAriaLabel', {
defaultMessage: 'Copy the full commit ID',
});
const revisionLinkLabel = i18n.translate('xpack.code.commits.revisionLinkAriaLabel', {
defaultMessage: 'View the project at this commit',
});
const getRevisionPath = (repoUri: string, commitId: string) => {
const diffPageEnabled = chrome.getInjected('codeDiffPageEnabled');
if (diffPageEnabled) {
return `#/${repoUri}/commit/${commitId}`;
} else {
return `#/${repoUri}/${PathTypes.tree}/${commitId}`;
}
};
const CommitActions = ({ commitId, repoUri }: ActionProps) => {
const revisionPath = getRevisionPath(repoUri, commitId);
return (
<div className="commit__actions">
<EuiCopy textToCopy={commitId}>
{copy => <EuiButtonIcon aria-label={copyLabel} onClick={copy} iconType="copyClipboard" />}
</EuiCopy>
<EuiButtonEmpty
className="commit__diff-link"
href={revisionPath}
aria-label={revisionLinkLabel}
>
<EuiText size="s">{commitId}</EuiText>
</EuiButtonEmpty>
<EuiButtonIcon
href={revisionPath}
aria-label={revisionLinkLabel}
iconType="editorCodeBlock"
/>
</div>
);
};
interface Props {
commit: CommitInfo;
date: string;
repoUri: string;
showRepoLink: boolean;
}
export const Commit = (props: Props) => {
const { date, commit, repoUri, showRepoLink } = props;
const { message, committer, id } = commit;
const { summary, body } = parseCommitMessage(message);
const commitId = id.substring(0, COMMIT_ID_LENGTH);
return (
<EuiPanel className="code-timeline__commit--root">
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween" alignItems="center">
<div className="eui-textTruncate">
<EuiTitle size="xxs" className="eui-textTruncate">
<h4>{summary}</h4>
</EuiTitle>
<EuiText size="xs" className="commit__metadata">
{showRepoLink && <RepoLink repoUri={repoUri} />}
<EuiTextColor color="subdued">
<FormattedMessage
id="xpack.code.commits.committedDescription"
defaultMessage="Committed on {date} by {committer}"
values={{ committer, date }}
/>
</EuiTextColor>
</EuiText>
<EuiText size="s">
<p className="eui-textTruncate">{body}</p>
</EuiText>
</div>
<CommitActions repoUri={repoUri} commitId={commitId} />
</EuiFlexGroup>
</EuiPanel>
);
};

View file

@ -1,53 +0,0 @@
/*
* 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 React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiTextColor } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { CommitInfo } from '../../../model/commit';
import { Commit } from './commit';
interface Props {
commits: CommitInfo[];
date: string;
repoUri: string;
}
export const CommitGroup = (props: Props) => {
const commitList = props.commits.map(commit => (
<Commit
commit={commit}
key={commit.id}
date={props.date}
repoUri={props.repoUri}
showRepoLink={false}
/>
));
return (
<div className="code-timeline__commit-container">
<EuiFlexGroup justifyContent="flexStart" gutterSize="s">
<EuiFlexItem grow={false}>
<div className="code-timeline__marker" />
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<h4>
<EuiTextColor color="subdued">
<FormattedMessage
id="xpack.code.mainPage.history.commitsOnTitle"
defaultMessage="Commits on {date}"
values={{ date: props.date }}
/>
</EuiTextColor>
</h4>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<div className="code-timeline">{commitList}</div>
</div>
);
};

View file

@ -1,99 +0,0 @@
/*
* 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 _ from 'lodash';
import moment from 'moment';
import React from 'react';
import { connect } from 'react-redux';
import { i18n } from '@kbn/i18n';
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { CommitGroup } from './group';
import { CommitInfo } from '../../../model/commit';
import { RootState } from '../../reducers';
import { hasMoreCommitsSelector, treeCommitsSelector } from '../../selectors';
import { fetchMoreCommits } from '../../actions';
const CommitHistoryLoading = () => (
<div className="codeLoader">
<EuiLoadingSpinner size="xl" />
</div>
);
const PageButtons = (props: { loading?: boolean; disabled: boolean; onClick: () => void }) => (
<EuiFlexGroup justifyContent="spaceAround">
<EuiFlexItem grow={false}>
<EuiButton
onClick={props.onClick}
iconType="arrowDown"
isLoading={props.loading}
isDisabled={props.disabled}
size="s"
>
<FormattedMessage id="xpack.code.mainPage.history.moreButtonLabel" defaultMessage="More" />
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
);
const commitDateFormatMap: { [key: string]: string } = {
en: 'MMM Do, YYYY',
'zh-cn': 'YYYY年MoDo',
};
export const CommitHistoryComponent = (props: {
commits: CommitInfo[];
repoUri: string;
header: React.ReactNode;
loadingCommits?: boolean;
showPagination?: boolean;
hasMoreCommit?: boolean;
fetchMoreCommits: any;
}) => {
const commits = _.groupBy(props.commits, commit => moment(commit.updated).format('YYYYMMDD'));
const commitDates = Object.keys(commits).sort((a, b) => b.localeCompare(a)); // sort desc
const locale = i18n.getLocale();
const commitDateFormat =
locale in commitDateFormatMap ? commitDateFormatMap[locale] : commitDateFormatMap.en;
const commitList = commitDates.map(cd => (
<CommitGroup
commits={commits[cd]}
date={moment(cd).format(commitDateFormat)}
key={cd}
repoUri={props.repoUri}
/>
));
return (
<div className="codeContainer__commitMessages">
<div className="codeHeader__commit">{props.header}</div>
{commitList}
{!props.showPagination && props.loadingCommits && <CommitHistoryLoading />}
{props.showPagination && (
<PageButtons
disabled={!props.hasMoreCommit || props.commits.length < 10}
onClick={() => props.fetchMoreCommits(props.repoUri)}
loading={props.loadingCommits}
/>
)}
</div>
);
};
const mapStateToProps = (state: RootState) => ({
file: state.file.file,
commits: treeCommitsSelector(state) || [],
loadingCommits: state.revision.loadingCommits,
hasMoreCommit: hasMoreCommitsSelector(state),
});
const mapDispatchToProps = {
fetchMoreCommits,
};
export const CommitHistory = connect(
mapStateToProps,
mapDispatchToProps
)(CommitHistoryComponent);

View file

@ -1,9 +0,0 @@
/*
* 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.
*/
export { Commit } from './commit';
export { CommitGroup } from './group';
export { CommitHistory, CommitHistoryComponent } from './history';

View file

@ -1,37 +0,0 @@
/*
* 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 React, { useState } from 'react';
import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
interface Props {
initialIsOpen: boolean;
title: React.ReactNode;
children: React.ReactNode;
className: string;
}
export const Accordion = (props: Props) => {
const [isOpen, setOpen] = useState(props.initialIsOpen);
return (
<EuiPanel paddingSize="none" className={props.className}>
<EuiFlexGroup
gutterSize="none"
justifyContent="spaceBetween"
alignItems="center"
onClick={() => {
setOpen(!isOpen);
}}
>
<EuiFlexItem>{props.title}</EuiFlexItem>
<EuiFlexItem grow={false} className="codeAccordionCollapse__icon">
<EuiIcon type={isOpen ? 'arrowDown' : 'arrowRight'} />
</EuiFlexItem>
</EuiFlexGroup>
<div hidden={!isOpen}>{props.children}</div>
</EuiPanel>
);
};

View file

@ -1,48 +0,0 @@
.codeDiffCommitSelectorsContainer {
border-left: $euiBorderThin solid $euiColorLightShade;
padding-left: $euiSize;
}
.codeDiffCommitMessage {
padding: $euiSizeS $euiSize;
}
.codeDiffChangedFiles {
color: $euiColorPrimary;
}
.codeDiffMetadata {
padding: 0 $euiSize $euiSizeS $euiSize;
}
.codeVisualizerIcon {
margin-right: $euiSizeS;
}
.codeDiffDeletion {
margin: 0 $euiSizeS;
background-color: $euiColorVis0;
}
.codeDiff__panel {
padding: 0 $euiSize;
}
.codeDiff__header {
border-bottom: $euiBorderThin solid $euiColorLightShade;
height: $euiSizeXXL;
padding: $euiSize;
}
.codeAccordionCollapse__icon {
margin: auto $euiSize auto 0;
cursor: pointer;
}
.codeViewFile__button {
height: $euiSizeL !important;
}
.codeAccordion {
margin-bottom: $euiSizeM;
}

View file

@ -1,177 +0,0 @@
/*
* 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 {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiText,
EuiBadge,
EuiNotificationBadge,
} from '@elastic/eui';
import React from 'react';
import { connect } from 'react-redux';
import { Link, RouteComponentProps, withRouter } from 'react-router-dom';
import { CommitDiff, FileDiff } from '../../../common/git_diff';
import { SearchScope, SearchOptions } from '../../../model';
import { changeSearchScope } from '../../actions';
import { RootState } from '../../reducers';
import { SearchBar } from '../search_bar';
import { DiffEditor } from './diff_editor';
import { Accordion } from './accordion';
const COMMIT_ID_LENGTH = 16;
interface Props extends RouteComponentProps<{ resource: string; org: string; repo: string }> {
commit: CommitDiff | null;
query: string;
onSearchScopeChanged: (s: SearchScope) => void;
repoScope: string[];
searchOptions: SearchOptions;
}
export enum DiffLayout {
Unified,
Split,
}
const Difference = (props: {
fileDiff: FileDiff;
repoUri: string;
revision: string;
initialIsOpen: boolean;
}) => (
<Accordion
initialIsOpen={props.initialIsOpen}
className="codeAccordion"
title={
<EuiFlexGroup
gutterSize="none"
alignItems="center"
justifyContent="spaceBetween"
className="codeDiff__header"
>
<EuiFlexItem>
<EuiFlexGroup gutterSize="none" alignItems="center">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="none">
<EuiNotificationBadge size="m">{props.fileDiff.additions}</EuiNotificationBadge>
<EuiNotificationBadge className="codeDiffDeletion" size="m">
{props.fileDiff.deletions}
</EuiNotificationBadge>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>{props.fileDiff.path}</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div
className="euiButton euiButton--primary euiButton--small codeViewFile__button"
role="button"
>
<span className="euiButton__content">
<Link to={`/${props.repoUri}/blob/${props.revision}/${props.fileDiff.path}`}>
View File
</Link>
</span>
</div>
</EuiFlexItem>
</EuiFlexGroup>
}
>
<DiffEditor
originCode={props.fileDiff.originCode!}
modifiedCode={props.fileDiff.modifiedCode!}
language={props.fileDiff.language!}
renderSideBySide={true}
/>
</Accordion>
);
export class DiffPage extends React.Component<Props> {
public state = {
diffLayout: DiffLayout.Split,
};
public setLayoutUnified = () => {
this.setState({ diffLayout: DiffLayout.Unified });
};
public setLayoutSplit = () => {
this.setState({ diffLayout: DiffLayout.Split });
};
public render() {
const DEFAULT_OPEN_FILE_DIFF_COUNT = 1;
const { commit, match } = this.props;
const { repo, org, resource } = match.params;
const repoUri = `${resource}/${org}/${repo}`;
if (!commit) {
return null;
}
const { additions, deletions, files } = commit;
const fileCount = files.length;
const diffs = commit.files.map((file, index) => (
<Difference
repoUri={repoUri}
revision={commit.commit.id}
fileDiff={file}
key={file.path}
initialIsOpen={index < DEFAULT_OPEN_FILE_DIFF_COUNT}
/>
));
return (
<div>
<SearchBar
query={this.props.query}
onSearchScopeChanged={this.props.onSearchScopeChanged}
searchOptions={this.props.searchOptions}
enableSubmitWhenOptionsChanged={false}
/>
<div className="codeDiffCommitMessage">
<EuiText size="s">{commit.commit.message}</EuiText>
</div>
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween" className="codeDiffMetadata">
<EuiFlexItem grow={false}>
<EuiText size="s">
<EuiIcon type="dataVisualizer" color="subdued" className="codeVisualizerIcon" />
Showing
<b className="codeDiffChangedFiles"> {fileCount} Changed files </b>
with
<b> {additions} additions</b> and <b>{deletions} deletions </b>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">
Committed by
<b> {commit.commit.committer} </b>
<EuiBadge color="hollow">{commit.commit.id.substr(0, COMMIT_ID_LENGTH)}</EuiBadge>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<div className="codeDiff__panel">{diffs}</div>
</div>
);
}
}
const mapStateToProps = (state: RootState) => ({
commit: state.commit.commit,
query: state.search.query,
repoScope: state.search.searchOptions.repoScope.map(r => r.uri),
searchOptions: state.search.searchOptions,
});
const mapDispatchToProps = {
onSearchScopeChanged: changeSearchScope,
};
export const Diff = withRouter(
connect(
mapStateToProps,
mapDispatchToProps
)(DiffPage)
);

View file

@ -1,68 +0,0 @@
/*
* 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 { editor } from 'monaco-editor';
import React from 'react';
import { MonacoDiffEditor } from '../../monaco/monaco_diff_editor';
interface Props {
originCode: string;
modifiedCode: string;
language: string;
renderSideBySide: boolean;
}
export class DiffEditor extends React.Component<Props> {
lineHeight = 18;
static linesCount(s: string = '') {
let count = 0;
let position = 0;
while (position !== -1) {
count++;
position = position + 1;
position = s.indexOf('\n', position);
}
return count;
}
private diffEditor: MonacoDiffEditor | null = null;
public mountDiffEditor = (container: HTMLDivElement) => {
this.diffEditor = new MonacoDiffEditor(
container,
this.props.originCode,
this.props.modifiedCode,
this.props.language,
this.props.renderSideBySide
);
this.diffEditor.init();
};
getEditorHeight = () => {
const originalLinesCount = DiffEditor.linesCount(this.props.originCode);
const modifiedLinesCount = DiffEditor.linesCount(this.props.modifiedCode);
return Math.min(Math.max(originalLinesCount, modifiedLinesCount) * this.lineHeight, 400);
};
public componentDidUpdate(prevProps: Props) {
if (prevProps.renderSideBySide !== this.props.renderSideBySide) {
this.updateLayout(this.props.renderSideBySide);
}
}
public updateLayout(renderSideBySide: boolean) {
this.diffEditor!.diffEditor!.updateOptions({ renderSideBySide } as editor.IDiffEditorOptions);
}
public render() {
return (
<div
id="diffEditor"
className="codeContainer__monaco"
ref={this.mountDiffEditor}
style={{ height: this.getEditorHeight() }}
/>
);
}
}

View file

@ -1,282 +0,0 @@
/*
* 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 { EuiFlexItem } from '@elastic/eui';
import { editor as editorInterfaces, IDisposable } from 'monaco-editor';
import React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { Hover, Position } from 'vscode-languageserver-protocol';
import { GitBlame } from '../../../common/git_blame';
import { closePanel, FetchFileResponse, hoverResult } from '../../actions';
import { MainRouteParams } from '../../common/types';
import { BlameWidget } from '../../monaco/blame/blame_widget';
import { monaco } from '../../monaco/monaco';
import { MonacoHelper } from '../../monaco/monaco_helper';
import { RootState } from '../../reducers';
import { refUrlSelector } from '../../selectors';
import { history } from '../../utils/url';
import { Modifier, Shortcut } from '../shortcuts';
import { ReferencesPanel } from './references_panel';
import { encodeRevisionString } from '../../../common/uri_util';
import { trackCodeUiMetric, METRIC_TYPE } from '../../services/ui_metric';
import { CodeUIUsageMetrics } from '../../../model/usage_telemetry_metrics';
export interface EditorActions {
closePanel(changeUrl: boolean): void;
hoverResult(hover: Hover): void;
}
interface Props {
hidden?: boolean;
file?: FetchFileResponse;
revealPosition?: Position;
panelShowing: boolean;
isPanelLoading: boolean;
panelContents: any[];
panelTitle: string;
hover?: Hover;
refUrl?: string;
blames: GitBlame[];
showBlame: boolean;
}
type IProps = Props & EditorActions & RouteComponentProps<MainRouteParams>;
export class EditorComponent extends React.Component<IProps> {
static defaultProps = {
hidden: false,
};
public blameWidgets: any;
private container: HTMLElement | undefined;
private monaco: MonacoHelper | undefined;
private editor: editorInterfaces.IStandaloneCodeEditor | undefined;
private lineDecorations: string[] | null = null;
private gutterClickHandler: IDisposable | undefined;
constructor(props: IProps, context: any) {
super(props, context);
}
registerGutterClickHandler = () => {
if (!this.gutterClickHandler) {
this.gutterClickHandler = this.editor!.onMouseDown(
(e: editorInterfaces.IEditorMouseEvent) => {
// track line number click count
trackCodeUiMetric(METRIC_TYPE.COUNT, CodeUIUsageMetrics.LINE_NUMBER_CLICK_COUNT);
const { resource, org, repo, revision, path, pathType } = this.props.match.params;
const queryString = this.props.location.search;
const repoUri = `${resource}/${org}/${repo}`;
if (e.target.type === monaco.editor.MouseTargetType.GUTTER_LINE_NUMBERS) {
const url = `${repoUri}/${pathType}/${encodeRevisionString(revision)}/${path}`;
const position = e.target.position || { lineNumber: 0, column: 0 };
history.push(`/${url}!L${position.lineNumber}:0${queryString}`);
}
this.monaco!.container.focus();
}
);
}
};
public componentDidMount(): void {
this.container = document.getElementById('mainEditor') as HTMLElement;
this.monaco = new MonacoHelper(this.container, this.props, this.props.location.search);
if (!this.props.revealPosition) {
this.monaco.clearLineSelection();
}
const { file } = this.props;
if (file && file.content) {
const { uri, path, revision } = file.payload;
this.loadText(file.content, uri, path, file.lang!, revision).then(() => {
if (this.props.revealPosition) {
this.revealPosition(this.props.revealPosition);
}
if (this.props.showBlame) {
this.loadBlame(this.props.blames);
}
});
}
}
public componentDidUpdate(prevProps: IProps) {
const { file } = this.props;
if (!file) {
return;
}
const { uri, path, revision } = file.payload;
const {
resource,
org,
repo,
revision: routeRevision,
path: routePath,
} = this.props.match.params;
const prevContent = prevProps.file && prevProps.file.content;
const qs = this.props.location.search;
if (!this.props.revealPosition && this.monaco) {
this.monaco.clearLineSelection();
}
if (prevContent !== file.content) {
this.loadText(file.content!, uri, path, file.lang!, revision).then(() => {
if (this.props.revealPosition) {
this.revealPosition(this.props.revealPosition);
}
});
} else if (
file.payload.uri === `${resource}/${org}/${repo}` &&
file.payload.revision === routeRevision &&
file.payload.path === routePath &&
prevProps.revealPosition !== this.props.revealPosition
) {
this.revealPosition(this.props.revealPosition);
}
if (this.monaco && qs !== prevProps.location.search) {
this.monaco.updateUrlQuery(qs);
}
if (this.editor) {
if (prevProps.showBlame !== this.props.showBlame && this.props.showBlame) {
this.editor.updateOptions({ lineDecorationsWidth: 316 });
this.loadBlame(this.props.blames);
} else if (!this.props.showBlame) {
this.destroyBlameWidgets();
this.editor.updateOptions({ lineDecorationsWidth: 16 });
}
if (prevProps.blames !== this.props.blames && this.props.showBlame) {
this.editor.updateOptions({ lineDecorationsWidth: 316 });
this.loadBlame(this.props.blames);
}
}
}
public componentWillUnmount() {
if (this.gutterClickHandler) {
this.gutterClickHandler.dispose();
}
this.monaco!.destroy();
}
public render() {
return (
<EuiFlexItem
data-test-subj="codeSourceViewer"
className="codeOverflowHidden"
grow={this.props.hidden ? false : 1}
hidden={this.props.hidden}
>
<Shortcut
keyCode="f"
help="With editor active Find in file"
linuxModifier={[Modifier.ctrl]}
macModifier={[Modifier.meta]}
winModifier={[Modifier.ctrl]}
/>
<div
tabIndex={0}
className="codeContainer__editor codeContainer__monaco"
id="mainEditor"
hidden={this.props.hidden}
/>
{this.renderReferences()}
</EuiFlexItem>
);
}
public loadBlame(blames: GitBlame[]) {
if (this.blameWidgets) {
this.destroyBlameWidgets();
}
if (!this.lineDecorations) {
this.lineDecorations = this.monaco!.editor!.deltaDecorations(
[],
[
{
range: new monaco.Range(1, 1, Infinity, 1),
options: { isWholeLine: true, linesDecorationsClassName: 'code-line-decoration' },
},
]
);
}
this.blameWidgets = blames.map((b, index) => {
return new BlameWidget(b, index === 0, this.monaco!.editor!);
});
}
public destroyBlameWidgets() {
if (this.blameWidgets) {
this.blameWidgets.forEach((bw: BlameWidget) => bw.destroy());
}
if (this.lineDecorations) {
this.monaco!.editor!.deltaDecorations(this.lineDecorations!, []);
this.lineDecorations = null;
}
this.blameWidgets = null;
}
private async loadText(text: string, repo: string, file: string, lang: string, revision: string) {
if (this.monaco) {
try {
await monaco.editor.colorize(text, lang, {});
} catch (e) {
// workaround a upstream issue: https://github.com/microsoft/monaco-editor/issues/134
lang = 'text';
}
this.editor = await this.monaco.loadFile(repo, file, text, lang, revision);
if (this.props.showBlame) {
this.editor.updateOptions({ lineDecorationsWidth: 316 });
this.loadBlame(this.props.blames);
}
this.registerGutterClickHandler();
}
}
private revealPosition(pos: Position | undefined) {
if (this.monaco) {
if (pos) {
this.monaco.revealPosition(pos.line, pos.character);
} else {
this.monaco.clearLineSelection();
}
}
}
private renderReferences() {
return (
this.props.panelShowing && (
<ReferencesPanel
onClose={() => this.props.closePanel(true)}
references={this.props.panelContents}
isLoading={this.props.isPanelLoading}
title={this.props.panelTitle}
refUrl={this.props.refUrl}
/>
)
);
}
}
const mapStateToProps = (state: RootState) => ({
file: state.file.file,
panelShowing: state.editor.panelShowing,
isPanelLoading: state.editor.loading,
panelContents: state.editor.panelContents,
panelTitle: state.editor.panelTitle,
hover: state.editor.hover,
refUrl: refUrlSelector(state),
revealPosition: state.editor.revealPosition,
blames: state.blame.blames,
});
const mapDispatchToProps = {
closePanel,
hoverResult,
};
export const Editor = withRouter(
connect(
mapStateToProps,
mapDispatchToProps
)(EditorComponent)
);

View file

@ -1,36 +0,0 @@
.code-editor-references-panel {
position: relative;
max-height: 50vh;
display: flex;
flex-direction: column;
box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.05), 0px -4px 4px rgba(0, 0, 0, 0.03),
0px -6px 12px rgba(0, 0, 0, 0.05), 0px -12px 24px rgba(0, 0, 0, 0.05);
}
.code-editor-references-panel.expanded {
position: relative;
flex-grow: 10;
max-height: 95%;
height: 95%;
}
.code-editor-reference-accordion-button {
font-size: 13px;
}
.expandButton {
position: absolute;
top: -1 * $euiSize;
right: $euiSize + 1px;
background: $euiColorLightestShade;
border: $euiBorderThin;
border-bottom: 0;
height: 0;
min-height: $euiSize;
padding: 0;
border-radius: $euiSizeXS $euiSizeXS 0 0;
}
.referencesPanel__code-block {
margin-bottom: $euiSizeXL;
}

Some files were not shown because too many files have changed in this diff Show more