diff --git a/x-pack/legacy/plugins/code/public/components/admin_page/project_tab.tsx b/x-pack/legacy/plugins/code/public/components/admin_page/project_tab.tsx index d5e0f4a52d12d..e6dcdcd3f2db9 100644 --- a/x-pack/legacy/plugins/code/public/components/admin_page/project_tab.tsx +++ b/x-pack/legacy/plugins/code/public/components/admin_page/project_tab.tsx @@ -6,23 +6,13 @@ import { EuiButton, - EuiButtonEmpty, - EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiForm, EuiFormRow, EuiGlobalToastList, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiOverlayMask, EuiSpacer, EuiSuperSelect, EuiText, - EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -37,6 +27,7 @@ import { ToastType } from '../../reducers/repository_management'; import { isImportRepositoryURLInvalid } from '../../utils/url'; import { ProjectItem } from './project_item'; import { ProjectSettings } from './project_settings'; +import { ImportModal } from '../apm_demo/import_modal'; enum SortOptionsValue { AlphabeticalAsc = 'alphabetical_asc', @@ -167,70 +158,21 @@ class CodeProjectTab extends React.PureComponent { } }; - public updateIsInvalid = () => { - this.setState({ isInvalid: isImportRepositoryURLInvalid(this.state.repoURL) }); - }; - public renderImportModal = () => { - return ( - - - - - - - - - -

- -

-
- - - - - -
- - - - - - - - -
-
- ); + const { isInvalid, repoURL, showImportProjectModal } = this.state; + + if (showImportProjectModal) { + return ( + + ); + } }; public setSortOption = (value: string) => { @@ -240,7 +182,6 @@ class CodeProjectTab extends React.PureComponent { public render() { const { projects, status, toastMessage, showToast, toastType } = this.props; const projectsCount = projects.length; - const modal = this.state.showImportProjectModal && this.renderImportModal(); const sortedProjects = projects.sort(sortFunctionsFactory(status)[this.state.sortOption]); const repoList = sortedProjects.map((repo: Repository) => ( @@ -319,7 +260,7 @@ class CodeProjectTab extends React.PureComponent { {repoList} - {modal} + {this.renderImportModal()} {settings} ); diff --git a/x-pack/legacy/plugins/code/public/components/apm_demo/code_integration.tsx b/x-pack/legacy/plugins/code/public/components/apm_demo/code_integration.tsx new file mode 100644 index 0000000000000..6e37539f986e0 --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/apm_demo/code_integration.tsx @@ -0,0 +1,77 @@ +/* + * 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 styled from 'styled-components'; +import { EuiButtonEmpty, EuiPopover, EuiText } from '@elastic/eui'; + +import { history } from '../../utils/url'; +import { RepoSelector } from './repo_selector'; +import { Frame } from './data'; + +interface Props { + onRepoSelect: (repo: string) => void; + onImportSuccess: (repo: string) => void; + isIntegrated: boolean; + frame: Frame; + repos: string[]; +} + +const PopoverContent = styled.div` + margin-bottom: 1rem; + width: 300px; +`; + +export const CodeIntegration = ({ + frame, + isIntegrated, + onRepoSelect, + onImportSuccess, + repos, +}: Props) => { + const [showSelector, setShowSelector] = useState(false); + + const handleClick = () => { + if (isIntegrated) { + const fileLinkUrl = `/${frame.uri}/blob/HEAD/${frame.filePath}`; + history.push(fileLinkUrl); + } else { + setShowSelector(true); + } + }; + + const handleSelect = (codeId: string) => { + onRepoSelect(codeId); + setShowSelector(false); + // TODO: show success + }; + + const button = ( + + View in Code + + ); + + return ( + setShowSelector(false)} + > + + +

No repository mapping found

+

+ We can't find the mapping between service and the source code. Select the repository or + import a new one. +

+
+
+ +
+ ); +}; diff --git a/x-pack/legacy/plugins/code/public/components/apm_demo/data.ts b/x-pack/legacy/plugins/code/public/components/apm_demo/data.ts new file mode 100644 index 0000000000000..d45f3f868f011 --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/apm_demo/data.ts @@ -0,0 +1,471 @@ +export type Frame = { + uri: string; + filePath: string; + language?: string; + hits: number; + compositeContent: { + content: string; + lineMapping: string[]; + ranges: { + startColumn: number; + startLineNumber: number; + endColumn: number; + endLineNumber: number; + }[]; + }; +}; + +export const frames: Frame[] = [ + { + uri: 'github.com/rylnd/ringside', + filePath: 'stories/ringside.story.tsx', + language: 'typescript', + hits: 11, + compositeContent: { + content: + "\nimport { interpolateRainbow } from 'd3-scale-chromatic';\n\nimport { Ringside } from '../src';\nimport { XAlignment, YAlignment, XBasis, YBasis } from '../src/types';\n\nlet ringside: Ringside;\n\nconst enumKeys: (e: any) => string[] = e =>\n\n\nconst color = position => {\n const combos = ringside.positions().map(p => JSON.stringify(p));\n const hash = combos.indexOf(JSON.stringify(position)) / combos.length;\n\n\n};\n\nconst Stories = storiesOf('Ringside', module).addDecorator(withKnobs);\n\nStories.add('Ringside', () => {\n", + lineMapping: [ + '..', + '5', + '6', + '7', + '8', + '9', + '10', + '11', + '12', + '..', + '14', + '15', + '16', + '17', + '18', + '..', + '20', + '21', + '22', + '23', + '24', + '..', + ], + ranges: [ + { + startColumn: 10, + startLineNumber: 4, + endColumn: 18, + endLineNumber: 4, + }, + { + startColumn: 5, + startLineNumber: 7, + endColumn: 13, + endLineNumber: 7, + }, + { + startColumn: 15, + startLineNumber: 7, + endColumn: 23, + endLineNumber: 7, + }, + { + startColumn: 18, + startLineNumber: 13, + endColumn: 26, + endLineNumber: 13, + }, + { + startColumn: 28, + startLineNumber: 19, + endColumn: 36, + endLineNumber: 19, + }, + { + startColumn: 14, + startLineNumber: 21, + endColumn: 22, + endLineNumber: 21, + }, + ], + }, + }, + { + uri: 'github.com/rylnd/ringside', + filePath: 'src/ringside.ts', + language: 'typescript', + hits: 4, + compositeContent: { + content: + "\nimport { fitsInside, fitsOutside } from './fitting';\n\nexport interface RingsideInterface {\n positions(): FittedPosition[];\n}\n\nclass Ringside implements RingsideInterface {\n readonly innerBounds: FullRect;\n readonly outerBounds: FullRect;\n\n}\n\nexport default Ringside;\n", + lineMapping: [ + '..', + '13', + '14', + '15', + '16', + '17', + '18', + '19', + '20', + '21', + '..', + '67', + '68', + '69', + '70', + ], + ranges: [ + { + startColumn: 18, + startLineNumber: 4, + endColumn: 26, + endLineNumber: 4, + }, + { + startColumn: 7, + startLineNumber: 8, + endColumn: 15, + endLineNumber: 8, + }, + { + startColumn: 27, + startLineNumber: 8, + endColumn: 35, + endLineNumber: 8, + }, + { + startColumn: 16, + startLineNumber: 14, + endColumn: 24, + endLineNumber: 14, + }, + ], + }, + }, + { + uri: 'github.com/rylnd/ringside', + filePath: 'test/ringside.test.ts', + language: 'typescript', + hits: 7, + compositeContent: { + content: + "import { Ringside } from '../src';\n\ndescribe('Ringside', () => {\n let inner;\n let outer;\n let height;\n let width;\n let ringside: Ringside;\n\n beforeEach(() => {\n\n width = 50;\n\n ringside = new Ringside(inner, outer, height, width);\n });\n\n", + lineMapping: [ + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '10', + '..', + '14', + '15', + '16', + '17', + '18', + '..', + ], + ranges: [ + { + startColumn: 10, + startLineNumber: 1, + endColumn: 18, + endLineNumber: 1, + }, + { + startColumn: 11, + startLineNumber: 3, + endColumn: 19, + endLineNumber: 3, + }, + { + startColumn: 7, + startLineNumber: 8, + endColumn: 15, + endLineNumber: 8, + }, + { + startColumn: 17, + startLineNumber: 8, + endColumn: 25, + endLineNumber: 8, + }, + { + startColumn: 5, + startLineNumber: 14, + endColumn: 13, + endLineNumber: 14, + }, + { + startColumn: 20, + startLineNumber: 14, + endColumn: 28, + endLineNumber: 14, + }, + ], + }, + }, + { + uri: 'github.com/rylnd/ringside', + filePath: 'src/index.ts', + language: 'typescript', + hits: 2, + compositeContent: { + content: + "export { default, default as Ringside } from './ringside';\nexport { default as Position } from './position';\nexport { default as Grid } from './grid';\n", + lineMapping: ['1', '2', '3', '..'], + ranges: [ + { + startColumn: 30, + startLineNumber: 1, + endColumn: 38, + endLineNumber: 1, + }, + { + startColumn: 49, + startLineNumber: 1, + endColumn: 57, + endLineNumber: 1, + }, + ], + }, + }, + { + uri: 'github.com/rylnd/ringside', + filePath: 'CHANGELOG.md', + language: 'markdown', + hits: 15, + compositeContent: { + content: + '\n\n\n## [1.0.1](https://github.com/rylnd/ringside/compare/v1.0.0...v1.0.1) (2018-12-16)\n\n\n### Bug Fixes\n\n* **vulnerabilities:** Update dependencies ([246eb4f](https://github.com/rylnd/ringside/commit/246eb4f))\n* Upgraded everything ([aa44f17](https://github.com/rylnd/ringside/commit/aa44f17))\n\n\n\n\n# [1.0.0](https://github.com/rylnd/ringside/compare/v0.2.0...v1.0.0) (2018-12-14)\n\n\n\n\n# [0.2.0](https://github.com/rylnd/ringside/compare/v0.1.1...v0.2.0) (2017-12-20)\n\n\n', + lineMapping: [ + '..', + '4', + '5', + '6', + '7', + '8', + '9', + '10', + '11', + '12', + '13', + '14', + '15', + '16', + '17', + '18', + '19', + '20', + '21', + '22', + '23', + '24', + '..', + ], + ranges: [ + { + startColumn: 37, + startLineNumber: 4, + endColumn: 45, + endLineNumber: 4, + }, + { + startColumn: 80, + startLineNumber: 9, + endColumn: 88, + endLineNumber: 9, + }, + { + startColumn: 59, + startLineNumber: 10, + endColumn: 67, + endLineNumber: 10, + }, + { + startColumn: 36, + startLineNumber: 15, + endColumn: 44, + endLineNumber: 15, + }, + { + startColumn: 36, + startLineNumber: 20, + endColumn: 44, + endLineNumber: 20, + }, + ], + }, + }, + { + uri: 'github.com/rylnd/ringside', + filePath: 'README.md', + language: 'markdown', + hits: 10, + compositeContent: { + content: + "# ringside [![CircleCI](https://circleci.com/gh/rylnd/ringside.svg?style=svg)](https://circleci.com/gh/rylnd/ringside)\n\nA library that determines the fit and positioning of a rectangle relative to inner and outer bounds.\n\n\n```bash\nnpm install ringside\n```\n\n\n\n```jsx\nimport Ringside from 'ringside';\n\n// define our target tooltip size\n", + lineMapping: [ + '1', + '2', + '3', + '..', + '6', + '7', + '8', + '9', + '10', + '..', + '14', + '15', + '16', + '17', + '18', + '..', + ], + ranges: [ + { + startColumn: 3, + startLineNumber: 1, + endColumn: 11, + endLineNumber: 1, + }, + { + startColumn: 55, + startLineNumber: 1, + endColumn: 63, + endLineNumber: 1, + }, + { + startColumn: 110, + startLineNumber: 1, + endColumn: 118, + endLineNumber: 1, + }, + { + startColumn: 13, + startLineNumber: 7, + endColumn: 21, + endLineNumber: 7, + }, + { + startColumn: 8, + startLineNumber: 13, + endColumn: 16, + endLineNumber: 13, + }, + { + startColumn: 23, + startLineNumber: 13, + endColumn: 31, + endLineNumber: 13, + }, + ], + }, + }, + { + uri: 'github.com/rylnd/ringside', + filePath: 'package.json', + language: undefined, + hits: 4, + compositeContent: { + content: + '{\n "name": "ringside",\n "version": "1.0.1",\n "description": "Positions a rectangle between inner and outer bounds",\n\n "repository": {\n "type": "git",\n "url": "git+https://github.com/rylnd/ringside.git"\n },\n "bugs": {\n "url": "https://github.com/rylnd/ringside/issues"\n },\n "homepage": "https://github.com/rylnd/ringside#readme"\n}\n', + lineMapping: [ + '1', + '2', + '3', + '4', + '..', + '63', + '64', + '65', + '66', + '67', + '68', + '69', + '70', + '71', + '72', + ], + ranges: [ + { + startColumn: 12, + startLineNumber: 2, + endColumn: 20, + endLineNumber: 2, + }, + { + startColumn: 42, + startLineNumber: 8, + endColumn: 50, + endLineNumber: 8, + }, + { + startColumn: 38, + startLineNumber: 11, + endColumn: 46, + endLineNumber: 11, + }, + { + startColumn: 41, + startLineNumber: 13, + endColumn: 49, + endLineNumber: 13, + }, + ], + }, + }, + { + uri: 'github.com/rylnd/ringside', + filePath: 'webpack.config.js', + language: 'javascript', + hits: 2, + compositeContent: { + content: + "\n target: 'node',\n output: {\n library: 'ringside',\n libraryTarget: 'umd',\n filename: 'index.js',\n\n target: 'web',\n output: {\n library: 'ringside',\n libraryTarget: 'umd',\n filename: 'index.browser.js',\n", + lineMapping: ['..', '35', '36', '37', '38', '39', '..', '46', '47', '48', '49', '50', '..'], + ranges: [ + { + startColumn: 15, + startLineNumber: 4, + endColumn: 23, + endLineNumber: 4, + }, + { + startColumn: 15, + startLineNumber: 10, + endColumn: 23, + endLineNumber: 10, + }, + ], + }, + }, + { + uri: 'github.com/rylnd/ringside', + filePath: 'package-lock.json', + language: undefined, + hits: 1, + compositeContent: { + content: '{\n "name": "ringside",\n "version": "1.0.1",\n "lockfileVersion": 1,\n', + lineMapping: ['1', '2', '3', '4', '..'], + ranges: [ + { + startColumn: 12, + startLineNumber: 2, + endColumn: 20, + endLineNumber: 2, + }, + ], + }, + }, +]; + +export const repos = [ + 'https://github.com/a/repo', + 'https://github.com/another/repo', + 'https://github.com/also/a_repo', +]; diff --git a/x-pack/legacy/plugins/code/public/components/apm_demo/import_modal.tsx b/x-pack/legacy/plugins/code/public/components/apm_demo/import_modal.tsx new file mode 100644 index 0000000000000..de526d4046e28 --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/apm_demo/import_modal.tsx @@ -0,0 +1,100 @@ +/* + * 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, { ChangeEvent } from 'react'; + +import { + EuiButton, + EuiButtonEmpty, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +interface Props { + isInvalid: boolean; + isLoading: boolean; + onChange: (e: ChangeEvent) => void; + onClose: () => void; + onSubmit: () => void; + value: string; +} + +export const ImportModal = ({ + isInvalid, + isLoading, + onChange, + onClose, + onSubmit, + value, +}: Props) => ( + + + + + + + + + +

+ +

+
+ + + + + +
+ + + + + + + + +
+
+); diff --git a/x-pack/legacy/plugins/code/public/components/apm_demo/index.tsx b/x-pack/legacy/plugins/code/public/components/apm_demo/index.tsx new file mode 100644 index 0000000000000..c1517832bb75a --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/apm_demo/index.tsx @@ -0,0 +1,58 @@ +/* + * 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 styled from 'styled-components'; + +import { CodeBlock } from '../codeblock/codeblock'; +import { CodeIntegration } from './code_integration'; +import { Frame, frames, repos } from './data'; + +const Container = styled.div` + padding: 1rem; +`; + +const AlignRight = styled.div` + text-align: right; +`; + +const associateToService = (frame: Frame) => (repo: string) => + alert(`repo ${repo} associated with frame ${JSON.stringify(frame)}`); +const handleImport = (repo: string) => alert(`import done: ${repo}`); + +export const ApmDemo = () => ( + + {frames.map((frame, i) => { + const { uri, filePath, compositeContent, language } = frame; + const { content, lineMapping, ranges } = compositeContent; + const isIntegrated = i % 2 === 0; + const key = `${uri}-${filePath}`; + + return ( +
+ + + + lineMapping[l - 1]} + onClick={() => {}} + /> +
+ ); + })} +
+); diff --git a/x-pack/legacy/plugins/code/public/components/apm_demo/repo_selector.tsx b/x-pack/legacy/plugins/code/public/components/apm_demo/repo_selector.tsx new file mode 100644 index 0000000000000..8aeba2b600ed2 --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/apm_demo/repo_selector.tsx @@ -0,0 +1,82 @@ +/* + * 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 { EuiButton, EuiSelect } from '@elastic/eui'; +import { ImportModal } from './import_modal'; +import { isImportRepositoryURLInvalid } from '../../utils/url'; + +interface Props { + onSelect: (repo: string) => void; + onImport: (repo: string) => void; + repos: string[]; +} + +const placeHolderOption = { value: 'select_new', text: 'Select' }; +const importNewOption = { value: 'import_new', text: 'Import new' }; +const importStub: (repo: string) => Promise = repo => + new Promise(resolve => setTimeout(() => resolve(repo), 5000)); + +export const RepoSelector = ({ onImport, onSelect, repos: _repos }: Props) => { + const [selectedValue, setSelectedValue] = useState(placeHolderOption.value); + const [newRepo, setNewRepo] = useState(''); + const [isInvalid, setIsInvalid] = useState(false); + const [showModal, setShowModal] = useState(false); + + const repos = newRepo ? [..._repos, newRepo] : _repos; + const selectedRepo = repos.find(repo => repo === selectedValue); + + const options = [ + placeHolderOption, + ...repos.map(repo => ({ value: repo, text: repo })), + importNewOption, + ]; + + const handleNewRepoChange = ({ target: { value } }: React.ChangeEvent) => { + setIsInvalid(isImportRepositoryURLInvalid(value)); + setNewRepo(value); + }; + + const handleChange = ({ target: { value } }: React.ChangeEvent) => { + setSelectedValue(value); + + if (value === 'import_new') { + setShowModal(true); + } + }; + + const handleSave = () => selectedRepo && onSelect(selectedRepo); + + const handleImportSubmit = () => { + setSelectedValue(newRepo); + importStub(newRepo).then(onImport); + setShowModal(false); + }; + + const handleClose = () => { + setSelectedValue(placeHolderOption.value); + setShowModal(false); + }; + + return ( + <> + + + Save Mapping + + {showModal && ( + + )} + + ); +}; diff --git a/x-pack/legacy/plugins/code/public/components/app.tsx b/x-pack/legacy/plugins/code/public/components/app.tsx index 54363213bcad5..395bbf991c3cf 100644 --- a/x-pack/legacy/plugins/code/public/components/app.tsx +++ b/x-pack/legacy/plugins/code/public/components/app.tsx @@ -17,6 +17,7 @@ import { NotFound } from './main/not_found'; import { Route } from './route'; import * as ROUTES from './routes'; import { Search } from './search_page/search'; +import { ApmDemo } from './apm_demo'; const RooComponent = (props: { setupOk?: boolean }) => { if (props.setupOk) { @@ -45,6 +46,7 @@ export const App = () => { + diff --git a/x-pack/legacy/plugins/code/public/components/routes.ts b/x-pack/legacy/plugins/code/public/components/routes.ts index 0d4ccf4e5e826..0d1b0124b9c5e 100644 --- a/x-pack/legacy/plugins/code/public/components/routes.ts +++ b/x-pack/legacy/plugins/code/public/components/routes.ts @@ -15,3 +15,4 @@ export const REPO = `/:resource/:org/:repo`; export const MAIN_ROOT = `/:resource/:org/:repo/${pathTypes}/:revision`; export const ADMIN = '/admin'; export const SEARCH = '/search'; +export const APM_DEMO = '/apm_demo';