mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
Merge pull request #1196 from scm-manager/feature/annotate
Annotate View
This commit is contained in:
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Added links in diff views to expand the gaps between "hunks" ([#1178](https://github.com/scm-manager/scm-manager/pull/1178))
|
||||
- Show commit contributors in table on changeset details view ([#1169](https://github.com/scm-manager/scm-manager/pull/1169))
|
||||
- Show changeset parents on changeset details view ([#1189](https://github.com/scm-manager/scm-manager/pull/1189))
|
||||
- Annotate view to display commit metadata for each line of a file ([#1196](https://github.com/scm-manager/scm-manager/pull/1196))
|
||||
|
||||
### Fixed
|
||||
- Avoid caching of detected browser language ([#1176](https://github.com/scm-manager/scm-manager/pull/1176))
|
||||
|
||||
@@ -71,13 +71,12 @@ public class VndMediaType {
|
||||
@SuppressWarnings("squid:S2068")
|
||||
public static final String PASSWORD_OVERWRITE = PREFIX + "passwordOverwrite" + SUFFIX;
|
||||
public static final String PERMISSION_COLLECTION = PREFIX + "permissionCollection" + SUFFIX;
|
||||
public static final String MERGE_RESULT = PREFIX + "mergeResult" + SUFFIX;
|
||||
public static final String MERGE_COMMAND = PREFIX + "mergeCommand" + SUFFIX;
|
||||
|
||||
public static final String NAMESPACE_STRATEGIES = PREFIX + "namespaceStrategies" + SUFFIX;
|
||||
|
||||
public static final String ME = PREFIX + "me" + SUFFIX;
|
||||
public static final String SOURCE = PREFIX + "source" + SUFFIX;
|
||||
public static final String ANNOTATE = PREFIX + "annotate" + SUFFIX;
|
||||
public static final String ERROR_TYPE = PREFIX + "error" + SUFFIX;
|
||||
|
||||
public static final String REPOSITORY_ROLE = PREFIX + "repositoryRole" + SUFFIX;
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@types/react-select": "^2.0.19",
|
||||
"@types/react-syntax-highlighter": "^11.0.1",
|
||||
"@types/storybook__addon-storyshots": "^5.1.1",
|
||||
"@types/styled-components": "^4.1.19",
|
||||
"@types/styled-components": "^5.1.0",
|
||||
"enzyme-context": "^1.1.2",
|
||||
"enzyme-context-react-router-4": "^2.0.0",
|
||||
"fetch-mock": "^7.5.1",
|
||||
@@ -60,7 +60,7 @@
|
||||
"react-markdown": "^4.0.6",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-select": "^2.1.2",
|
||||
"react-syntax-highlighter": "^11.0.2"
|
||||
"react-syntax-highlighter": "https://github.com/conorhastings/react-syntax-highlighter#08bcf49b1aa7877ce94f7208e73dfa6bef8b26e7"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
|
||||
@@ -24,25 +24,41 @@
|
||||
import React from "react";
|
||||
import DateFromNow from "./DateFromNow";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import DateShort from "./DateShort";
|
||||
import styled from "styled-components";
|
||||
|
||||
const baseProps = {
|
||||
timeZone: "Europe/Berlin",
|
||||
baseDate: "2019-10-12T13:56:42+02:00"
|
||||
};
|
||||
|
||||
storiesOf("DateFromNow", module).add("Default", () => (
|
||||
<div>
|
||||
const dates = [
|
||||
"2009-06-30T18:30:00+02:00",
|
||||
"2019-06-30T18:30:00+02:00",
|
||||
"2019-10-12T13:56:40+02:00",
|
||||
"2019-10-11T13:56:40+02:00"
|
||||
];
|
||||
|
||||
const Wrapper = styled.div`
|
||||
padding: 2rem;
|
||||
`;
|
||||
|
||||
storiesOf("Date", module)
|
||||
.add("Date from now", () => (
|
||||
<Wrapper>
|
||||
{dates.map(d => (
|
||||
<p>
|
||||
<DateFromNow date="2009-06-30T18:30:00+02:00" {...baseProps} />
|
||||
<DateFromNow date={d} {...baseProps} />
|
||||
</p>
|
||||
))}
|
||||
</Wrapper>
|
||||
))
|
||||
.add("Short", () => (
|
||||
<Wrapper>
|
||||
{dates.map(d => (
|
||||
<p>
|
||||
<DateFromNow date="2019-06-30T18:30:00+02:00" {...baseProps} />
|
||||
<DateShort date={d} {...baseProps} />
|
||||
</p>
|
||||
<p>
|
||||
<DateFromNow date="2019-10-12T13:56:40+02:00" {...baseProps} />
|
||||
</p>
|
||||
<p>
|
||||
<DateFromNow date="2019-10-11T13:56:40+02:00" {...baseProps} />
|
||||
</p>
|
||||
</div>
|
||||
));
|
||||
))}
|
||||
</Wrapper>
|
||||
));
|
||||
31
scm-ui/ui-components/src/DateElement.ts
Normal file
31
scm-ui/ui-components/src/DateElement.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import styled from "styled-components";
|
||||
|
||||
const DateElement = styled.time`
|
||||
border-bottom: 1px dotted rgba(219, 219, 219);
|
||||
cursor: help;
|
||||
`;
|
||||
|
||||
export default DateElement;
|
||||
@@ -21,109 +21,26 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React from "react";
|
||||
import { withTranslation, WithTranslation } from "react-i18next";
|
||||
import { formatDistance, format, parseISO, Locale } from "date-fns";
|
||||
import { enUS, de, es } from "date-fns/locale";
|
||||
import styled from "styled-components";
|
||||
|
||||
type LocaleMap = {
|
||||
[key: string]: Locale;
|
||||
import React, { FC } from "react";
|
||||
import useDateFormatter, { DateProps } from "./useDateFormatter";
|
||||
import DateElement from "./DateElement";
|
||||
|
||||
type Props = DateProps & {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
type DateInput = Date | string;
|
||||
|
||||
export const supportedLocales: LocaleMap = {
|
||||
enUS,
|
||||
en: enUS,
|
||||
de,
|
||||
es
|
||||
};
|
||||
|
||||
type Props = WithTranslation & {
|
||||
date?: DateInput;
|
||||
timeZone?: string;
|
||||
|
||||
/**
|
||||
* baseDate is the date from which the distance is calculated,
|
||||
* default is the current time (new Date()). This property
|
||||
* is required to keep snapshots tests green over the time on
|
||||
* ci server.
|
||||
*/
|
||||
baseDate?: DateInput;
|
||||
};
|
||||
|
||||
type Options = {
|
||||
addSuffix: boolean;
|
||||
locale: Locale;
|
||||
timeZone?: string;
|
||||
};
|
||||
|
||||
const DateElement = styled.time`
|
||||
border-bottom: 1px dotted rgba(219, 219, 219);
|
||||
cursor: help;
|
||||
`;
|
||||
|
||||
export const chooseLocale = (language: string, languages?: string[]) => {
|
||||
for (const lng of languages || []) {
|
||||
const locale = supportedLocales[lng];
|
||||
if (locale) {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
const locale = supportedLocales[language];
|
||||
if (locale) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
return enUS;
|
||||
};
|
||||
|
||||
class DateFromNow extends React.Component<Props> {
|
||||
getLocale = (): Locale => {
|
||||
const { i18n } = this.props;
|
||||
return chooseLocale(i18n.language, i18n.languages);
|
||||
};
|
||||
|
||||
createOptions = () => {
|
||||
const { timeZone } = this.props;
|
||||
const options: Options = {
|
||||
addSuffix: true,
|
||||
locale: this.getLocale()
|
||||
};
|
||||
if (timeZone) {
|
||||
options.timeZone = timeZone;
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
toDate = (value: DateInput): Date => {
|
||||
if (value instanceof Date) {
|
||||
return value;
|
||||
}
|
||||
return parseISO(value);
|
||||
};
|
||||
|
||||
getBaseDate = () => {
|
||||
const { baseDate } = this.props;
|
||||
if (baseDate) {
|
||||
return this.toDate(baseDate);
|
||||
}
|
||||
return new Date();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { date } = this.props;
|
||||
if (date) {
|
||||
const isoDate = this.toDate(date);
|
||||
const options = this.createOptions();
|
||||
const distance = formatDistance(isoDate, this.getBaseDate(), options);
|
||||
const formatted = format(isoDate, "yyyy-MM-dd HH:mm:ss", options);
|
||||
return <DateElement title={formatted}>{distance}</DateElement>;
|
||||
}
|
||||
const DateFromNow: FC<Props> = ({ className, ...dateProps }) => {
|
||||
const formatter = useDateFormatter(dateProps);
|
||||
if (!formatter) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation()(DateFromNow);
|
||||
return (
|
||||
<DateElement className={className} title={formatter.formatFull()}>
|
||||
{formatter.formatDistance()}
|
||||
</DateElement>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateFromNow;
|
||||
|
||||
46
scm-ui/ui-components/src/DateShort.tsx
Normal file
46
scm-ui/ui-components/src/DateShort.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import useDateFormatter, { DateProps } from "./useDateFormatter";
|
||||
import DateElement from "./DateElement";
|
||||
|
||||
type Props = DateProps & {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const DateShort: FC<Props> = ({ className, ...dateProps }) => {
|
||||
const formatter = useDateFormatter(dateProps);
|
||||
if (!formatter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DateElement className={className} title={formatter.formatFull()}>
|
||||
{formatter.formatShort()}
|
||||
</DateElement>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateShort;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,7 @@ import {
|
||||
export { validation, urls, repositories };
|
||||
|
||||
export { default as DateFromNow } from "./DateFromNow";
|
||||
export { default as DateShort } from "./DateShort";
|
||||
export { default as ErrorNotification } from "./ErrorNotification";
|
||||
export { default as ErrorPage } from "./ErrorPage";
|
||||
export { default as Icon } from "./Icon";
|
||||
|
||||
131
scm-ui/ui-components/src/repos/annotate/Annotate.stories.tsx
Normal file
131
scm-ui/ui-components/src/repos/annotate/Annotate.stories.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React, { FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import Annotate from "./Annotate";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import repository from "../../__resources__/repository";
|
||||
import { Binder, BinderContext } from "@scm-manager/ui-extensions";
|
||||
import { Person } from "../../avatar/Avatar";
|
||||
import { AnnotatedSource } from "@scm-manager/ui-types";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin: 2rem;
|
||||
`;
|
||||
|
||||
const commitCreateNewApp = {
|
||||
revision: "0d8c1d328f4599b363755671afe667c7ace52bae",
|
||||
author: {
|
||||
name: "Arthur Dent",
|
||||
mail: "arthur.dent@hitchhiker.com"
|
||||
},
|
||||
description: "create new app",
|
||||
when: new Date("2020-04-09T13:07:42Z")
|
||||
};
|
||||
|
||||
const commitFixedMissingImport = {
|
||||
revision: "fab38559ce3ab8c388e067712b4bd7ab94b9fa9b",
|
||||
author: {
|
||||
name: "Tricia Marie McMillan",
|
||||
mail: "trillian@hitchhiker.com"
|
||||
},
|
||||
description: "fixed missing import",
|
||||
when: new Date("2020-05-10T09:18:42Z")
|
||||
};
|
||||
|
||||
const commitImplementMain = {
|
||||
revision: "5203292ab2bc0c020dd22adc4d3897da4930e43f",
|
||||
author: {
|
||||
name: "Ford Prefect",
|
||||
mail: "ford.prefect@hitchhiker.com"
|
||||
},
|
||||
description: "implemented main function",
|
||||
when: new Date("2020-04-12T16:29:42Z")
|
||||
};
|
||||
|
||||
const source: AnnotatedSource = {
|
||||
language: "go",
|
||||
lines: [
|
||||
{
|
||||
lineNumber: 1,
|
||||
code: "package main",
|
||||
...commitCreateNewApp
|
||||
},
|
||||
{
|
||||
lineNumber: 2,
|
||||
code: "",
|
||||
...commitCreateNewApp
|
||||
},
|
||||
{
|
||||
lineNumber: 3,
|
||||
code: 'import "fmt"',
|
||||
...commitFixedMissingImport
|
||||
},
|
||||
{
|
||||
lineNumber: 4,
|
||||
code: "",
|
||||
...commitFixedMissingImport
|
||||
},
|
||||
{
|
||||
lineNumber: 5,
|
||||
code: "func main() {",
|
||||
...commitCreateNewApp
|
||||
},
|
||||
{
|
||||
lineNumber: 6,
|
||||
code: ' fmt.Println("Hello World")',
|
||||
...commitImplementMain
|
||||
},
|
||||
{
|
||||
lineNumber: 7,
|
||||
code: "}",
|
||||
...commitCreateNewApp
|
||||
},
|
||||
{
|
||||
lineNumber: 8,
|
||||
code: "",
|
||||
...commitCreateNewApp
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const Robohash: FC = ({ children }) => {
|
||||
const binder = new Binder("robohash");
|
||||
binder.bind("avatar.factory", (person: Person) => `https://robohash.org/${person.mail}.png`);
|
||||
return <BinderContext.Provider value={binder}>{children}</BinderContext.Provider>;
|
||||
};
|
||||
|
||||
storiesOf("Annotate", module)
|
||||
.addDecorator(storyFn => <MemoryRouter initialEntries={["/"]}>{storyFn()}</MemoryRouter>)
|
||||
.addDecorator(storyFn => <Wrapper className="box">{storyFn()}</Wrapper>)
|
||||
.add("Default", () => (
|
||||
<Annotate source={source} repository={repository} baseDate={new Date("2020-04-16T09:22:42Z")} />
|
||||
))
|
||||
.add("With Avatars", () => (
|
||||
<Robohash>
|
||||
<Annotate source={source} repository={repository} baseDate={new Date("2020-04-15T09:47:42Z")} />
|
||||
</Robohash>
|
||||
));
|
||||
157
scm-ui/ui-components/src/repos/annotate/Annotate.tsx
Normal file
157
scm-ui/ui-components/src/repos/annotate/Annotate.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { FC, useReducer } from "react";
|
||||
import { Repository, AnnotatedSource, AnnotatedLine } from "@scm-manager/ui-types";
|
||||
|
||||
// @ts-ignore
|
||||
import { LightAsync as ReactSyntaxHighlighter, createElement } from "react-syntax-highlighter";
|
||||
import { arduinoLight } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
||||
import { DateInput } from "../../useDateFormatter";
|
||||
import Popover from "./Popover";
|
||||
import AnnotateLine from "./AnnotateLine";
|
||||
import { Action } from "./actions";
|
||||
|
||||
type Props = {
|
||||
source: AnnotatedSource;
|
||||
repository: Repository;
|
||||
baseDate?: DateInput;
|
||||
};
|
||||
|
||||
type State = {
|
||||
annotation?: AnnotatedLine;
|
||||
offset?: number;
|
||||
line?: number;
|
||||
onPopover: boolean;
|
||||
onLine: boolean;
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
onPopover: false,
|
||||
onLine: false
|
||||
};
|
||||
|
||||
const reducer = (state: State, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case "enter-line": {
|
||||
if (state.onPopover) {
|
||||
return state;
|
||||
}
|
||||
return {
|
||||
annotation: action.annotation,
|
||||
offset: action.offset,
|
||||
line: action.line,
|
||||
onLine: true,
|
||||
onPopover: false
|
||||
};
|
||||
}
|
||||
case "leave-line": {
|
||||
if (state.onPopover) {
|
||||
return {
|
||||
...state,
|
||||
onLine: false
|
||||
};
|
||||
}
|
||||
return initialState;
|
||||
}
|
||||
case "enter-popover": {
|
||||
return {
|
||||
...state,
|
||||
onPopover: true
|
||||
};
|
||||
}
|
||||
case "leave-popover": {
|
||||
if (state.onLine) {
|
||||
return {
|
||||
...state,
|
||||
onPopover: false
|
||||
};
|
||||
}
|
||||
return initialState;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Annotate: FC<Props> = ({ source, repository, baseDate }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
const defaultRenderer = ({ rows, stylesheet, useInlineStyles }: any) => {
|
||||
let lastRevision = "";
|
||||
return rows.map((node: React.ReactNode, i: number) => {
|
||||
const line = createElement({
|
||||
node,
|
||||
stylesheet,
|
||||
useInlineStyles,
|
||||
key: `code-segment${i}`
|
||||
});
|
||||
|
||||
if (i + 1 < rows.length) {
|
||||
const annotation = source.lines[i];
|
||||
const newAnnotation = annotation.revision !== lastRevision;
|
||||
lastRevision = annotation.revision;
|
||||
return (
|
||||
<AnnotateLine dispatch={dispatch} annotation={annotation} showAnnotation={newAnnotation} nr={i + 1}>
|
||||
{line}
|
||||
</AnnotateLine>
|
||||
);
|
||||
}
|
||||
|
||||
return line;
|
||||
});
|
||||
};
|
||||
|
||||
let popover = null;
|
||||
if ((state.onPopover || state.onLine) && state.annotation) {
|
||||
popover = (
|
||||
<Popover
|
||||
annotation={state.annotation}
|
||||
dispatch={dispatch}
|
||||
offsetTop={state.offset}
|
||||
repository={repository}
|
||||
baseDate={baseDate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const code = source.lines.reduce((content, line) => {
|
||||
content += line.code + "\n";
|
||||
return content;
|
||||
}, "");
|
||||
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
{popover}
|
||||
<ReactSyntaxHighlighter
|
||||
showLineNumbers={false}
|
||||
language={source.language ? source.language : "text"}
|
||||
style={arduinoLight}
|
||||
renderer={defaultRenderer}
|
||||
>
|
||||
{code}
|
||||
</ReactSyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Annotate;
|
||||
145
scm-ui/ui-components/src/repos/annotate/AnnotateLine.tsx
Normal file
145
scm-ui/ui-components/src/repos/annotate/AnnotateLine.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React, { FC, Dispatch, useRef } from "react";
|
||||
import styled from "styled-components";
|
||||
import AuthorImage from "./AuthorImage";
|
||||
import DateShort from "../../DateShort";
|
||||
import { Action } from "./actions";
|
||||
import { AnnotatedLine } from "@scm-manager/ui-types";
|
||||
|
||||
const LineElement = styled.div`
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
vertical-align: top;
|
||||
`;
|
||||
|
||||
const Author = styled(LineElement)`
|
||||
width: 8em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const When = styled(LineElement)`
|
||||
display: inline-block;
|
||||
|
||||
width: 6.5em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
margin: 0 0.5em;
|
||||
`;
|
||||
|
||||
const LineNumber = styled(LineElement)`
|
||||
width: 3em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
border-left: 1px solid lightgrey;
|
||||
border-right: 1px solid lightgrey;
|
||||
|
||||
text-align: right;
|
||||
|
||||
padding: 0 0.5em;
|
||||
`;
|
||||
|
||||
const Line = styled.div`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 1.5em;
|
||||
vertical-align: top;
|
||||
`;
|
||||
|
||||
const Metadata = styled(LineElement)`
|
||||
cursor: help;
|
||||
`;
|
||||
|
||||
const EmptyMetadata = styled(LineElement)`
|
||||
width: 16.7em;
|
||||
`;
|
||||
|
||||
const dispatchDeferred = (dispatch: Dispatch<Action>, action: Action) => {
|
||||
setTimeout(() => dispatch(action), 250);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
annotation: AnnotatedLine;
|
||||
showAnnotation: boolean;
|
||||
nr: number;
|
||||
dispatch: Dispatch<Action>;
|
||||
};
|
||||
|
||||
const AnnotateLine: FC<Props> = ({ annotation, showAnnotation, dispatch, nr, children }) => {
|
||||
const link = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onMouseEnter = () => {
|
||||
if (showAnnotation) {
|
||||
dispatchDeferred(dispatch, {
|
||||
annotation,
|
||||
line: nr,
|
||||
offset: link.current!.offsetTop,
|
||||
type: "enter-line"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const OnMouseLeave = () => {
|
||||
if (showAnnotation) {
|
||||
dispatchDeferred(dispatch, {
|
||||
line: nr,
|
||||
type: "leave-line"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (!showAnnotation) {
|
||||
return (
|
||||
<Line>
|
||||
<EmptyMetadata />
|
||||
<LineNumber>{nr}</LineNumber> <LineElement>{children}</LineElement>
|
||||
</Line>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Line>
|
||||
<Metadata className="has-text-info" onMouseOver={onMouseEnter} onMouseOut={OnMouseLeave} ref={link}>
|
||||
<Author>
|
||||
<AuthorImage person={annotation.author} />
|
||||
{annotation.author.name}
|
||||
</Author>{" "}
|
||||
<When>
|
||||
<DateShort date={annotation.when} />
|
||||
</When>{" "}
|
||||
</Metadata>
|
||||
<LineNumber>{nr}</LineNumber> <LineElement>{children}</LineElement>
|
||||
</Line>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnnotateLine;
|
||||
36
scm-ui/ui-components/src/repos/annotate/AuthorImage.tsx
Normal file
36
scm-ui/ui-components/src/repos/annotate/AuthorImage.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { AvatarImage } from "../../avatar";
|
||||
import styled from "styled-components";
|
||||
|
||||
const AuthorImage = styled(AvatarImage)`
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-right: 0.2em;
|
||||
margin-bottom: 0.2em;
|
||||
vertical-align: middle;
|
||||
`;
|
||||
|
||||
export default AuthorImage;
|
||||
142
scm-ui/ui-components/src/repos/annotate/Popover.tsx
Normal file
142
scm-ui/ui-components/src/repos/annotate/Popover.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { FC, useState, useRef, useLayoutEffect, Dispatch } from "react";
|
||||
import styled from "styled-components";
|
||||
import { Link } from "react-router-dom";
|
||||
import DateFromNow from "../../DateFromNow";
|
||||
import { SingleContributor } from "../changesets";
|
||||
import { DateInput } from "../../useDateFormatter";
|
||||
import { Repository, AnnotatedLine } from "@scm-manager/ui-types";
|
||||
import AuthorImage from "./AuthorImage";
|
||||
import { Action } from "./actions";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
const PopoverContainer = styled.div`
|
||||
position: absolute;
|
||||
left: 2.25em;
|
||||
z-index: 100;
|
||||
width: 30em;
|
||||
display: block;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
border-style: solid;
|
||||
pointer-events: none;
|
||||
height: 0;
|
||||
width: 0;
|
||||
top: 100%;
|
||||
left: 5.5em;
|
||||
border-color: transparent;
|
||||
border-bottom-color: white;
|
||||
border-left-color: white;
|
||||
border-width: 0.4rem;
|
||||
margin-left: -0.4rem;
|
||||
margin-top: -0.4rem;
|
||||
-webkit-transform-origin: center;
|
||||
transform-origin: center;
|
||||
box-shadow: -1px 1px 2px rgba(10, 10, 10, 0.2);
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
`;
|
||||
|
||||
const SmallHr = styled.hr`
|
||||
margin: 0.5em 0;
|
||||
`;
|
||||
|
||||
const PopoverHeading = styled.div`
|
||||
height: 1.5em;
|
||||
`;
|
||||
|
||||
const PopoverDescription = styled.p`
|
||||
margin-top: 0.5em;
|
||||
`;
|
||||
|
||||
const shortRevision = (revision: string) => {
|
||||
if (revision.length > 7) {
|
||||
return revision.substring(0, 7);
|
||||
}
|
||||
return revision;
|
||||
};
|
||||
|
||||
type PopoverProps = {
|
||||
annotation: AnnotatedLine;
|
||||
offsetTop?: number;
|
||||
repository: Repository;
|
||||
baseDate?: DateInput;
|
||||
dispatch: Dispatch<Action>;
|
||||
};
|
||||
|
||||
const Popover: FC<PopoverProps> = ({ annotation, offsetTop, repository, baseDate, dispatch }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
const [height, setHeight] = useState(125);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current) {
|
||||
setHeight(ref.current.clientHeight);
|
||||
}
|
||||
}, [ref]);
|
||||
|
||||
const onMouseEnter = () => {
|
||||
dispatch({
|
||||
type: "enter-popover"
|
||||
});
|
||||
};
|
||||
|
||||
const OnMouseLeave = () => {
|
||||
dispatch({
|
||||
type: "leave-popover"
|
||||
});
|
||||
};
|
||||
|
||||
const top = (offsetTop || 0) - height - 5;
|
||||
return (
|
||||
<PopoverContainer
|
||||
ref={ref}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={OnMouseLeave}
|
||||
className="box"
|
||||
style={{ top: `${top}px` }}
|
||||
>
|
||||
<PopoverHeading className="is-clearfix">
|
||||
<span className="is-pulled-left">
|
||||
<AuthorImage person={annotation.author} />
|
||||
<SingleContributor person={annotation.author} displayTextOnly={true} />
|
||||
</span>
|
||||
<DateFromNow className="is-pulled-right" date={annotation.when} baseDate={baseDate} />
|
||||
</PopoverHeading>
|
||||
<SmallHr />
|
||||
<p>
|
||||
{t("changeset.label") + " "}
|
||||
<Link to={`/repo/${repository.namespace}/${repository.name}/code/changeset/${annotation.revision}`}>
|
||||
{shortRevision(annotation.revision)}
|
||||
</Link>
|
||||
</p>
|
||||
<PopoverDescription className="content">{annotation.description}</PopoverDescription>
|
||||
</PopoverContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Popover;
|
||||
47
scm-ui/ui-components/src/repos/annotate/actions.ts
Normal file
47
scm-ui/ui-components/src/repos/annotate/actions.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { AnnotatedLine } from "@scm-manager/ui-types";
|
||||
|
||||
type EnterLine = {
|
||||
annotation: AnnotatedLine;
|
||||
offset: number;
|
||||
line: number;
|
||||
type: "enter-line";
|
||||
};
|
||||
|
||||
type LeaveLine = {
|
||||
line: number;
|
||||
type: "leave-line";
|
||||
};
|
||||
|
||||
type EnterPopover = {
|
||||
type: "enter-popover";
|
||||
};
|
||||
|
||||
type LeavePopover = {
|
||||
type: "leave-popover";
|
||||
};
|
||||
|
||||
export type Action = EnterLine | LeaveLine | EnterPopover | LeavePopover;
|
||||
25
scm-ui/ui-components/src/repos/annotate/index.ts
Normal file
25
scm-ui/ui-components/src/repos/annotate/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export { default as Annotate } from "./Annotate";
|
||||
@@ -36,6 +36,7 @@ type Props = {
|
||||
|
||||
type PersonProps = {
|
||||
person: Person;
|
||||
className?: string;
|
||||
displayTextOnly?: boolean;
|
||||
};
|
||||
|
||||
@@ -70,7 +71,7 @@ const ContributorWithAvatar: FC<PersonAvatarProps> = ({ person, avatar }) => {
|
||||
return <ContributorAvatar src={avatar} alt={person.name} title={person.name} />;
|
||||
};
|
||||
|
||||
const SingleContributor: FC<PersonProps> = ({ person, displayTextOnly }) => {
|
||||
export const SingleContributor: FC<PersonProps> = ({ person, className, displayTextOnly }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
const avatar = useAvatar(person);
|
||||
if (!displayTextOnly && avatar) {
|
||||
@@ -78,12 +79,16 @@ const SingleContributor: FC<PersonProps> = ({ person, displayTextOnly }) => {
|
||||
}
|
||||
if (person.mail) {
|
||||
return (
|
||||
<a href={"mailto:" + person.mail} title={t("changeset.contributors.mailto") + " " + person.mail}>
|
||||
<a
|
||||
className={className}
|
||||
href={"mailto:" + person.mail}
|
||||
title={t("changeset.contributors.mailto") + " " + person.mail}
|
||||
>
|
||||
{person.name}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <>{person.name}</>;
|
||||
return <span className={className}>{person.name}</span>;
|
||||
};
|
||||
|
||||
type PersonsProps = {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
import * as changesets from "./changesets";
|
||||
export { changesets };
|
||||
|
||||
export { default as ChangesetAuthor } from "./ChangesetAuthor";
|
||||
export { default as ChangesetAuthor, SingleContributor } from "./ChangesetAuthor";
|
||||
export { default as ChangesetButtonGroup } from "./ChangesetButtonGroup";
|
||||
export { default as ChangesetDiff } from "./ChangesetDiff";
|
||||
export { default as ChangesetId } from "./ChangesetId";
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
|
||||
export { diffs };
|
||||
|
||||
export * from "./annotate";
|
||||
export * from "./changesets";
|
||||
|
||||
export { default as Diff } from "./Diff";
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { chooseLocale, supportedLocales } from "./DateFromNow";
|
||||
import { chooseLocale, supportedLocales } from "./useDateFormatter";
|
||||
|
||||
describe("test choose locale", () => {
|
||||
it("should choose de", () => {
|
||||
126
scm-ui/ui-components/src/useDateFormatter.ts
Normal file
126
scm-ui/ui-components/src/useDateFormatter.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { enUS, de, es } from "date-fns/locale";
|
||||
import { formatDistance, format, Locale, parseISO } from "date-fns";
|
||||
|
||||
type LocaleMap = {
|
||||
[key: string]: Locale;
|
||||
};
|
||||
|
||||
export const supportedLocales: LocaleMap = {
|
||||
enUS,
|
||||
en: enUS,
|
||||
de,
|
||||
es
|
||||
};
|
||||
|
||||
type Options = {
|
||||
addSuffix: boolean;
|
||||
locale: Locale;
|
||||
timeZone?: string;
|
||||
};
|
||||
|
||||
export const chooseLocale = (language: string, languages?: string[]) => {
|
||||
for (const lng of languages || []) {
|
||||
const locale = supportedLocales[lng];
|
||||
if (locale) {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
const locale = supportedLocales[language];
|
||||
if (locale) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
return enUS;
|
||||
};
|
||||
|
||||
export type DateInput = Date | string;
|
||||
|
||||
export type DateProps = {
|
||||
date?: DateInput;
|
||||
timeZone?: string;
|
||||
|
||||
/**
|
||||
* baseDate is the date from which the distance is calculated,
|
||||
* default is the current time (new Date()). This property
|
||||
* is required to keep snapshots tests green over the time on
|
||||
* ci server.
|
||||
*/
|
||||
baseDate?: DateInput;
|
||||
};
|
||||
|
||||
const createOptions = (locale: Locale, timeZone?: string) => {
|
||||
const options: Options = {
|
||||
addSuffix: true,
|
||||
locale
|
||||
};
|
||||
if (timeZone) {
|
||||
options.timeZone = timeZone;
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
const createBaseDate = (baseDate?: DateInput) => {
|
||||
if (baseDate) {
|
||||
return toDate(baseDate);
|
||||
}
|
||||
return new Date();
|
||||
};
|
||||
|
||||
const toDate = (value: DateInput): Date => {
|
||||
if (value instanceof Date) {
|
||||
return value;
|
||||
}
|
||||
return parseISO(value);
|
||||
};
|
||||
|
||||
const useDateFormatter = ({ date, baseDate, timeZone }: DateProps) => {
|
||||
const { i18n } = useTranslation();
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isoDate = toDate(date);
|
||||
const base = createBaseDate(baseDate);
|
||||
|
||||
const locale = chooseLocale(i18n.language, i18n.languages);
|
||||
const options = createOptions(locale, timeZone);
|
||||
return {
|
||||
formatShort() {
|
||||
return format(isoDate, "yyyy-MM-dd", options);
|
||||
},
|
||||
formatFull() {
|
||||
return format(isoDate, "yyyy-MM-dd HH:mm:ss", options);
|
||||
},
|
||||
formatDistance() {
|
||||
return formatDistance(isoDate, base, options);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default useDateFormatter;
|
||||
@@ -15,7 +15,7 @@
|
||||
"react-redux": "^5.0.7",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"redux": "^4.0.0",
|
||||
"styled-components": "^4.4.0"
|
||||
"styled-components": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scm-manager/babel-preset": "^2.0.0",
|
||||
@@ -35,7 +35,7 @@
|
||||
"@types/react": "^16.9.9",
|
||||
"@types/react-redux": "5.0.7",
|
||||
"@types/react-router-dom": "^5.1.0",
|
||||
"@types/styled-components": "^4.1.19",
|
||||
"@types/styled-components": "^5.1.0",
|
||||
"jest": "^24.9.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
39
scm-ui/ui-types/src/Annotate.ts
Normal file
39
scm-ui/ui-types/src/Annotate.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Person } from "./Person";
|
||||
|
||||
export type AnnotatedSource = {
|
||||
lines: AnnotatedLine[];
|
||||
language?: string;
|
||||
};
|
||||
|
||||
export type AnnotatedLine = {
|
||||
author: Person;
|
||||
code: string;
|
||||
description: string;
|
||||
lineNumber: number;
|
||||
revision: string;
|
||||
when: Date;
|
||||
};
|
||||
@@ -25,11 +25,7 @@
|
||||
import { Collection, Links } from "./hal";
|
||||
import { Tag } from "./Tags";
|
||||
import { Branch } from "./Branches";
|
||||
|
||||
export type Person = {
|
||||
name: string;
|
||||
mail?: string;
|
||||
};
|
||||
import { Person } from "./Person";
|
||||
|
||||
export type Changeset = Collection & {
|
||||
id: string;
|
||||
|
||||
28
scm-ui/ui-types/src/Person.ts
Normal file
28
scm-ui/ui-types/src/Person.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export type Person = {
|
||||
name: string;
|
||||
mail?: string;
|
||||
};
|
||||
@@ -34,7 +34,11 @@ export { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes";
|
||||
|
||||
export { Branch, BranchRequest } from "./Branches";
|
||||
|
||||
export { Changeset, Person, Contributor, ParentChangeset } from "./Changesets";
|
||||
export { Person } from "./Person";
|
||||
|
||||
export { Changeset, Contributor, ParentChangeset } from "./Changesets";
|
||||
|
||||
export { AnnotatedSource, AnnotatedLine } from "./Annotate";
|
||||
|
||||
export { Tag } from "./Tags";
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"redux-devtools-extension": "^2.13.5",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"styled-components": "^4.4.0",
|
||||
"styled-components": "^5.1.0",
|
||||
"systemjs": "0.21.6"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -39,7 +39,7 @@
|
||||
"@types/react-dom": "^16.9.2",
|
||||
"@types/react-redux": "5.0.7",
|
||||
"@types/react-router-dom": "^5.1.0",
|
||||
"@types/styled-components": "^4.1.19",
|
||||
"@types/styled-components": "^5.1.0",
|
||||
"@types/systemjs": "^0.20.6",
|
||||
"fetch-mock": "^7.5.1",
|
||||
"react-test-renderer": "^16.10.2",
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"collapseDiffs": "Auf-/Zuklappen"
|
||||
},
|
||||
"changeset": {
|
||||
"label": "Changeset",
|
||||
"description": "Beschreibung",
|
||||
"summary": "Changeset <0/> wurde <1/> committet",
|
||||
"shortSummary": "Committet <0/> <1/>",
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"collapseDiffs": "Collapse"
|
||||
},
|
||||
"changeset": {
|
||||
"label": "Changeset",
|
||||
"description": "Description",
|
||||
"summary": "Changeset <0/> was committed <1/>",
|
||||
"shortSummary": "Committed <0/> <1/>",
|
||||
|
||||
@@ -24,20 +24,27 @@
|
||||
import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Button, ButtonAddons } from "@scm-manager/ui-components";
|
||||
import { SourceViewSelection } from "../../containers/Content";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
className?: string;
|
||||
historyIsSelected: boolean;
|
||||
showHistory: (p: boolean) => void;
|
||||
selected: SourceViewSelection;
|
||||
showSources: () => void;
|
||||
showHistory: () => void;
|
||||
showAnnotations: () => void;
|
||||
};
|
||||
|
||||
class FileButtonAddons extends React.Component<Props> {
|
||||
showHistory = () => {
|
||||
this.props.showHistory(true);
|
||||
this.props.showHistory();
|
||||
};
|
||||
|
||||
showSources = () => {
|
||||
this.props.showHistory(false);
|
||||
this.props.showSources();
|
||||
};
|
||||
|
||||
showAnnotations = () => {
|
||||
this.props.showAnnotations();
|
||||
};
|
||||
|
||||
color = (selected: boolean) => {
|
||||
@@ -45,19 +52,26 @@ class FileButtonAddons extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className, t, historyIsSelected } = this.props;
|
||||
const { className, t, selected, showSources, showHistory, showAnnotations } = this.props;
|
||||
|
||||
return (
|
||||
<ButtonAddons className={className}>
|
||||
<div title={t("sources.content.sourcesButton")}>
|
||||
<Button action={this.showSources} color={this.color(!historyIsSelected)}>
|
||||
<Button action={showSources} color={this.color(selected === "source")}>
|
||||
<span className="icon">
|
||||
<i className="fas fa-code" />
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div title={t("sources.content.annotateButton")}>
|
||||
<Button action={showAnnotations} color={this.color(selected === "annotations")}>
|
||||
<span className="icon">
|
||||
<i className="fas fa-user-clock" />
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div title={t("sources.content.historyButton")}>
|
||||
<Button action={this.showHistory} color={this.color(historyIsSelected)}>
|
||||
<Button action={showHistory} color={this.color(selected === "history")}>
|
||||
<span className="icon">
|
||||
<i className="fas fa-history" />
|
||||
</span>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import { Link, Repository, File, AnnotatedSource } from "@scm-manager/ui-types";
|
||||
import { Annotate, apiClient, ErrorNotification, Loading } from "@scm-manager/ui-components";
|
||||
import { getContentType } from "./contentType";
|
||||
|
||||
type Props = {
|
||||
file: File;
|
||||
repository: Repository;
|
||||
};
|
||||
|
||||
const AnnotateView: FC<Props> = ({ file, repository }) => {
|
||||
const [annotation, setAnnotation] = useState<AnnotatedSource | undefined>(undefined);
|
||||
const [language, setLanguage] = useState<string | undefined>(undefined);
|
||||
const [error, setError] = useState<Error | undefined>(undefined);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const languagePromise = getContentType((file._links.self as Link).href).then(result =>
|
||||
setLanguage(result.language)
|
||||
);
|
||||
|
||||
const apiClientPromise = apiClient
|
||||
.get((file._links.annotate as Link).href)
|
||||
.then(response => response.json())
|
||||
.then(setAnnotation);
|
||||
|
||||
Promise.all([languagePromise, apiClientPromise])
|
||||
.then(() => setLoading(false))
|
||||
.catch(setError);
|
||||
}, [file]);
|
||||
|
||||
if (error) {
|
||||
return <ErrorNotification error={error} />;
|
||||
}
|
||||
|
||||
if (!annotation || loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return <Annotate source={{ ...annotation, language }} repository={repository} />;
|
||||
};
|
||||
|
||||
export default AnnotateView;
|
||||
@@ -33,6 +33,7 @@ import { getSources } from "../modules/sources";
|
||||
import FileButtonAddons from "../components/content/FileButtonAddons";
|
||||
import SourcesView from "./SourcesView";
|
||||
import HistoryView from "./HistoryView";
|
||||
import AnnotateView from "./AnnotateView";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
loading: boolean;
|
||||
@@ -45,7 +46,7 @@ type Props = WithTranslation & {
|
||||
|
||||
type State = {
|
||||
collapsed: boolean;
|
||||
showHistory: boolean;
|
||||
selected: SourceViewSelection;
|
||||
errorFromExtension?: Error;
|
||||
};
|
||||
|
||||
@@ -81,13 +82,15 @@ const LighterGreyBackgroundTable = styled.table`
|
||||
background-color: #fbfbfb;
|
||||
`;
|
||||
|
||||
export type SourceViewSelection = "source" | "history" | "annotations";
|
||||
|
||||
class Content extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
collapsed: true,
|
||||
showHistory: false
|
||||
selected: "source"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,13 +100,6 @@ class Content extends React.Component<Props, State> {
|
||||
}));
|
||||
};
|
||||
|
||||
setShowHistoryState(showHistory: boolean) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
showHistory
|
||||
});
|
||||
}
|
||||
|
||||
handleExtensionError = (error: Error) => {
|
||||
this.setState({
|
||||
errorFromExtension: error
|
||||
@@ -112,14 +108,15 @@ class Content extends React.Component<Props, State> {
|
||||
|
||||
showHeader() {
|
||||
const { repository, file, revision } = this.props;
|
||||
const { showHistory, collapsed } = this.state;
|
||||
const { selected, collapsed } = this.state;
|
||||
const icon = collapsed ? "angle-right" : "angle-down";
|
||||
|
||||
const selector = file._links.history ? (
|
||||
<RightMarginFileButtonAddons
|
||||
file={file}
|
||||
historyIsSelected={showHistory}
|
||||
showHistory={(changeShowHistory: boolean) => this.setShowHistoryState(changeShowHistory)}
|
||||
selected={selected}
|
||||
showSources={() => this.setState({ selected: "source" })}
|
||||
showHistory={() => this.setState({ selected: "history" })}
|
||||
showAnnotations={() => this.setState({ selected: "annotations" })}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
@@ -212,15 +209,26 @@ class Content extends React.Component<Props, State> {
|
||||
|
||||
render() {
|
||||
const { file, revision, repository, path, breadcrumb } = this.props;
|
||||
const { showHistory, errorFromExtension } = this.state;
|
||||
const { selected, errorFromExtension } = this.state;
|
||||
|
||||
const header = this.showHeader();
|
||||
const content =
|
||||
showHistory && file._links.history ? (
|
||||
<HistoryView file={file} repository={repository} />
|
||||
) : (
|
||||
<SourcesView revision={revision} file={file} repository={repository} path={path} />
|
||||
let content;
|
||||
switch (selected) {
|
||||
case "source":
|
||||
content = (
|
||||
<SourcesView revision={revision} file={file} repository={repository} path={path}/>
|
||||
);
|
||||
break;
|
||||
case "history":
|
||||
content = (
|
||||
<HistoryView file={file} repository={repository}/>
|
||||
);
|
||||
break;
|
||||
case "annotations":
|
||||
content = (
|
||||
<AnnotateView file={file} repository={repository} />
|
||||
);
|
||||
}
|
||||
const moreInformation = this.showMoreInformation();
|
||||
|
||||
return (
|
||||
|
||||
@@ -24,18 +24,18 @@
|
||||
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
|
||||
export function getContentType(url: string) {
|
||||
export type ContentType = {
|
||||
type : string;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
export function getContentType(url: string) : Promise<ContentType> {
|
||||
return apiClient
|
||||
.head(url)
|
||||
.then(response => {
|
||||
return {
|
||||
type: response.headers.get("Content-Type"),
|
||||
language: response.headers.get("X-Programming-Language")
|
||||
type: response.headers.get("Content-Type") || "application/octet-stream",
|
||||
language: response.headers.get("X-Programming-Language") || undefined
|
||||
};
|
||||
})
|
||||
.catch(err => {
|
||||
return {
|
||||
error: err
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AnnotateResource {
|
||||
|
||||
private final RepositoryServiceFactory serviceFactory;
|
||||
private final BlameResultToBlameDtoMapper mapper;
|
||||
|
||||
@Inject
|
||||
public AnnotateResource(RepositoryServiceFactory serviceFactory, BlameResultToBlameDtoMapper mapper) {
|
||||
this.serviceFactory = serviceFactory;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of a file with additional information for each line regarding the last commit that
|
||||
* changed this line: The revision, the author, the date, and the description of the commit.
|
||||
*
|
||||
* @param namespace the namespace of the repository
|
||||
* @param name the name of the repository
|
||||
* @param revision the revision
|
||||
* @param path The path of the file
|
||||
*/
|
||||
@GET
|
||||
@Path("{revision}/{path: .*}")
|
||||
@Produces(VndMediaType.ANNOTATE)
|
||||
@Operation(summary = "File content by revision", description = "Returns the annotated file for the given revision in the repository.", tags = "Repository")
|
||||
@ApiResponse(responseCode = "200", description = "success")
|
||||
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||
@ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the repository")
|
||||
@ApiResponse(
|
||||
responseCode = "404",
|
||||
description = "not found, no repository with the specified name available in the namespace",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.ERROR_TYPE,
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
@ApiResponse(
|
||||
responseCode = "500",
|
||||
description = "internal server error",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.ERROR_TYPE,
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
public BlameDto annotate(
|
||||
@PathParam("namespace") String namespace,
|
||||
@PathParam("name") String name,
|
||||
@PathParam("revision") String revision,
|
||||
@PathParam("path") String path
|
||||
) throws IOException {
|
||||
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
|
||||
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
|
||||
return mapper.map(repositoryService.getBlameCommand().setRevision(revision).getBlameResult(path), namespaceAndName, revision, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,7 @@ abstract class BaseFileObjectDtoMapper extends HalAppenderMapper implements Inst
|
||||
} else {
|
||||
links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path));
|
||||
links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)));
|
||||
links.single(link("annotate", resourceLinks.annotate().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)));
|
||||
}
|
||||
if (fileObject.isTruncated()) {
|
||||
links.single(link("proceed", resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path) + "?offset=" + (offset + BrowseCommandRequest.DEFAULT_REQUEST_LIMIT)));
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class BlameDto extends HalRepresentation {
|
||||
|
||||
private List<BlameLineDto> lines;
|
||||
|
||||
public BlameDto(Links links) {
|
||||
super(links);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Data
|
||||
public class BlameLineDto {
|
||||
|
||||
private PersonDto author;
|
||||
private String code;
|
||||
private String description;
|
||||
private int lineNumber;
|
||||
private String revision;
|
||||
private Instant when;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.BlameLine;
|
||||
import sonia.scm.repository.BlameResult;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Person;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mapper
|
||||
public abstract class BlameResultToBlameDtoMapper implements InstantAttributeMapper {
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
BlameDto map(BlameResult result, NamespaceAndName namespaceAndName, String revision, String path) {
|
||||
BlameDto dto = createDto(namespaceAndName, revision, path);
|
||||
dto.setLines(result.getBlameLines().stream().map(this::map).collect(Collectors.toList()));
|
||||
return dto;
|
||||
}
|
||||
|
||||
abstract BlameLineDto map(BlameLine line);
|
||||
|
||||
abstract PersonDto map(Person person);
|
||||
|
||||
@ObjectFactory
|
||||
BlameDto createDto(NamespaceAndName namespaceAndName, String revision, String path) {
|
||||
return new BlameDto(Links.linkingTo()
|
||||
.self(
|
||||
resourceLinks.annotate()
|
||||
.self(
|
||||
namespaceAndName.getNamespace(),
|
||||
namespaceAndName.getName(),
|
||||
revision,
|
||||
path))
|
||||
.build());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setResourceLinks(ResourceLinks resourceLinks) {
|
||||
this.resourceLinks = resourceLinks;
|
||||
}
|
||||
}
|
||||
@@ -32,46 +32,48 @@ import sonia.scm.web.api.RepositoryToHalMapper;
|
||||
public class MapperModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(UserDtoToUserMapper.class).to(Mappers.getMapper(UserDtoToUserMapper.class).getClass());
|
||||
bind(UserToUserDtoMapper.class).to(Mappers.getMapper(UserToUserDtoMapper.class).getClass());
|
||||
bind(UserDtoToUserMapper.class).to(Mappers.getMapperClass(UserDtoToUserMapper.class));
|
||||
bind(UserToUserDtoMapper.class).to(Mappers.getMapperClass(UserToUserDtoMapper.class));
|
||||
bind(UserCollectionToDtoMapper.class);
|
||||
|
||||
bind(GroupDtoToGroupMapper.class).to(Mappers.getMapper(GroupDtoToGroupMapper.class).getClass());
|
||||
bind(GroupToGroupDtoMapper.class).to(Mappers.getMapper(GroupToGroupDtoMapper.class).getClass());
|
||||
bind(GroupDtoToGroupMapper.class).to(Mappers.getMapperClass(GroupDtoToGroupMapper.class));
|
||||
bind(GroupToGroupDtoMapper.class).to(Mappers.getMapperClass(GroupToGroupDtoMapper.class));
|
||||
bind(GroupCollectionToDtoMapper.class);
|
||||
|
||||
bind(ScmConfigurationToConfigDtoMapper.class).to(Mappers.getMapper(ScmConfigurationToConfigDtoMapper.class).getClass());
|
||||
bind(ConfigDtoToScmConfigurationMapper.class).to(Mappers.getMapper(ConfigDtoToScmConfigurationMapper.class).getClass());
|
||||
bind(ScmConfigurationToConfigDtoMapper.class).to(Mappers.getMapperClass(ScmConfigurationToConfigDtoMapper.class));
|
||||
bind(ConfigDtoToScmConfigurationMapper.class).to(Mappers.getMapperClass(ConfigDtoToScmConfigurationMapper.class));
|
||||
|
||||
bind(RepositoryToRepositoryDtoMapper.class).to(Mappers.getMapper(RepositoryToRepositoryDtoMapper.class).getClass());
|
||||
bind(RepositoryDtoToRepositoryMapper.class).to(Mappers.getMapper(RepositoryDtoToRepositoryMapper.class).getClass());
|
||||
bind(RepositoryToRepositoryDtoMapper.class).to(Mappers.getMapperClass(RepositoryToRepositoryDtoMapper.class));
|
||||
bind(RepositoryDtoToRepositoryMapper.class).to(Mappers.getMapperClass(RepositoryDtoToRepositoryMapper.class));
|
||||
|
||||
bind(RepositoryTypeToRepositoryTypeDtoMapper.class).to(Mappers.getMapper(RepositoryTypeToRepositoryTypeDtoMapper.class).getClass());
|
||||
bind(RepositoryTypeToRepositoryTypeDtoMapper.class).to(Mappers.getMapperClass(RepositoryTypeToRepositoryTypeDtoMapper.class));
|
||||
bind(RepositoryTypeCollectionToDtoMapper.class);
|
||||
|
||||
bind(BranchToBranchDtoMapper.class).to(Mappers.getMapper(BranchToBranchDtoMapper.class).getClass());
|
||||
bind(RepositoryPermissionDtoToRepositoryPermissionMapper.class).to(Mappers.getMapper(RepositoryPermissionDtoToRepositoryPermissionMapper.class).getClass());
|
||||
bind(RepositoryPermissionToRepositoryPermissionDtoMapper.class).to(Mappers.getMapper(RepositoryPermissionToRepositoryPermissionDtoMapper.class).getClass());
|
||||
bind(BranchToBranchDtoMapper.class).to(Mappers.getMapperClass(BranchToBranchDtoMapper.class));
|
||||
bind(RepositoryPermissionDtoToRepositoryPermissionMapper.class).to(Mappers.getMapperClass(RepositoryPermissionDtoToRepositoryPermissionMapper.class));
|
||||
bind(RepositoryPermissionToRepositoryPermissionDtoMapper.class).to(Mappers.getMapperClass(RepositoryPermissionToRepositoryPermissionDtoMapper.class));
|
||||
|
||||
bind(RepositoryRoleToRepositoryRoleDtoMapper.class).to(Mappers.getMapper(RepositoryRoleToRepositoryRoleDtoMapper.class).getClass());
|
||||
bind(RepositoryRoleDtoToRepositoryRoleMapper.class).to(Mappers.getMapper(RepositoryRoleDtoToRepositoryRoleMapper.class).getClass());
|
||||
bind(RepositoryRoleToRepositoryRoleDtoMapper.class).to(Mappers.getMapperClass(RepositoryRoleToRepositoryRoleDtoMapper.class));
|
||||
bind(RepositoryRoleDtoToRepositoryRoleMapper.class).to(Mappers.getMapperClass(RepositoryRoleDtoToRepositoryRoleMapper.class));
|
||||
bind(RepositoryRoleCollectionToDtoMapper.class);
|
||||
|
||||
bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapper(DefaultChangesetToChangesetDtoMapper.class).getClass());
|
||||
bind(ChangesetToParentDtoMapper.class).to(Mappers.getMapper(ChangesetToParentDtoMapper.class).getClass());
|
||||
bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapperClass(DefaultChangesetToChangesetDtoMapper.class));
|
||||
bind(ChangesetToParentDtoMapper.class).to(Mappers.getMapperClass(ChangesetToParentDtoMapper.class));
|
||||
|
||||
bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass());
|
||||
bind(TagToTagDtoMapper.class).to(Mappers.getMapperClass(TagToTagDtoMapper.class));
|
||||
|
||||
bind(BrowserResultToFileObjectDtoMapper.class).to(Mappers.getMapper(BrowserResultToFileObjectDtoMapper.class).getClass());
|
||||
bind(ModificationsToDtoMapper.class).to(Mappers.getMapper(ModificationsToDtoMapper.class).getClass());
|
||||
bind(BrowserResultToFileObjectDtoMapper.class).to(Mappers.getMapperClass(BrowserResultToFileObjectDtoMapper.class));
|
||||
bind(ModificationsToDtoMapper.class).to(Mappers.getMapperClass(ModificationsToDtoMapper.class));
|
||||
|
||||
bind(ReducedObjectModelToDtoMapper.class).to(Mappers.getMapper(ReducedObjectModelToDtoMapper.class).getClass());
|
||||
bind(ReducedObjectModelToDtoMapper.class).to(Mappers.getMapperClass(ReducedObjectModelToDtoMapper.class));
|
||||
|
||||
bind(ResteasyViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapper(ResteasyViolationExceptionToErrorDtoMapper.class).getClass());
|
||||
bind(ScmViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapper(ScmViolationExceptionToErrorDtoMapper.class).getClass());
|
||||
bind(ExceptionWithContextToErrorDtoMapper.class).to(Mappers.getMapper(ExceptionWithContextToErrorDtoMapper.class).getClass());
|
||||
bind(ResteasyViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapperClass(ResteasyViolationExceptionToErrorDtoMapper.class));
|
||||
bind(ScmViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapperClass(ScmViolationExceptionToErrorDtoMapper.class));
|
||||
bind(ExceptionWithContextToErrorDtoMapper.class).to(Mappers.getMapperClass(ExceptionWithContextToErrorDtoMapper.class));
|
||||
|
||||
bind(RepositoryToHalMapper.class).to(Mappers.getMapper(RepositoryToRepositoryDtoMapper.class).getClass());
|
||||
bind(RepositoryToHalMapper.class).to(Mappers.getMapperClass(RepositoryToRepositoryDtoMapper.class));
|
||||
|
||||
bind(BlameResultToBlameDtoMapper.class).to(Mappers.getMapperClass(BlameResultToBlameDtoMapper.class));
|
||||
|
||||
// no mapstruct required
|
||||
bind(MeDtoFactory.class);
|
||||
@@ -80,6 +82,6 @@ public class MapperModule extends AbstractModule {
|
||||
|
||||
bind(ScmPathInfoStore.class).in(ServletScopes.REQUEST);
|
||||
|
||||
bind(PluginDtoMapper.class).to(Mappers.getMapper(PluginDtoMapper.class).getClass());
|
||||
bind(PluginDtoMapper.class).to(Mappers.getMapperClass(PluginDtoMapper.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
public class RepositoryBasedResourceProvider {
|
||||
private final Provider<TagRootResource> tagRootResource;
|
||||
private final Provider<BranchRootResource> branchRootResource;
|
||||
private final Provider<ChangesetRootResource> changesetRootResource;
|
||||
private final Provider<SourceRootResource> sourceRootResource;
|
||||
private final Provider<ContentResource> contentResource;
|
||||
private final Provider<RepositoryPermissionRootResource> permissionRootResource;
|
||||
private final Provider<DiffRootResource> diffRootResource;
|
||||
private final Provider<ModificationsRootResource> modificationsRootResource;
|
||||
private final Provider<FileHistoryRootResource> fileHistoryRootResource;
|
||||
private final Provider<IncomingRootResource> incomingRootResource;
|
||||
private final Provider<AnnotateResource> annotateResource;
|
||||
|
||||
@Inject
|
||||
public RepositoryBasedResourceProvider(
|
||||
Provider<TagRootResource> tagRootResource,
|
||||
Provider<BranchRootResource> branchRootResource,
|
||||
Provider<ChangesetRootResource> changesetRootResource,
|
||||
Provider<SourceRootResource> sourceRootResource,
|
||||
Provider<ContentResource> contentResource,
|
||||
Provider<RepositoryPermissionRootResource> permissionRootResource,
|
||||
Provider<DiffRootResource> diffRootResource,
|
||||
Provider<ModificationsRootResource> modificationsRootResource,
|
||||
Provider<FileHistoryRootResource> fileHistoryRootResource,
|
||||
Provider<IncomingRootResource> incomingRootResource, Provider<AnnotateResource> annotateResource) {
|
||||
this.tagRootResource = tagRootResource;
|
||||
this.branchRootResource = branchRootResource;
|
||||
this.changesetRootResource = changesetRootResource;
|
||||
this.sourceRootResource = sourceRootResource;
|
||||
this.contentResource = contentResource;
|
||||
this.permissionRootResource = permissionRootResource;
|
||||
this.diffRootResource = diffRootResource;
|
||||
this.modificationsRootResource = modificationsRootResource;
|
||||
this.fileHistoryRootResource = fileHistoryRootResource;
|
||||
this.incomingRootResource = incomingRootResource;
|
||||
this.annotateResource = annotateResource;
|
||||
}
|
||||
|
||||
public TagRootResource getTagRootResource() {
|
||||
return tagRootResource.get();
|
||||
}
|
||||
|
||||
public BranchRootResource getBranchRootResource() {
|
||||
return branchRootResource.get();
|
||||
}
|
||||
|
||||
public ChangesetRootResource getChangesetRootResource() {
|
||||
return changesetRootResource.get();
|
||||
}
|
||||
|
||||
public SourceRootResource getSourceRootResource() {
|
||||
return sourceRootResource.get();
|
||||
}
|
||||
|
||||
public ContentResource getContentResource() {
|
||||
return contentResource.get();
|
||||
}
|
||||
|
||||
public RepositoryPermissionRootResource getPermissionRootResource() {
|
||||
return permissionRootResource.get();
|
||||
}
|
||||
|
||||
public DiffRootResource getDiffRootResource() {
|
||||
return diffRootResource.get();
|
||||
}
|
||||
|
||||
public ModificationsRootResource getModificationsRootResource() {
|
||||
return modificationsRootResource.get();
|
||||
}
|
||||
|
||||
public FileHistoryRootResource getFileHistoryRootResource() {
|
||||
return fileHistoryRootResource.get();
|
||||
}
|
||||
|
||||
public IncomingRootResource getIncomingRootResource() {
|
||||
return incomingRootResource.get();
|
||||
}
|
||||
|
||||
public AnnotateResource getAnnotateResource() {
|
||||
return annotateResource.get();
|
||||
}
|
||||
}
|
||||
@@ -58,46 +58,19 @@ public class RepositoryResource {
|
||||
|
||||
private final RepositoryManager manager;
|
||||
private final SingleResourceManagerAdapter<Repository, RepositoryDto> adapter;
|
||||
private final Provider<TagRootResource> tagRootResource;
|
||||
private final Provider<BranchRootResource> branchRootResource;
|
||||
private final Provider<ChangesetRootResource> changesetRootResource;
|
||||
private final Provider<SourceRootResource> sourceRootResource;
|
||||
private final Provider<ContentResource> contentResource;
|
||||
private final Provider<RepositoryPermissionRootResource> permissionRootResource;
|
||||
private final Provider<DiffRootResource> diffRootResource;
|
||||
private final Provider<ModificationsRootResource> modificationsRootResource;
|
||||
private final Provider<FileHistoryRootResource> fileHistoryRootResource;
|
||||
private final Provider<IncomingRootResource> incomingRootResource;
|
||||
private final RepositoryBasedResourceProvider resourceProvider;
|
||||
|
||||
@Inject
|
||||
public RepositoryResource(
|
||||
RepositoryToRepositoryDtoMapper repositoryToDtoMapper,
|
||||
RepositoryDtoToRepositoryMapper dtoToRepositoryMapper, RepositoryManager manager,
|
||||
Provider<TagRootResource> tagRootResource,
|
||||
Provider<BranchRootResource> branchRootResource,
|
||||
Provider<ChangesetRootResource> changesetRootResource,
|
||||
Provider<SourceRootResource> sourceRootResource, Provider<ContentResource> contentResource,
|
||||
Provider<RepositoryPermissionRootResource> permissionRootResource,
|
||||
Provider<DiffRootResource> diffRootResource,
|
||||
Provider<ModificationsRootResource> modificationsRootResource,
|
||||
Provider<FileHistoryRootResource> fileHistoryRootResource,
|
||||
Provider<IncomingRootResource> incomingRootResource
|
||||
RepositoryBasedResourceProvider resourceProvider
|
||||
) {
|
||||
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
||||
this.manager = manager;
|
||||
this.repositoryToDtoMapper = repositoryToDtoMapper;
|
||||
this.adapter = new SingleResourceManagerAdapter<>(manager, Repository.class);
|
||||
this.tagRootResource = tagRootResource;
|
||||
this.branchRootResource = branchRootResource;
|
||||
this.changesetRootResource = changesetRootResource;
|
||||
this.sourceRootResource = sourceRootResource;
|
||||
this.contentResource = contentResource;
|
||||
this.permissionRootResource = permissionRootResource;
|
||||
this.diffRootResource = diffRootResource;
|
||||
this.modificationsRootResource = modificationsRootResource;
|
||||
this.fileHistoryRootResource = fileHistoryRootResource;
|
||||
this.incomingRootResource = incomingRootResource;
|
||||
|
||||
this.resourceProvider = resourceProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,52 +184,57 @@ public class RepositoryResource {
|
||||
|
||||
@Path("tags/")
|
||||
public TagRootResource tags() {
|
||||
return tagRootResource.get();
|
||||
return resourceProvider.getTagRootResource();
|
||||
}
|
||||
|
||||
@Path("diff/")
|
||||
public DiffRootResource diff() {
|
||||
return diffRootResource.get();
|
||||
return resourceProvider.getDiffRootResource();
|
||||
}
|
||||
|
||||
@Path("branches/")
|
||||
public BranchRootResource branches() {
|
||||
return branchRootResource.get();
|
||||
return resourceProvider.getBranchRootResource();
|
||||
}
|
||||
|
||||
@Path("changesets/")
|
||||
public ChangesetRootResource changesets() {
|
||||
return changesetRootResource.get();
|
||||
return resourceProvider.getChangesetRootResource();
|
||||
}
|
||||
|
||||
@Path("history/")
|
||||
public FileHistoryRootResource history() {
|
||||
return fileHistoryRootResource.get();
|
||||
return resourceProvider.getFileHistoryRootResource();
|
||||
}
|
||||
|
||||
@Path("sources/")
|
||||
public SourceRootResource sources() {
|
||||
return sourceRootResource.get();
|
||||
return resourceProvider.getSourceRootResource();
|
||||
}
|
||||
|
||||
@Path("content/")
|
||||
public ContentResource content() {
|
||||
return contentResource.get();
|
||||
return resourceProvider.getContentResource();
|
||||
}
|
||||
|
||||
@Path("permissions/")
|
||||
public RepositoryPermissionRootResource permissions() {
|
||||
return permissionRootResource.get();
|
||||
return resourceProvider.getPermissionRootResource();
|
||||
}
|
||||
|
||||
@Path("modifications/")
|
||||
public ModificationsRootResource modifications() {
|
||||
return modificationsRootResource.get();
|
||||
return resourceProvider.getModificationsRootResource();
|
||||
}
|
||||
|
||||
@Path("incoming/")
|
||||
public IncomingRootResource incoming() {
|
||||
return incomingRootResource.get();
|
||||
return resourceProvider.getIncomingRootResource();
|
||||
}
|
||||
|
||||
@Path("annotate/")
|
||||
public AnnotateResource annotate() {
|
||||
return resourceProvider.getAnnotateResource();
|
||||
}
|
||||
|
||||
private Supplier<Repository> loadBy(String namespace, String name) {
|
||||
|
||||
@@ -566,6 +566,22 @@ class ResourceLinks {
|
||||
}
|
||||
}
|
||||
|
||||
public AnnotateLinks annotate() {
|
||||
return new AnnotateLinks(scmPathInfoStore.get());
|
||||
}
|
||||
|
||||
static class AnnotateLinks {
|
||||
private final LinkBuilder annotateLinkBuilder;
|
||||
|
||||
AnnotateLinks(ScmPathInfo pathInfo) {
|
||||
this.annotateLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, AnnotateResource.class);
|
||||
}
|
||||
|
||||
String self(String namespace, String name, String revision, String path) {
|
||||
return annotateLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("annotate").parameters().method("annotate").parameters(revision, path).href();
|
||||
}
|
||||
}
|
||||
|
||||
RepositoryVerbLinks repositoryVerbs() {
|
||||
return new RepositoryVerbLinks(scmPathInfoStore.get());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.BlameLine;
|
||||
import sonia.scm.repository.BlameResult;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.api.BlameCommandBuilder;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.web.RestDispatcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AnnotateResourceTest extends RepositoryTestBase {
|
||||
|
||||
public static final NamespaceAndName NAMESPACE_AND_NAME = new NamespaceAndName("space", "X");
|
||||
public static final String REVISION = "123";
|
||||
public static final String PATH = "some/file";
|
||||
@Mock
|
||||
private RepositoryServiceFactory serviceFactory;
|
||||
@Mock
|
||||
private RepositoryService service;
|
||||
@Mock
|
||||
private BlameCommandBuilder blameCommandBuilder;
|
||||
|
||||
private final RestDispatcher dispatcher = new RestDispatcher();
|
||||
private final MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
@BeforeEach
|
||||
void initResource() {
|
||||
BlameResultToBlameDtoMapperImpl mapper = new BlameResultToBlameDtoMapperImpl();
|
||||
mapper.setResourceLinks(ResourceLinksMock.createMock(URI.create("/")));
|
||||
annotateResource = new AnnotateResource(serviceFactory, mapper);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void initRepositoryService() {
|
||||
when(serviceFactory.create(NAMESPACE_AND_NAME)).thenReturn(service);
|
||||
when(service.getBlameCommand()).thenReturn(blameCommandBuilder);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void initBlameCommand() throws IOException {
|
||||
BlameLine line1 = new BlameLine(
|
||||
0,
|
||||
"100",
|
||||
System.currentTimeMillis(),
|
||||
new Person("Arthur Dent", "arthur@hitchhiker.com"),
|
||||
"first try",
|
||||
"jump"
|
||||
);
|
||||
BlameLine line2 = new BlameLine(
|
||||
1,
|
||||
"42",
|
||||
System.currentTimeMillis(),
|
||||
new Person("Zaphod Beeblebrox", "zaphod@hitchhiker.com"),
|
||||
"got it",
|
||||
"heart of gold"
|
||||
);
|
||||
BlameResult result = new BlameResult(asList(line1, line2));
|
||||
when(blameCommandBuilder.setRevision(REVISION)).thenReturn(blameCommandBuilder);
|
||||
when(blameCommandBuilder.getBlameResult(PATH)).thenReturn(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAnnotations() throws URISyntaxException, UnsupportedEncodingException, JsonProcessingException {
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + NAMESPACE_AND_NAME + "/annotate/" + REVISION + "/" + PATH);
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
|
||||
String content = response.getContentAsString();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode jsonNode = mapper.readTree(content);
|
||||
JsonNode blameLines = jsonNode.get("lines");
|
||||
assertThat(blameLines.isArray()).isTrue();
|
||||
assertThat(jsonNode.get("_links").get("self").get("href").asText())
|
||||
.isEqualTo("/v2/repositories/space/X/annotate/123/some%2Ffile");
|
||||
|
||||
Iterator<JsonNode> lineIterator = blameLines.iterator();
|
||||
JsonNode line1 = lineIterator.next();
|
||||
assertThat(line1.get("author").get("mail").asText()).isEqualTo("arthur@hitchhiker.com");
|
||||
assertThat(line1.get("author").get("name").asText()).isEqualTo("Arthur Dent");
|
||||
assertThat(line1.get("code").asText()).isEqualTo("jump");
|
||||
assertThat(line1.get("description").asText()).isEqualTo("first try");
|
||||
assertThat(line1.get("lineNumber").asInt()).isEqualTo(0);
|
||||
assertThat(line1.get("revision").asText()).isEqualTo("100");
|
||||
|
||||
JsonNode line2 = lineIterator.next();
|
||||
assertThat(line2.get("author").get("mail").asText()).isEqualTo("zaphod@hitchhiker.com");
|
||||
assertThat(line2.get("author").get("name").asText()).isEqualTo("Zaphod Beeblebrox");
|
||||
assertThat(line2.get("code").asText()).isEqualTo("heart of gold");
|
||||
assertThat(line2.get("description").asText()).isEqualTo("got it");
|
||||
assertThat(line2.get("lineNumber").asInt()).isEqualTo(1);
|
||||
assertThat(line2.get("revision").asText()).isEqualTo("42");
|
||||
|
||||
assertThat(lineIterator.hasNext()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -103,8 +103,6 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
||||
|
||||
private BranchChangesetCollectionToDtoMapper changesetCollectionToDtoMapper;
|
||||
|
||||
private BranchRootResource branchRootResource;
|
||||
|
||||
@Mock
|
||||
private BranchCollectionToDtoMapper branchCollectionToDtoMapper;
|
||||
|
||||
@@ -126,7 +124,6 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
||||
changesetCollectionToDtoMapper = new BranchChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||
BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks);
|
||||
branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper, resourceLinks);
|
||||
super.branchRootResource = Providers.of(branchRootResource);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||
|
||||
@@ -91,8 +91,6 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
|
||||
@InjectMocks
|
||||
private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
|
||||
private ChangesetRootResource changesetRootResource;
|
||||
|
||||
private final Subject subject = mock(Subject.class);
|
||||
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||
|
||||
@@ -100,7 +98,6 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
|
||||
public void prepareEnvironment() {
|
||||
changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||
changesetRootResource = new ChangesetRootResource(serviceFactory, changesetCollectionToDtoMapper, changesetToChangesetDtoMapper);
|
||||
super.changesetRootResource = Providers.of(changesetRootResource);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
|
||||
|
||||
@@ -90,9 +90,6 @@ public class DiffResourceTest extends RepositoryTestBase {
|
||||
@Mock
|
||||
private DiffResultToDiffResultDtoMapper diffResultToDiffResultDtoMapper;
|
||||
|
||||
private DiffRootResource diffRootResource;
|
||||
|
||||
|
||||
private final Subject subject = mock(Subject.class);
|
||||
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||
|
||||
@@ -100,7 +97,6 @@ public class DiffResourceTest extends RepositoryTestBase {
|
||||
@Before
|
||||
public void prepareEnvironment() {
|
||||
diffRootResource = new DiffRootResource(serviceFactory, diffResultToDiffResultDtoMapper);
|
||||
super.diffRootResource = Providers.of(diffRootResource);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||
|
||||
@@ -92,8 +92,6 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
|
||||
@InjectMocks
|
||||
private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
|
||||
private FileHistoryRootResource fileHistoryRootResource;
|
||||
|
||||
private RestDispatcher dispatcher = new RestDispatcher();
|
||||
|
||||
private final Subject subject = mock(Subject.class);
|
||||
@@ -103,7 +101,6 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
|
||||
public void prepareEnvironment() {
|
||||
fileHistoryCollectionToDtoMapper = new FileHistoryCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||
fileHistoryRootResource = new FileHistoryRootResource(serviceFactory, fileHistoryCollectionToDtoMapper);
|
||||
super.fileHistoryRootResource = Providers.of(fileHistoryRootResource);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||
|
||||
@@ -110,9 +110,6 @@ public class IncomingRootResourceTest extends RepositoryTestBase {
|
||||
@InjectMocks
|
||||
private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
|
||||
private IncomingRootResource incomingRootResource;
|
||||
|
||||
|
||||
private final Subject subject = mock(Subject.class);
|
||||
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||
|
||||
@@ -121,7 +118,6 @@ public class IncomingRootResourceTest extends RepositoryTestBase {
|
||||
public void prepareEnvironment() {
|
||||
incomingChangesetCollectionToDtoMapper = new IncomingChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||
incomingRootResource = new IncomingRootResource(serviceFactory, incomingChangesetCollectionToDtoMapper, diffResultToDiffResultDtoMapper);
|
||||
super.incomingRootResource = Providers.of(incomingRootResource);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
|
||||
when(serviceFactory.create(REPOSITORY)).thenReturn(repositoryService);
|
||||
|
||||
@@ -88,9 +88,6 @@ public class ModificationsResourceTest extends RepositoryTestBase {
|
||||
@InjectMocks
|
||||
private ModificationsToDtoMapperImpl modificationsToDtoMapper;
|
||||
|
||||
private ModificationsRootResource modificationsRootResource;
|
||||
|
||||
|
||||
private final Subject subject = mock(Subject.class);
|
||||
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||
|
||||
@@ -98,7 +95,6 @@ public class ModificationsResourceTest extends RepositoryTestBase {
|
||||
@Before
|
||||
public void prepareEnvironment() {
|
||||
modificationsRootResource = new ModificationsRootResource(serviceFactory, modificationsToDtoMapper);
|
||||
super.modificationsRootResource = Providers.of(modificationsRootResource);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
|
||||
|
||||
@@ -144,8 +144,6 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
|
||||
|
||||
private RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper;
|
||||
|
||||
private RepositoryPermissionRootResource repositoryPermissionRootResource;
|
||||
|
||||
private final Subject subject = mock(Subject.class);
|
||||
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||
|
||||
@@ -154,8 +152,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
|
||||
public void prepareEnvironment() {
|
||||
initMocks(this);
|
||||
repositoryPermissionCollectionToDtoMapper = new RepositoryPermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks);
|
||||
repositoryPermissionRootResource = new RepositoryPermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, repositoryPermissionCollectionToDtoMapper, resourceLinks, repositoryManager);
|
||||
super.permissionRootResource = Providers.of(repositoryPermissionRootResource);
|
||||
permissionRootResource = new RepositoryPermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, repositoryPermissionCollectionToDtoMapper, resourceLinks, repositoryManager);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
subjectThreadState.bind();
|
||||
ThreadContext.bind(subject);
|
||||
|
||||
@@ -122,7 +122,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
||||
super.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
||||
super.manager = repositoryManager;
|
||||
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
|
||||
super.repositoryCollectionResource = Providers.of(new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer));
|
||||
super.repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||
when(scmPathInfoStore.get()).thenReturn(uriInfo);
|
||||
|
||||
@@ -24,46 +24,50 @@
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.inject.util.Providers;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import static com.google.inject.util.Providers.of;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public abstract class RepositoryTestBase {
|
||||
abstract class RepositoryTestBase {
|
||||
|
||||
|
||||
protected RepositoryToRepositoryDtoMapper repositoryToDtoMapper;
|
||||
protected RepositoryDtoToRepositoryMapper dtoToRepositoryMapper;
|
||||
protected RepositoryManager manager;
|
||||
protected Provider<TagRootResource> tagRootResource;
|
||||
protected Provider<BranchRootResource> branchRootResource;
|
||||
protected Provider<ChangesetRootResource> changesetRootResource;
|
||||
protected Provider<SourceRootResource> sourceRootResource;
|
||||
protected Provider<ContentResource> contentResource;
|
||||
protected Provider<RepositoryPermissionRootResource> permissionRootResource;
|
||||
protected Provider<DiffRootResource> diffRootResource;
|
||||
protected Provider<ModificationsRootResource> modificationsRootResource;
|
||||
protected Provider<FileHistoryRootResource> fileHistoryRootResource;
|
||||
protected Provider<RepositoryCollectionResource> repositoryCollectionResource;
|
||||
protected Provider<IncomingRootResource> incomingRootResource;
|
||||
RepositoryToRepositoryDtoMapper repositoryToDtoMapper;
|
||||
RepositoryDtoToRepositoryMapper dtoToRepositoryMapper;
|
||||
RepositoryManager manager;
|
||||
TagRootResource tagRootResource;
|
||||
BranchRootResource branchRootResource;
|
||||
ChangesetRootResource changesetRootResource;
|
||||
SourceRootResource sourceRootResource;
|
||||
ContentResource contentResource;
|
||||
RepositoryPermissionRootResource permissionRootResource;
|
||||
DiffRootResource diffRootResource;
|
||||
ModificationsRootResource modificationsRootResource;
|
||||
FileHistoryRootResource fileHistoryRootResource;
|
||||
IncomingRootResource incomingRootResource;
|
||||
RepositoryCollectionResource repositoryCollectionResource;
|
||||
AnnotateResource annotateResource;
|
||||
|
||||
|
||||
RepositoryRootResource getRepositoryRootResource() {
|
||||
return new RepositoryRootResource(Providers.of(new RepositoryResource(
|
||||
RepositoryBasedResourceProvider repositoryBasedResourceProvider = new RepositoryBasedResourceProvider(
|
||||
of(tagRootResource),
|
||||
of(branchRootResource),
|
||||
of(changesetRootResource),
|
||||
of(sourceRootResource),
|
||||
of(contentResource),
|
||||
of(permissionRootResource),
|
||||
of(diffRootResource),
|
||||
of(modificationsRootResource),
|
||||
of(fileHistoryRootResource),
|
||||
of(incomingRootResource),
|
||||
of(annotateResource));
|
||||
return new RepositoryRootResource(
|
||||
of(new RepositoryResource(
|
||||
repositoryToDtoMapper,
|
||||
dtoToRepositoryMapper,
|
||||
manager,
|
||||
tagRootResource,
|
||||
branchRootResource,
|
||||
changesetRootResource,
|
||||
sourceRootResource,
|
||||
contentResource,
|
||||
permissionRootResource,
|
||||
diffRootResource,
|
||||
modificationsRootResource,
|
||||
fileHistoryRootResource,
|
||||
incomingRootResource)), repositoryCollectionResource);
|
||||
repositoryBasedResourceProvider
|
||||
)),
|
||||
of(repositoryCollectionResource));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ package sonia.scm.api.v2.resources;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -33,47 +34,48 @@ public class ResourceLinksMock {
|
||||
public static ResourceLinks createMock(URI baseUri) {
|
||||
ResourceLinks resourceLinks = mock(ResourceLinks.class);
|
||||
|
||||
ScmPathInfo uriInfo = mock(ScmPathInfo.class);
|
||||
when(uriInfo.getApiRestUri()).thenReturn(baseUri);
|
||||
ScmPathInfo pathInfo = mock(ScmPathInfo.class);
|
||||
when(pathInfo.getApiRestUri()).thenReturn(baseUri);
|
||||
|
||||
ResourceLinks.UserLinks userLinks = new ResourceLinks.UserLinks(uriInfo);
|
||||
when(resourceLinks.user()).thenReturn(userLinks);
|
||||
when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(uriInfo,userLinks));
|
||||
when(resourceLinks.userCollection()).thenReturn(new ResourceLinks.UserCollectionLinks(uriInfo));
|
||||
when(resourceLinks.userPermissions()).thenReturn(new ResourceLinks.UserPermissionLinks(uriInfo));
|
||||
when(resourceLinks.autoComplete()).thenReturn(new ResourceLinks.AutoCompleteLinks(uriInfo));
|
||||
when(resourceLinks.group()).thenReturn(new ResourceLinks.GroupLinks(uriInfo));
|
||||
when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(uriInfo));
|
||||
when(resourceLinks.groupPermissions()).thenReturn(new ResourceLinks.GroupPermissionLinks(uriInfo));
|
||||
when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(uriInfo));
|
||||
when(resourceLinks.incoming()).thenReturn(new ResourceLinks.IncomingLinks(uriInfo));
|
||||
when(resourceLinks.repositoryCollection()).thenReturn(new ResourceLinks.RepositoryCollectionLinks(uriInfo));
|
||||
when(resourceLinks.tag()).thenReturn(new ResourceLinks.TagCollectionLinks(uriInfo));
|
||||
when(resourceLinks.branchCollection()).thenReturn(new ResourceLinks.BranchCollectionLinks(uriInfo));
|
||||
when(resourceLinks.changeset()).thenReturn(new ResourceLinks.ChangesetLinks(uriInfo));
|
||||
when(resourceLinks.fileHistory()).thenReturn(new ResourceLinks.FileHistoryLinks(uriInfo));
|
||||
when(resourceLinks.source()).thenReturn(new ResourceLinks.SourceLinks(uriInfo));
|
||||
when(resourceLinks.repositoryPermission()).thenReturn(new ResourceLinks.RepositoryPermissionLinks(uriInfo));
|
||||
when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo));
|
||||
when(resourceLinks.branch()).thenReturn(new ResourceLinks.BranchLinks(uriInfo));
|
||||
when(resourceLinks.diff()).thenReturn(new ResourceLinks.DiffLinks(uriInfo));
|
||||
when(resourceLinks.modifications()).thenReturn(new ResourceLinks.ModificationsLinks(uriInfo));
|
||||
when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(uriInfo));
|
||||
when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo));
|
||||
when(resourceLinks.installedPluginCollection()).thenReturn(new ResourceLinks.InstalledPluginCollectionLinks(uriInfo));
|
||||
when(resourceLinks.availablePluginCollection()).thenReturn(new ResourceLinks.AvailablePluginCollectionLinks(uriInfo));
|
||||
when(resourceLinks.pendingPluginCollection()).thenReturn(new ResourceLinks.PendingPluginCollectionLinks(uriInfo));
|
||||
when(resourceLinks.installedPlugin()).thenReturn(new ResourceLinks.InstalledPluginLinks(uriInfo));
|
||||
when(resourceLinks.availablePlugin()).thenReturn(new ResourceLinks.AvailablePluginLinks(uriInfo));
|
||||
when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(uriInfo));
|
||||
when(resourceLinks.uiPlugin()).thenReturn(new ResourceLinks.UIPluginLinks(uriInfo));
|
||||
when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(uriInfo));
|
||||
when(resourceLinks.index()).thenReturn(new ResourceLinks.IndexLinks(uriInfo));
|
||||
when(resourceLinks.permissions()).thenReturn(new ResourceLinks.PermissionsLinks(uriInfo));
|
||||
when(resourceLinks.repositoryVerbs()).thenReturn(new ResourceLinks.RepositoryVerbLinks(uriInfo));
|
||||
when(resourceLinks.repositoryRole()).thenReturn(new ResourceLinks.RepositoryRoleLinks(uriInfo));
|
||||
when(resourceLinks.repositoryRoleCollection()).thenReturn(new ResourceLinks.RepositoryRoleCollectionLinks(uriInfo));
|
||||
when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(uriInfo));
|
||||
ResourceLinks.UserLinks userLinks = new ResourceLinks.UserLinks(pathInfo);
|
||||
lenient().when(resourceLinks.user()).thenReturn(userLinks);
|
||||
lenient().when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(pathInfo,userLinks));
|
||||
lenient().when(resourceLinks.userCollection()).thenReturn(new ResourceLinks.UserCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.userPermissions()).thenReturn(new ResourceLinks.UserPermissionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.autoComplete()).thenReturn(new ResourceLinks.AutoCompleteLinks(pathInfo));
|
||||
lenient().when(resourceLinks.group()).thenReturn(new ResourceLinks.GroupLinks(pathInfo));
|
||||
lenient().when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.groupPermissions()).thenReturn(new ResourceLinks.GroupPermissionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(pathInfo));
|
||||
lenient().when(resourceLinks.incoming()).thenReturn(new ResourceLinks.IncomingLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryCollection()).thenReturn(new ResourceLinks.RepositoryCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.tag()).thenReturn(new ResourceLinks.TagCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.branchCollection()).thenReturn(new ResourceLinks.BranchCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.changeset()).thenReturn(new ResourceLinks.ChangesetLinks(pathInfo));
|
||||
lenient().when(resourceLinks.fileHistory()).thenReturn(new ResourceLinks.FileHistoryLinks(pathInfo));
|
||||
lenient().when(resourceLinks.source()).thenReturn(new ResourceLinks.SourceLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryPermission()).thenReturn(new ResourceLinks.RepositoryPermissionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(pathInfo));
|
||||
lenient().when(resourceLinks.branch()).thenReturn(new ResourceLinks.BranchLinks(pathInfo));
|
||||
lenient().when(resourceLinks.diff()).thenReturn(new ResourceLinks.DiffLinks(pathInfo));
|
||||
lenient().when(resourceLinks.modifications()).thenReturn(new ResourceLinks.ModificationsLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.installedPluginCollection()).thenReturn(new ResourceLinks.InstalledPluginCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.availablePluginCollection()).thenReturn(new ResourceLinks.AvailablePluginCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.pendingPluginCollection()).thenReturn(new ResourceLinks.PendingPluginCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.installedPlugin()).thenReturn(new ResourceLinks.InstalledPluginLinks(pathInfo));
|
||||
lenient().when(resourceLinks.availablePlugin()).thenReturn(new ResourceLinks.AvailablePluginLinks(pathInfo));
|
||||
lenient().when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.uiPlugin()).thenReturn(new ResourceLinks.UIPluginLinks(pathInfo));
|
||||
lenient().when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(pathInfo));
|
||||
lenient().when(resourceLinks.index()).thenReturn(new ResourceLinks.IndexLinks(pathInfo));
|
||||
lenient().when(resourceLinks.permissions()).thenReturn(new ResourceLinks.PermissionsLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryVerbs()).thenReturn(new ResourceLinks.RepositoryVerbLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryRole()).thenReturn(new ResourceLinks.RepositoryRoleLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryRoleCollection()).thenReturn(new ResourceLinks.RepositoryRoleCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(pathInfo));
|
||||
lenient().when(resourceLinks.annotate()).thenReturn(new ResourceLinks.AnnotateLinks(pathInfo));
|
||||
|
||||
return resourceLinks;
|
||||
}
|
||||
|
||||
@@ -74,8 +74,7 @@ public class SourceRootResourceTest extends RepositoryTestBase {
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
||||
when(service.getBrowseCommand()).thenReturn(browseCommandBuilder);
|
||||
|
||||
SourceRootResource sourceRootResource = new SourceRootResource(serviceFactory, browserResultToFileObjectDtoMapper);
|
||||
super.sourceRootResource = Providers.of(sourceRootResource);
|
||||
sourceRootResource = new SourceRootResource(serviceFactory, browserResultToFileObjectDtoMapper);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
}
|
||||
|
||||
|
||||
@@ -84,9 +84,6 @@ public class TagRootResourceTest extends RepositoryTestBase {
|
||||
@InjectMocks
|
||||
private TagToTagDtoMapperImpl tagToTagDtoMapper;
|
||||
|
||||
private TagRootResource tagRootResource;
|
||||
|
||||
|
||||
private final Subject subject = mock(Subject.class);
|
||||
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||
|
||||
@@ -95,7 +92,6 @@ public class TagRootResourceTest extends RepositoryTestBase {
|
||||
public void prepareEnvironment() throws Exception {
|
||||
tagCollectionToDtoMapper = new TagCollectionToDtoMapper(resourceLinks, tagToTagDtoMapper);
|
||||
tagRootResource = new TagRootResource(serviceFactory, tagCollectionToDtoMapper, tagToTagDtoMapper);
|
||||
super.tagRootResource = Providers.of(tagRootResource);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
|
||||
|
||||
154
yarn.lock
154
yarn.lock
@@ -54,6 +54,16 @@
|
||||
semver "^5.4.1"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/generator@^7.10.1":
|
||||
version "7.10.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.2.tgz#0fa5b5b2389db8bfdfcc3492b551ee20f5dd69a9"
|
||||
integrity sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA==
|
||||
dependencies:
|
||||
"@babel/types" "^7.10.2"
|
||||
jsesc "^2.5.1"
|
||||
lodash "^4.17.13"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/generator@^7.4.0", "@babel/generator@^7.9.6":
|
||||
version "7.9.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43"
|
||||
@@ -145,6 +155,15 @@
|
||||
"@babel/traverse" "^7.8.3"
|
||||
"@babel/types" "^7.8.3"
|
||||
|
||||
"@babel/helper-function-name@^7.10.1":
|
||||
version "7.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz#92bd63829bfc9215aca9d9defa85f56b539454f4"
|
||||
integrity sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==
|
||||
dependencies:
|
||||
"@babel/helper-get-function-arity" "^7.10.1"
|
||||
"@babel/template" "^7.10.1"
|
||||
"@babel/types" "^7.10.1"
|
||||
|
||||
"@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5":
|
||||
version "7.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c"
|
||||
@@ -154,6 +173,13 @@
|
||||
"@babel/template" "^7.8.3"
|
||||
"@babel/types" "^7.9.5"
|
||||
|
||||
"@babel/helper-get-function-arity@^7.10.1":
|
||||
version "7.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz#7303390a81ba7cb59613895a192b93850e373f7d"
|
||||
integrity sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==
|
||||
dependencies:
|
||||
"@babel/types" "^7.10.1"
|
||||
|
||||
"@babel/helper-get-function-arity@^7.8.3":
|
||||
version "7.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5"
|
||||
@@ -248,6 +274,13 @@
|
||||
"@babel/template" "^7.8.3"
|
||||
"@babel/types" "^7.8.3"
|
||||
|
||||
"@babel/helper-split-export-declaration@^7.10.1":
|
||||
version "7.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f"
|
||||
integrity sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==
|
||||
dependencies:
|
||||
"@babel/types" "^7.10.1"
|
||||
|
||||
"@babel/helper-split-export-declaration@^7.8.3":
|
||||
version "7.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9"
|
||||
@@ -951,7 +984,7 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.3.3":
|
||||
"@babel/template@^7.10.1", "@babel/template@^7.3.3":
|
||||
version "7.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
|
||||
integrity sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==
|
||||
@@ -969,7 +1002,7 @@
|
||||
"@babel/parser" "^7.8.6"
|
||||
"@babel/types" "^7.8.6"
|
||||
|
||||
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.8.3", "@babel/traverse@^7.9.6":
|
||||
"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.8.3", "@babel/traverse@^7.9.6":
|
||||
version "7.9.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.6.tgz#5540d7577697bf619cc57b92aa0f1c231a94f442"
|
||||
integrity sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==
|
||||
@@ -984,6 +1017,21 @@
|
||||
globals "^11.1.0"
|
||||
lodash "^4.17.13"
|
||||
|
||||
"@babel/traverse@^7.4.5":
|
||||
version "7.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.1.tgz#bbcef3031e4152a6c0b50147f4958df54ca0dd27"
|
||||
integrity sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.10.1"
|
||||
"@babel/generator" "^7.10.1"
|
||||
"@babel/helper-function-name" "^7.10.1"
|
||||
"@babel/helper-split-export-declaration" "^7.10.1"
|
||||
"@babel/parser" "^7.10.1"
|
||||
"@babel/types" "^7.10.1"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
lodash "^4.17.13"
|
||||
|
||||
"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.7.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6":
|
||||
version "7.9.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7"
|
||||
@@ -993,7 +1041,7 @@
|
||||
lodash "^4.17.13"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.10.1", "@babel/types@^7.3.3":
|
||||
"@babel/types@^7.10.1", "@babel/types@^7.10.2", "@babel/types@^7.3.3":
|
||||
version "7.10.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.2.tgz#30283be31cad0dbf6fb00bd40641ca0ea675172d"
|
||||
integrity sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==
|
||||
@@ -1068,7 +1116,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.6.6.tgz#62266c5f0eac6941fece302abad69f2ee7e25e44"
|
||||
integrity sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ==
|
||||
|
||||
"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.1":
|
||||
"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.8":
|
||||
version "0.8.8"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
|
||||
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
|
||||
@@ -1129,7 +1177,7 @@
|
||||
"@emotion/styled-base" "^10.0.27"
|
||||
babel-plugin-emotion "^10.0.27"
|
||||
|
||||
"@emotion/stylis@0.8.5":
|
||||
"@emotion/stylis@0.8.5", "@emotion/stylis@^0.8.4":
|
||||
version "0.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04"
|
||||
integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
|
||||
@@ -1139,7 +1187,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.7.1.tgz#50f63225e712d99e2b2b39c19c70fff023793ca5"
|
||||
integrity sha512-/SLmSIkN13M//53TtNxgxo57mcJk/UJIDFRKwOiLIBEyBHEcipgR6hNMQ/59Sl4VjCJ0Z/3zeAZyvnSLPG/1HQ==
|
||||
|
||||
"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.0":
|
||||
"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4":
|
||||
version "0.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
|
||||
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
|
||||
@@ -3260,10 +3308,10 @@
|
||||
dependencies:
|
||||
"@storybook/addon-storyshots" "*"
|
||||
|
||||
"@types/styled-components@^4.1.19":
|
||||
version "4.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-4.4.3.tgz#74dd00ad760845a98890a8539361d8afc32059de"
|
||||
integrity sha512-U0udeNOZBfUkJycmGJwmzun0FBt11rZy08weVQmE2xfUNAbX8AGOEWxWna2d+qAUKxKgMlcG+TZT0+K2FfDcnQ==
|
||||
"@types/styled-components@^5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.0.tgz#24d3412ba5395aa06e14fbc93c52f9454cebd0d6"
|
||||
integrity sha512-ZFlLCuwF5r+4Vb7JUmd+Yr2S0UBdBGmI7ctFTgJMypIp3xOHI4LCFVn2dKMvpk6xDB2hLRykrEWMBwJEpUAUIQ==
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "*"
|
||||
"@types/react" "*"
|
||||
@@ -4098,6 +4146,11 @@ babel-code-frame@^6.22.0:
|
||||
esutils "^2.0.2"
|
||||
js-tokens "^3.0.2"
|
||||
|
||||
babel-core@7.0.0-bridge.0:
|
||||
version "7.0.0-bridge.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
|
||||
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
|
||||
|
||||
babel-eslint@^10.0.3:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
|
||||
@@ -5891,14 +5944,14 @@ css-select@^2.0.0:
|
||||
domutils "^1.7.0"
|
||||
nth-check "^1.0.2"
|
||||
|
||||
css-to-react-native@^2.2.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.3.2.tgz#e75e2f8f7aa385b4c3611c52b074b70a002f2e7d"
|
||||
integrity sha512-VOFaeZA053BqvvvqIA8c9n0+9vFppVBAHCp6JgFTtTMU3Mzi+XnelJ9XC9ul3BqFzZyQ5N+H0SnwsWT2Ebchxw==
|
||||
css-to-react-native@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756"
|
||||
integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==
|
||||
dependencies:
|
||||
camelize "^1.0.0"
|
||||
css-color-keywords "^1.0.0"
|
||||
postcss-value-parser "^3.3.0"
|
||||
postcss-value-parser "^4.0.2"
|
||||
|
||||
css-tree@1.0.0-alpha.37:
|
||||
version "1.0.0-alpha.37"
|
||||
@@ -7435,7 +7488,7 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||
|
||||
fault@^1.0.0, fault@^1.0.2:
|
||||
fault@^1.0.0:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13"
|
||||
integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==
|
||||
@@ -7969,7 +8022,7 @@ gitconfiglocal@^1.0.0:
|
||||
dependencies:
|
||||
ini "^1.3.2"
|
||||
|
||||
gitdiff-parser@^0.1.2:
|
||||
gitdiff-parser@^0.1.2, "gitdiff-parser@https://github.com/scm-manager/gitdiff-parser#617747460280bf4522bb84d217a9064ac8eb6d3d":
|
||||
version "0.1.2"
|
||||
resolved "https://github.com/scm-manager/gitdiff-parser#617747460280bf4522bb84d217a9064ac8eb6d3d"
|
||||
|
||||
@@ -8334,7 +8387,7 @@ hoist-non-react-statics@^2.3.1:
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
||||
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
|
||||
|
||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
|
||||
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
@@ -9287,11 +9340,6 @@ is-utf8@^0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
|
||||
|
||||
is-what@^3.3.1:
|
||||
version "3.8.0"
|
||||
resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.8.0.tgz#610bc46a524355f2424eb85eedc6ebbbf7e1ff8c"
|
||||
integrity sha512-UKeBoQfV8bjlM4pmx1FLDHdxslW/1mTksEs8ReVsilPmUv5cORd4+2/wFcviI3cUjrLybxCjzc8DnodAzJ/Wrg==
|
||||
|
||||
is-whitespace-character@^1.0.0:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7"
|
||||
@@ -10775,7 +10823,7 @@ lower-case@^2.0.1:
|
||||
dependencies:
|
||||
tslib "^1.10.0"
|
||||
|
||||
lowlight@^1.13.0:
|
||||
lowlight@1.13.1, lowlight@^1.13.0, lowlight@~1.11.0:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.13.1.tgz#c4f0e03906ebd23fedf2d258f6ab2f6324cf90eb"
|
||||
integrity sha512-kQ71/T6RksEVz9AlPq07/2m+SU/1kGvt9k39UtvHX760u4SaWakaYH7hYgH5n6sTsCWk4MVYzUzLU59aN5CSmQ==
|
||||
@@ -10783,14 +10831,6 @@ lowlight@^1.13.0:
|
||||
fault "^1.0.0"
|
||||
highlight.js "~9.16.0"
|
||||
|
||||
lowlight@~1.11.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.11.0.tgz#1304d83005126d4e8b1dc0f07981e9b689ec2efc"
|
||||
integrity sha512-xrGGN6XLL7MbTMdPD6NfWPwY43SNkjf/d0mecSx/CW36fUZTjRHEq0/Cdug3TWKtRXLWi7iMl1eP0olYxj/a4A==
|
||||
dependencies:
|
||||
fault "^1.0.2"
|
||||
highlight.js "~9.13.0"
|
||||
|
||||
lru-cache@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
|
||||
@@ -11019,13 +11059,6 @@ meow@^7.0.0:
|
||||
type-fest "^0.13.1"
|
||||
yargs-parser "^18.1.3"
|
||||
|
||||
merge-anything@^2.2.4:
|
||||
version "2.4.4"
|
||||
resolved "https://registry.yarnpkg.com/merge-anything/-/merge-anything-2.4.4.tgz#6226b2ac3d3d3fc5fb9e8d23aa400df25f98fdf0"
|
||||
integrity sha512-l5XlriUDJKQT12bH+rVhAHjwIuXWdAIecGwsYjv2LJo+dA1AeRTmeQS+3QBpO6lEthBMDi2IUMpLC1yyRvGlwQ==
|
||||
dependencies:
|
||||
is-what "^3.3.1"
|
||||
|
||||
merge-deep@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.2.tgz#f39fa100a4f1bd34ff29f7d2bf4508fbb8d83ad2"
|
||||
@@ -12783,7 +12816,7 @@ postcss-unique-selectors@^4.0.1:
|
||||
postcss "^7.0.0"
|
||||
uniqs "^2.0.0"
|
||||
|
||||
postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0:
|
||||
postcss-value-parser@^3.0.0:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
|
||||
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
|
||||
@@ -12877,7 +12910,7 @@ pretty-hrtime@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
|
||||
integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=
|
||||
|
||||
prismjs@^1.8.4:
|
||||
prismjs@^1.16.0, prismjs@^1.8.4:
|
||||
version "1.20.0"
|
||||
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.20.0.tgz#9b685fc480a3514ee7198eac6a3bf5024319ff03"
|
||||
integrity sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ==
|
||||
@@ -12975,7 +13008,7 @@ prop-types-exact@^1.2.0:
|
||||
object.assign "^4.1.0"
|
||||
reflect.ownkeys "^0.2.0"
|
||||
|
||||
prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
@@ -13492,6 +13525,16 @@ react-syntax-highlighter@^11.0.2:
|
||||
prismjs "^1.8.4"
|
||||
refractor "^2.4.1"
|
||||
|
||||
"react-syntax-highlighter@https://github.com/conorhastings/react-syntax-highlighter#08bcf49b1aa7877ce94f7208e73dfa6bef8b26e7":
|
||||
version "12.0.2"
|
||||
resolved "https://github.com/conorhastings/react-syntax-highlighter#08bcf49b1aa7877ce94f7208e73dfa6bef8b26e7"
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
highlight.js "~9.13.0"
|
||||
lowlight "~1.11.0"
|
||||
prismjs "^1.16.0"
|
||||
refractor "^2.10.1"
|
||||
|
||||
react-test-renderer@^16.0.0-0, react-test-renderer@^16.10.2:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1"
|
||||
@@ -13804,7 +13847,7 @@ reflect.ownkeys@^0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
|
||||
integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=
|
||||
|
||||
refractor@^2.4.1:
|
||||
refractor@^2.10.1, refractor@^2.4.1:
|
||||
version "2.10.1"
|
||||
resolved "https://registry.yarnpkg.com/refractor/-/refractor-2.10.1.tgz#166c32f114ed16fd96190ad21d5193d3afc7d34e"
|
||||
integrity sha512-Xh9o7hQiQlDbxo5/XkOX6H+x/q8rmlmZKr97Ie1Q8ZM32IRRd3B/UxuA/yXDW79DBSXGWxm2yRTbcTVmAciJRw==
|
||||
@@ -15138,23 +15181,20 @@ style-loader@^1.0.0:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^2.6.6"
|
||||
|
||||
styled-components@^4.4.0:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-4.4.1.tgz#e0631e889f01db67df4de576fedaca463f05c2f2"
|
||||
integrity sha512-RNqj14kYzw++6Sr38n7197xG33ipEOktGElty4I70IKzQF1jzaD1U4xQ+Ny/i03UUhHlC5NWEO+d8olRCDji6g==
|
||||
styled-components@^5.1.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.1.1.tgz#96dfb02a8025794960863b9e8e365e3b6be5518d"
|
||||
integrity sha512-1ps8ZAYu2Husx+Vz8D+MvXwEwvMwFv+hqqUwhNlDN5ybg6A+3xyW1ECrAgywhvXapNfXiz79jJyU0x22z0FFTg==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.0.0"
|
||||
"@babel/traverse" "^7.0.0"
|
||||
"@emotion/is-prop-valid" "^0.8.1"
|
||||
"@emotion/unitless" "^0.7.0"
|
||||
"@babel/traverse" "^7.4.5"
|
||||
"@emotion/is-prop-valid" "^0.8.8"
|
||||
"@emotion/stylis" "^0.8.4"
|
||||
"@emotion/unitless" "^0.7.4"
|
||||
babel-plugin-styled-components ">= 1"
|
||||
css-to-react-native "^2.2.2"
|
||||
memoize-one "^5.0.0"
|
||||
merge-anything "^2.2.4"
|
||||
prop-types "^15.5.4"
|
||||
react-is "^16.6.0"
|
||||
stylis "^3.5.0"
|
||||
stylis-rule-sheet "^0.0.10"
|
||||
css-to-react-native "^3.0.0"
|
||||
hoist-non-react-statics "^3.0.0"
|
||||
shallowequal "^1.1.0"
|
||||
supports-color "^5.5.0"
|
||||
|
||||
stylehacks@^4.0.0:
|
||||
|
||||
Reference in New Issue
Block a user