* 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
|
@ -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[]
|
|
@ -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[]
|
|
@ -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[]
|
|
@ -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.
|
|
@ -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[]
|
|
@ -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 don’t 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[]
|
|
@ -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[]
|
|
@ -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[]
|
Before Width: | Height: | Size: 611 KiB |
Before Width: | Height: | Size: 531 KiB |
Before Width: | Height: | Size: 628 KiB |
Before Width: | Height: | Size: 267 KiB |
Before Width: | Height: | Size: 217 KiB |
Before Width: | Height: | Size: 457 KiB |
Before Width: | Height: | Size: 901 KiB |
Before Width: | Height: | Size: 328 KiB |
Before Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 698 KiB |
Before Width: | Height: | Size: 382 KiB |
Before Width: | Height: | Size: 607 KiB |
|
@ -1,45 +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 you’d 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.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`
|
|
@ -277,7 +277,7 @@ setting specifies the port to use.
|
|||
|
||||
`server.rewriteBasePath:`:: *Default: false* Deprecated setting that specifies if Kibana should
|
||||
rewrite requests that are prefixed with `server.basePath`, or require that they
|
||||
are rewritten by your reverse proxy.
|
||||
are rewritten by your reverse proxy.
|
||||
|
||||
`server.socketTimeout:`:: *Default: "120000"* The number of milliseconds to wait before closing an
|
||||
inactive socket.
|
||||
|
@ -326,7 +326,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[]
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 };
|
|
@ -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';
|
|
@ -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
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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',
|
||||
}
|
|
@ -1,55 +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 with both whitelisted host and protocol
|
||||
expect(
|
||||
validateGitUrl('https://github.com/elastic/elasticsearch.git', ['github.com'], ['https'])
|
||||
).toBeTruthy();
|
||||
});
|
|
@ -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 { 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 {
|
||||
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;
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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',
|
||||
];
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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']);
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
|
@ -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, ':');
|
||||
};
|
|
@ -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(false),
|
||||
}).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);
|
||||
plugin.setup(coreSetup);
|
||||
|
||||
// @ts-ignore
|
||||
const kbnServer = this.kbnServer;
|
||||
kbnServer.ready().then(async () => {
|
||||
await plugin.start(coreSetup);
|
||||
});
|
||||
|
||||
server.events.on('stop', async () => {
|
||||
await plugin.stop();
|
||||
});
|
||||
},
|
||||
});
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"id": "code",
|
||||
"version": "kibana",
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["elasticsearch"]
|
||||
}
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -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',
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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';
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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',
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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');
|
|
@ -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);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 (
|
||||
<div 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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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);
|
|
@ -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);
|
|
@ -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"
|
||||
/>
|
||||
|
||||
<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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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="We’ve 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);
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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';
|
|
@ -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>
|
||||
`;
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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);
|
|
@ -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';
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
);
|
|
@ -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() }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
);
|
|
@ -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;
|
||||
}
|