Merge with 2.0.0-m3

This commit is contained in:
René Pfeuffer
2018-11-01 12:03:49 +01:00
77 changed files with 1421 additions and 5168 deletions

View File

@@ -757,7 +757,7 @@
<guice.version>4.0</guice.version>
<!-- event bus -->
<legman.version>1.3.0</legman.version>
<legman.version>1.4.0</legman.version>
<!-- webserver -->
<jetty.version>9.2.10.v20150310</jetty.version>

View File

@@ -80,13 +80,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>

View File

@@ -113,92 +113,35 @@
<plugin>
<groupId>sonia.scm.maven</groupId>
<artifactId>smp-maven-plugin</artifactId>
<version>1.0.0-alpha-2</version>
<version>1.0.0-alpha-3</version>
<extensions>true</extensions>
<configuration>
<disableCompression>true</disableCompression>
<links>
<link>@scm-manager/ui-types</link>
<link>@scm-manager/ui-components</link>
</links>
</configuration>
<executions>
<execution>
<id>fix-descriptor</id>
<phase>process-resources</phase>
<goals>
<goal>fix-descriptor</goal>
</goals>
</execution>
<execution>
<id>append-dependencies</id>
<phase>process-classes</phase>
<goals>
<goal>append-dependencies</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.sdorra</groupId>
<artifactId>buildfrontend-maven-plugin</artifactId>
<configuration>
<node>
<version>${nodejs.version}</version>
</node>
<pkgManager>
<type>YARN</type>
<version>${yarn.version}</version>
</pkgManager>
<failOnMissingPackageJson>false</failOnMissingPackageJson>
<script>build</script>
</configuration>
<executions>
<execution>
<id>install</id>
<phase>process-resources</phase>
<goals>
<goal>install</goal>
</goals>
</execution>
<execution>
<id>build</id>
<phase>compile</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.sdorra</groupId>
<artifactId>buildfrontend-maven-plugin</artifactId>
<configuration>
<node>
<version>${nodejs.version}</version>
</node>
<pkgManager>
<type>YARN</type>
<version>${yarn.version}</version>
</pkgManager>
<failOnMissingPackageJson>false</failOnMissingPackageJson>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>sonia.maven</groupId>
<artifactId>web-compressor</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>compress-directory</goal>
</goals>
</execution>
</executions>
<configuration>
<replace>true</replace>
<baseDirectory>${project.build.directory}/classes</baseDirectory>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>doc</id>

View File

@@ -4,5 +4,6 @@
[include]
[libs]
./node_modules/@scm-manager/ui-components/flow-typed
[options]

View File

@@ -1,23 +0,0 @@
// flow-typed signature: cf86673cc32d185bdab1d2ea90578d37
// flow-typed version: 614bf49aa8/classnames_v2.x.x/flow_>=v0.25.x
type $npm$classnames$Classes =
| string
| { [className: string]: * }
| false
| void
| null;
declare module "classnames" {
declare module.exports: (
...classes: Array<$npm$classnames$Classes | $npm$classnames$Classes[]>
) => string;
}
declare module "classnames/bind" {
declare module.exports: $Exports<"classnames">;
}
declare module "classnames/dedupe" {
declare module.exports: $Exports<"classnames">;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,331 +0,0 @@
// flow-typed signature: 23b805356f90ad9384dd88489654e380
// flow-typed version: e9374c5fe9/moment_v2.3.x/flow_>=v0.25.x
type moment$MomentOptions = {
y?: number | string,
year?: number | string,
years?: number | string,
M?: number | string,
month?: number | string,
months?: number | string,
d?: number | string,
day?: number | string,
days?: number | string,
date?: number | string,
h?: number | string,
hour?: number | string,
hours?: number | string,
m?: number | string,
minute?: number | string,
minutes?: number | string,
s?: number | string,
second?: number | string,
seconds?: number | string,
ms?: number | string,
millisecond?: number | string,
milliseconds?: number | string
};
type moment$MomentObject = {
years: number,
months: number,
date: number,
hours: number,
minutes: number,
seconds: number,
milliseconds: number
};
type moment$MomentCreationData = {
input: string,
format: string,
locale: Object,
isUTC: boolean,
strict: boolean
};
type moment$CalendarFormat = string | ((moment: moment$Moment) => string);
type moment$CalendarFormats = {
sameDay?: moment$CalendarFormat,
nextDay?: moment$CalendarFormat,
nextWeek?: moment$CalendarFormat,
lastDay?: moment$CalendarFormat,
lastWeek?: moment$CalendarFormat,
sameElse?: moment$CalendarFormat
};
declare class moment$LocaleData {
months(moment: moment$Moment): string,
monthsShort(moment: moment$Moment): string,
monthsParse(month: string): number,
weekdays(moment: moment$Moment): string,
weekdaysShort(moment: moment$Moment): string,
weekdaysMin(moment: moment$Moment): string,
weekdaysParse(weekDay: string): number,
longDateFormat(dateFormat: string): string,
isPM(date: string): boolean,
meridiem(hours: number, minutes: number, isLower: boolean): string,
calendar(
key:
| "sameDay"
| "nextDay"
| "lastDay"
| "nextWeek"
| "prevWeek"
| "sameElse",
moment: moment$Moment
): string,
relativeTime(
number: number,
withoutSuffix: boolean,
key: "s" | "m" | "mm" | "h" | "hh" | "d" | "dd" | "M" | "MM" | "y" | "yy",
isFuture: boolean
): string,
pastFuture(diff: any, relTime: string): string,
ordinal(number: number): string,
preparse(str: string): any,
postformat(str: string): any,
week(moment: moment$Moment): string,
invalidDate(): string,
firstDayOfWeek(): number,
firstDayOfYear(): number
}
declare class moment$MomentDuration {
humanize(suffix?: boolean): string,
milliseconds(): number,
asMilliseconds(): number,
seconds(): number,
asSeconds(): number,
minutes(): number,
asMinutes(): number,
hours(): number,
asHours(): number,
days(): number,
asDays(): number,
months(): number,
asWeeks(): number,
weeks(): number,
asMonths(): number,
years(): number,
asYears(): number,
add(value: number | moment$MomentDuration | Object, unit?: string): this,
subtract(value: number | moment$MomentDuration | Object, unit?: string): this,
as(unit: string): number,
get(unit: string): number,
toJSON(): string,
toISOString(): string,
isValid(): boolean
}
declare class moment$Moment {
static ISO_8601: string,
static (
string?: string,
format?: string | Array<string>,
strict?: boolean
): moment$Moment,
static (
string?: string,
format?: string | Array<string>,
locale?: string,
strict?: boolean
): moment$Moment,
static (
initDate: ?Object | number | Date | Array<number> | moment$Moment | string
): moment$Moment,
static unix(seconds: number): moment$Moment,
static utc(): moment$Moment,
static utc(number: number | Array<number>): moment$Moment,
static utc(
str: string,
str2?: string | Array<string>,
str3?: string
): moment$Moment,
static utc(moment: moment$Moment): moment$Moment,
static utc(date: Date): moment$Moment,
static parseZone(): moment$Moment,
static parseZone(rawDate: string): moment$Moment,
static parseZone(
rawDate: string,
format: string | Array<string>
): moment$Moment,
static parseZone(
rawDate: string,
format: string,
strict: boolean
): moment$Moment,
static parseZone(
rawDate: string,
format: string,
locale: string,
strict: boolean
): moment$Moment,
isValid(): boolean,
invalidAt(): 0 | 1 | 2 | 3 | 4 | 5 | 6,
creationData(): moment$MomentCreationData,
millisecond(number: number): this,
milliseconds(number: number): this,
millisecond(): number,
milliseconds(): number,
second(number: number): this,
seconds(number: number): this,
second(): number,
seconds(): number,
minute(number: number): this,
minutes(number: number): this,
minute(): number,
minutes(): number,
hour(number: number): this,
hours(number: number): this,
hour(): number,
hours(): number,
date(number: number): this,
dates(number: number): this,
date(): number,
dates(): number,
day(day: number | string): this,
days(day: number | string): this,
day(): number,
days(): number,
weekday(number: number): this,
weekday(): number,
isoWeekday(number: number): this,
isoWeekday(): number,
dayOfYear(number: number): this,
dayOfYear(): number,
week(number: number): this,
weeks(number: number): this,
week(): number,
weeks(): number,
isoWeek(number: number): this,
isoWeeks(number: number): this,
isoWeek(): number,
isoWeeks(): number,
month(number: number): this,
months(number: number): this,
month(): number,
months(): number,
quarter(number: number): this,
quarter(): number,
year(number: number): this,
years(number: number): this,
year(): number,
years(): number,
weekYear(number: number): this,
weekYear(): number,
isoWeekYear(number: number): this,
isoWeekYear(): number,
weeksInYear(): number,
isoWeeksInYear(): number,
get(string: string): number,
set(unit: string, value: number): this,
set(options: { [unit: string]: number }): this,
static max(...dates: Array<moment$Moment>): moment$Moment,
static max(dates: Array<moment$Moment>): moment$Moment,
static min(...dates: Array<moment$Moment>): moment$Moment,
static min(dates: Array<moment$Moment>): moment$Moment,
add(
value: number | moment$MomentDuration | moment$Moment | Object,
unit?: string
): this,
subtract(
value: number | moment$MomentDuration | moment$Moment | string | Object,
unit?: string
): this,
startOf(unit: string): this,
endOf(unit: string): this,
local(): this,
utc(): this,
utcOffset(
offset: number | string,
keepLocalTime?: boolean,
keepMinutes?: boolean
): this,
utcOffset(): number,
format(format?: string): string,
fromNow(removeSuffix?: boolean): string,
from(
value: moment$Moment | string | number | Date | Array<number>,
removePrefix?: boolean
): string,
toNow(removePrefix?: boolean): string,
to(
value: moment$Moment | string | number | Date | Array<number>,
removePrefix?: boolean
): string,
calendar(refTime?: any, formats?: moment$CalendarFormats): string,
diff(
date: moment$Moment | string | number | Date | Array<number>,
format?: string,
floating?: boolean
): number,
valueOf(): number,
unix(): number,
daysInMonth(): number,
toDate(): Date,
toArray(): Array<number>,
toJSON(): string,
toISOString(
keepOffset?: boolean
): string,
toObject(): moment$MomentObject,
isBefore(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSame(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isAfter(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSameOrBefore(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSameOrAfter(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isBetween(
fromDate: moment$Moment | string | number | Date | Array<number>,
toDate?: ?moment$Moment | string | number | Date | Array<number>,
granularity?: ?string,
inclusion?: ?string
): boolean,
isDST(): boolean,
isDSTShifted(): boolean,
isLeapYear(): boolean,
clone(): moment$Moment,
static isMoment(obj: any): boolean,
static isDate(obj: any): boolean,
static locale(locale: string, localeData?: Object): string,
static updateLocale(locale: string, localeData?: ?Object): void,
static locale(locales: Array<string>): string,
locale(locale: string, customization?: Object | null): moment$Moment,
locale(): string,
static months(): Array<string>,
static monthsShort(): Array<string>,
static weekdays(): Array<string>,
static weekdaysShort(): Array<string>,
static weekdaysMin(): Array<string>,
static months(): string,
static monthsShort(): string,
static weekdays(): string,
static weekdaysShort(): string,
static weekdaysMin(): string,
static localeData(key?: string): moment$LocaleData,
static duration(
value: number | Object | string,
unit?: string
): moment$MomentDuration,
static isDuration(obj: any): boolean,
static normalizeUnits(unit: string): string,
static invalid(object: any): moment$Moment
}
declare module "moment" {
declare module.exports: Class<moment$Moment>;
}

View File

@@ -1,137 +0,0 @@
// flow-typed signature: ba35d02d668b0d0a3e04a63a6847974e
// flow-typed version: <<STUB>>/react-jss_v8.6.1/flow_v0.79.1
/**
* This is an autogenerated libdef stub for:
*
* 'react-jss'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module 'react-jss' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module 'react-jss/dist/react-jss' {
declare module.exports: any;
}
declare module 'react-jss/dist/react-jss.min' {
declare module.exports: any;
}
declare module 'react-jss/lib/compose' {
declare module.exports: any;
}
declare module 'react-jss/lib/compose.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/contextTypes' {
declare module.exports: any;
}
declare module 'react-jss/lib/createHoc' {
declare module.exports: any;
}
declare module 'react-jss/lib/getDisplayName' {
declare module.exports: any;
}
declare module 'react-jss/lib/index' {
declare module.exports: any;
}
declare module 'react-jss/lib/index.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/injectSheet' {
declare module.exports: any;
}
declare module 'react-jss/lib/injectSheet.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/jss' {
declare module.exports: any;
}
declare module 'react-jss/lib/JssProvider' {
declare module.exports: any;
}
declare module 'react-jss/lib/JssProvider.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/ns' {
declare module.exports: any;
}
declare module 'react-jss/lib/propTypes' {
declare module.exports: any;
}
// Filename aliases
declare module 'react-jss/dist/react-jss.js' {
declare module.exports: $Exports<'react-jss/dist/react-jss'>;
}
declare module 'react-jss/dist/react-jss.min.js' {
declare module.exports: $Exports<'react-jss/dist/react-jss.min'>;
}
declare module 'react-jss/lib/compose.js' {
declare module.exports: $Exports<'react-jss/lib/compose'>;
}
declare module 'react-jss/lib/compose.test.js' {
declare module.exports: $Exports<'react-jss/lib/compose.test'>;
}
declare module 'react-jss/lib/contextTypes.js' {
declare module.exports: $Exports<'react-jss/lib/contextTypes'>;
}
declare module 'react-jss/lib/createHoc.js' {
declare module.exports: $Exports<'react-jss/lib/createHoc'>;
}
declare module 'react-jss/lib/getDisplayName.js' {
declare module.exports: $Exports<'react-jss/lib/getDisplayName'>;
}
declare module 'react-jss/lib/index.js' {
declare module.exports: $Exports<'react-jss/lib/index'>;
}
declare module 'react-jss/lib/index.test.js' {
declare module.exports: $Exports<'react-jss/lib/index.test'>;
}
declare module 'react-jss/lib/injectSheet.js' {
declare module.exports: $Exports<'react-jss/lib/injectSheet'>;
}
declare module 'react-jss/lib/injectSheet.test.js' {
declare module.exports: $Exports<'react-jss/lib/injectSheet.test'>;
}
declare module 'react-jss/lib/jss.js' {
declare module.exports: $Exports<'react-jss/lib/jss'>;
}
declare module 'react-jss/lib/JssProvider.js' {
declare module.exports: $Exports<'react-jss/lib/JssProvider'>;
}
declare module 'react-jss/lib/JssProvider.test.js' {
declare module.exports: $Exports<'react-jss/lib/JssProvider.test'>;
}
declare module 'react-jss/lib/ns.js' {
declare module.exports: $Exports<'react-jss/lib/ns'>;
}
declare module 'react-jss/lib/propTypes.js' {
declare module.exports: $Exports<'react-jss/lib/propTypes'>;
}

View File

@@ -3,12 +3,15 @@
"license" : "BSD-3-Clause",
"main": "src/main/js/index.js",
"scripts": {
"build": "ui-bundler plugin"
"build": "ui-bundler plugin",
"watch": "ui-bundler plugin -w",
"lint": "ui-bundler lint",
"flow": "flow check"
},
"dependencies": {
"@scm-manager/ui-extensions": "^0.0.7"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.17"
"@scm-manager/ui-bundler": "^0.0.19"
}
}

View File

@@ -43,11 +43,20 @@
</dependencies>
<!-- create test jar -->
<build>
<plugins>
<plugin>
<groupId>sonia.scm.maven</groupId>
<artifactId>smp-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<corePlugin>true</corePlugin>
</configuration>
</plugin>
<!-- create test jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
@@ -61,33 +70,6 @@
</executions>
</plugin>
<plugin>
<groupId>com.github.sdorra</groupId>
<artifactId>buildfrontend-maven-plugin</artifactId>
<executions>
<execution>
<id>link-ui-types</id>
<phase>process-sources</phase>
<goals>
<goal>install-link</goal>
</goals>
<configuration>
<pkg>@scm-manager/ui-types</pkg>
</configuration>
</execution>
<execution>
<id>link-ui-components</id>
<phase>process-sources</phase>
<goals>
<goal>install-link</goal>
</goals>
<configuration>
<pkg>@scm-manager/ui-components</pkg>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -707,9 +707,9 @@
version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.17":
version "0.0.17"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb"
"@scm-manager/ui-bundler@^0.0.19":
version "0.0.19"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae"
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -4,5 +4,6 @@
[include]
[libs]
./node_modules/@scm-manager/ui-components/flow-typed
[options]

View File

@@ -1,23 +0,0 @@
// flow-typed signature: cf86673cc32d185bdab1d2ea90578d37
// flow-typed version: 614bf49aa8/classnames_v2.x.x/flow_>=v0.25.x
type $npm$classnames$Classes =
| string
| { [className: string]: * }
| false
| void
| null;
declare module "classnames" {
declare module.exports: (
...classes: Array<$npm$classnames$Classes | $npm$classnames$Classes[]>
) => string;
}
declare module "classnames/bind" {
declare module.exports: $Exports<"classnames">;
}
declare module "classnames/dedupe" {
declare module.exports: $Exports<"classnames">;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,331 +0,0 @@
// flow-typed signature: 23b805356f90ad9384dd88489654e380
// flow-typed version: e9374c5fe9/moment_v2.3.x/flow_>=v0.25.x
type moment$MomentOptions = {
y?: number | string,
year?: number | string,
years?: number | string,
M?: number | string,
month?: number | string,
months?: number | string,
d?: number | string,
day?: number | string,
days?: number | string,
date?: number | string,
h?: number | string,
hour?: number | string,
hours?: number | string,
m?: number | string,
minute?: number | string,
minutes?: number | string,
s?: number | string,
second?: number | string,
seconds?: number | string,
ms?: number | string,
millisecond?: number | string,
milliseconds?: number | string
};
type moment$MomentObject = {
years: number,
months: number,
date: number,
hours: number,
minutes: number,
seconds: number,
milliseconds: number
};
type moment$MomentCreationData = {
input: string,
format: string,
locale: Object,
isUTC: boolean,
strict: boolean
};
type moment$CalendarFormat = string | ((moment: moment$Moment) => string);
type moment$CalendarFormats = {
sameDay?: moment$CalendarFormat,
nextDay?: moment$CalendarFormat,
nextWeek?: moment$CalendarFormat,
lastDay?: moment$CalendarFormat,
lastWeek?: moment$CalendarFormat,
sameElse?: moment$CalendarFormat
};
declare class moment$LocaleData {
months(moment: moment$Moment): string,
monthsShort(moment: moment$Moment): string,
monthsParse(month: string): number,
weekdays(moment: moment$Moment): string,
weekdaysShort(moment: moment$Moment): string,
weekdaysMin(moment: moment$Moment): string,
weekdaysParse(weekDay: string): number,
longDateFormat(dateFormat: string): string,
isPM(date: string): boolean,
meridiem(hours: number, minutes: number, isLower: boolean): string,
calendar(
key:
| "sameDay"
| "nextDay"
| "lastDay"
| "nextWeek"
| "prevWeek"
| "sameElse",
moment: moment$Moment
): string,
relativeTime(
number: number,
withoutSuffix: boolean,
key: "s" | "m" | "mm" | "h" | "hh" | "d" | "dd" | "M" | "MM" | "y" | "yy",
isFuture: boolean
): string,
pastFuture(diff: any, relTime: string): string,
ordinal(number: number): string,
preparse(str: string): any,
postformat(str: string): any,
week(moment: moment$Moment): string,
invalidDate(): string,
firstDayOfWeek(): number,
firstDayOfYear(): number
}
declare class moment$MomentDuration {
humanize(suffix?: boolean): string,
milliseconds(): number,
asMilliseconds(): number,
seconds(): number,
asSeconds(): number,
minutes(): number,
asMinutes(): number,
hours(): number,
asHours(): number,
days(): number,
asDays(): number,
months(): number,
asWeeks(): number,
weeks(): number,
asMonths(): number,
years(): number,
asYears(): number,
add(value: number | moment$MomentDuration | Object, unit?: string): this,
subtract(value: number | moment$MomentDuration | Object, unit?: string): this,
as(unit: string): number,
get(unit: string): number,
toJSON(): string,
toISOString(): string,
isValid(): boolean
}
declare class moment$Moment {
static ISO_8601: string,
static (
string?: string,
format?: string | Array<string>,
strict?: boolean
): moment$Moment,
static (
string?: string,
format?: string | Array<string>,
locale?: string,
strict?: boolean
): moment$Moment,
static (
initDate: ?Object | number | Date | Array<number> | moment$Moment | string
): moment$Moment,
static unix(seconds: number): moment$Moment,
static utc(): moment$Moment,
static utc(number: number | Array<number>): moment$Moment,
static utc(
str: string,
str2?: string | Array<string>,
str3?: string
): moment$Moment,
static utc(moment: moment$Moment): moment$Moment,
static utc(date: Date): moment$Moment,
static parseZone(): moment$Moment,
static parseZone(rawDate: string): moment$Moment,
static parseZone(
rawDate: string,
format: string | Array<string>
): moment$Moment,
static parseZone(
rawDate: string,
format: string,
strict: boolean
): moment$Moment,
static parseZone(
rawDate: string,
format: string,
locale: string,
strict: boolean
): moment$Moment,
isValid(): boolean,
invalidAt(): 0 | 1 | 2 | 3 | 4 | 5 | 6,
creationData(): moment$MomentCreationData,
millisecond(number: number): this,
milliseconds(number: number): this,
millisecond(): number,
milliseconds(): number,
second(number: number): this,
seconds(number: number): this,
second(): number,
seconds(): number,
minute(number: number): this,
minutes(number: number): this,
minute(): number,
minutes(): number,
hour(number: number): this,
hours(number: number): this,
hour(): number,
hours(): number,
date(number: number): this,
dates(number: number): this,
date(): number,
dates(): number,
day(day: number | string): this,
days(day: number | string): this,
day(): number,
days(): number,
weekday(number: number): this,
weekday(): number,
isoWeekday(number: number): this,
isoWeekday(): number,
dayOfYear(number: number): this,
dayOfYear(): number,
week(number: number): this,
weeks(number: number): this,
week(): number,
weeks(): number,
isoWeek(number: number): this,
isoWeeks(number: number): this,
isoWeek(): number,
isoWeeks(): number,
month(number: number): this,
months(number: number): this,
month(): number,
months(): number,
quarter(number: number): this,
quarter(): number,
year(number: number): this,
years(number: number): this,
year(): number,
years(): number,
weekYear(number: number): this,
weekYear(): number,
isoWeekYear(number: number): this,
isoWeekYear(): number,
weeksInYear(): number,
isoWeeksInYear(): number,
get(string: string): number,
set(unit: string, value: number): this,
set(options: { [unit: string]: number }): this,
static max(...dates: Array<moment$Moment>): moment$Moment,
static max(dates: Array<moment$Moment>): moment$Moment,
static min(...dates: Array<moment$Moment>): moment$Moment,
static min(dates: Array<moment$Moment>): moment$Moment,
add(
value: number | moment$MomentDuration | moment$Moment | Object,
unit?: string
): this,
subtract(
value: number | moment$MomentDuration | moment$Moment | string | Object,
unit?: string
): this,
startOf(unit: string): this,
endOf(unit: string): this,
local(): this,
utc(): this,
utcOffset(
offset: number | string,
keepLocalTime?: boolean,
keepMinutes?: boolean
): this,
utcOffset(): number,
format(format?: string): string,
fromNow(removeSuffix?: boolean): string,
from(
value: moment$Moment | string | number | Date | Array<number>,
removePrefix?: boolean
): string,
toNow(removePrefix?: boolean): string,
to(
value: moment$Moment | string | number | Date | Array<number>,
removePrefix?: boolean
): string,
calendar(refTime?: any, formats?: moment$CalendarFormats): string,
diff(
date: moment$Moment | string | number | Date | Array<number>,
format?: string,
floating?: boolean
): number,
valueOf(): number,
unix(): number,
daysInMonth(): number,
toDate(): Date,
toArray(): Array<number>,
toJSON(): string,
toISOString(
keepOffset?: boolean
): string,
toObject(): moment$MomentObject,
isBefore(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSame(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isAfter(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSameOrBefore(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSameOrAfter(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isBetween(
fromDate: moment$Moment | string | number | Date | Array<number>,
toDate?: ?moment$Moment | string | number | Date | Array<number>,
granularity?: ?string,
inclusion?: ?string
): boolean,
isDST(): boolean,
isDSTShifted(): boolean,
isLeapYear(): boolean,
clone(): moment$Moment,
static isMoment(obj: any): boolean,
static isDate(obj: any): boolean,
static locale(locale: string, localeData?: Object): string,
static updateLocale(locale: string, localeData?: ?Object): void,
static locale(locales: Array<string>): string,
locale(locale: string, customization?: Object | null): moment$Moment,
locale(): string,
static months(): Array<string>,
static monthsShort(): Array<string>,
static weekdays(): Array<string>,
static weekdaysShort(): Array<string>,
static weekdaysMin(): Array<string>,
static months(): string,
static monthsShort(): string,
static weekdays(): string,
static weekdaysShort(): string,
static weekdaysMin(): string,
static localeData(key?: string): moment$LocaleData,
static duration(
value: number | Object | string,
unit?: string
): moment$MomentDuration,
static isDuration(obj: any): boolean,
static normalizeUnits(unit: string): string,
static invalid(object: any): moment$Moment
}
declare module "moment" {
declare module.exports: Class<moment$Moment>;
}

View File

@@ -1,137 +0,0 @@
// flow-typed signature: ba35d02d668b0d0a3e04a63a6847974e
// flow-typed version: <<STUB>>/react-jss_v8.6.1/flow_v0.79.1
/**
* This is an autogenerated libdef stub for:
*
* 'react-jss'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module 'react-jss' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module 'react-jss/dist/react-jss' {
declare module.exports: any;
}
declare module 'react-jss/dist/react-jss.min' {
declare module.exports: any;
}
declare module 'react-jss/lib/compose' {
declare module.exports: any;
}
declare module 'react-jss/lib/compose.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/contextTypes' {
declare module.exports: any;
}
declare module 'react-jss/lib/createHoc' {
declare module.exports: any;
}
declare module 'react-jss/lib/getDisplayName' {
declare module.exports: any;
}
declare module 'react-jss/lib/index' {
declare module.exports: any;
}
declare module 'react-jss/lib/index.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/injectSheet' {
declare module.exports: any;
}
declare module 'react-jss/lib/injectSheet.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/jss' {
declare module.exports: any;
}
declare module 'react-jss/lib/JssProvider' {
declare module.exports: any;
}
declare module 'react-jss/lib/JssProvider.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/ns' {
declare module.exports: any;
}
declare module 'react-jss/lib/propTypes' {
declare module.exports: any;
}
// Filename aliases
declare module 'react-jss/dist/react-jss.js' {
declare module.exports: $Exports<'react-jss/dist/react-jss'>;
}
declare module 'react-jss/dist/react-jss.min.js' {
declare module.exports: $Exports<'react-jss/dist/react-jss.min'>;
}
declare module 'react-jss/lib/compose.js' {
declare module.exports: $Exports<'react-jss/lib/compose'>;
}
declare module 'react-jss/lib/compose.test.js' {
declare module.exports: $Exports<'react-jss/lib/compose.test'>;
}
declare module 'react-jss/lib/contextTypes.js' {
declare module.exports: $Exports<'react-jss/lib/contextTypes'>;
}
declare module 'react-jss/lib/createHoc.js' {
declare module.exports: $Exports<'react-jss/lib/createHoc'>;
}
declare module 'react-jss/lib/getDisplayName.js' {
declare module.exports: $Exports<'react-jss/lib/getDisplayName'>;
}
declare module 'react-jss/lib/index.js' {
declare module.exports: $Exports<'react-jss/lib/index'>;
}
declare module 'react-jss/lib/index.test.js' {
declare module.exports: $Exports<'react-jss/lib/index.test'>;
}
declare module 'react-jss/lib/injectSheet.js' {
declare module.exports: $Exports<'react-jss/lib/injectSheet'>;
}
declare module 'react-jss/lib/injectSheet.test.js' {
declare module.exports: $Exports<'react-jss/lib/injectSheet.test'>;
}
declare module 'react-jss/lib/jss.js' {
declare module.exports: $Exports<'react-jss/lib/jss'>;
}
declare module 'react-jss/lib/JssProvider.js' {
declare module.exports: $Exports<'react-jss/lib/JssProvider'>;
}
declare module 'react-jss/lib/JssProvider.test.js' {
declare module.exports: $Exports<'react-jss/lib/JssProvider.test'>;
}
declare module 'react-jss/lib/ns.js' {
declare module.exports: $Exports<'react-jss/lib/ns'>;
}
declare module 'react-jss/lib/propTypes.js' {
declare module.exports: $Exports<'react-jss/lib/propTypes'>;
}

View File

@@ -9,6 +9,6 @@
"@scm-manager/ui-extensions": "^0.0.7"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.17"
"@scm-manager/ui-bundler": "^0.0.19"
}
}

View File

@@ -31,8 +31,6 @@
</dependencies>
<!-- create test jar -->
<build>
<plugins>
@@ -54,7 +52,18 @@
<strictCheck>true</strictCheck>
</configuration>
</plugin>
<plugin>
<groupId>sonia.scm.maven</groupId>
<artifactId>smp-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<corePlugin>true</corePlugin>
</configuration>
</plugin>
<!-- create test jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
@@ -68,33 +77,6 @@
</executions>
</plugin>
<plugin>
<groupId>com.github.sdorra</groupId>
<artifactId>buildfrontend-maven-plugin</artifactId>
<executions>
<execution>
<id>link-ui-types</id>
<phase>process-sources</phase>
<goals>
<goal>install-link</goal>
</goals>
<configuration>
<pkg>@scm-manager/ui-types</pkg>
</configuration>
</execution>
<execution>
<id>link-ui-components</id>
<phase>process-sources</phase>
<goals>
<goal>install-link</goal>
</goals>
<configuration>
<pkg>@scm-manager/ui-components</pkg>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -641,9 +641,9 @@
version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.17":
version "0.0.17"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb"
"@scm-manager/ui-bundler@^0.0.19":
version "0.0.19"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae"
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"
@@ -660,6 +660,7 @@
browserify-css "^0.14.0"
colors "^1.3.1"
commander "^2.17.1"
connect-history-api-fallback "^1.5.0"
eslint "^5.4.0"
eslint-config-react-app "^2.1.0"
eslint-plugin-flowtype "^2.50.0"

View File

@@ -23,4 +23,19 @@
</dependency>
</dependencies>
</project>
<build>
<plugins>
<plugin>
<groupId>sonia.scm.maven</groupId>
<artifactId>smp-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<corePlugin>true</corePlugin>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -4,5 +4,6 @@
[include]
[libs]
./node_modules/@scm-manager/ui-components/flow-typed
[options]

View File

@@ -1,23 +0,0 @@
// flow-typed signature: cf86673cc32d185bdab1d2ea90578d37
// flow-typed version: 614bf49aa8/classnames_v2.x.x/flow_>=v0.25.x
type $npm$classnames$Classes =
| string
| { [className: string]: * }
| false
| void
| null;
declare module "classnames" {
declare module.exports: (
...classes: Array<$npm$classnames$Classes | $npm$classnames$Classes[]>
) => string;
}
declare module "classnames/bind" {
declare module.exports: $Exports<"classnames">;
}
declare module "classnames/dedupe" {
declare module.exports: $Exports<"classnames">;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,331 +0,0 @@
// flow-typed signature: 23b805356f90ad9384dd88489654e380
// flow-typed version: e9374c5fe9/moment_v2.3.x/flow_>=v0.25.x
type moment$MomentOptions = {
y?: number | string,
year?: number | string,
years?: number | string,
M?: number | string,
month?: number | string,
months?: number | string,
d?: number | string,
day?: number | string,
days?: number | string,
date?: number | string,
h?: number | string,
hour?: number | string,
hours?: number | string,
m?: number | string,
minute?: number | string,
minutes?: number | string,
s?: number | string,
second?: number | string,
seconds?: number | string,
ms?: number | string,
millisecond?: number | string,
milliseconds?: number | string
};
type moment$MomentObject = {
years: number,
months: number,
date: number,
hours: number,
minutes: number,
seconds: number,
milliseconds: number
};
type moment$MomentCreationData = {
input: string,
format: string,
locale: Object,
isUTC: boolean,
strict: boolean
};
type moment$CalendarFormat = string | ((moment: moment$Moment) => string);
type moment$CalendarFormats = {
sameDay?: moment$CalendarFormat,
nextDay?: moment$CalendarFormat,
nextWeek?: moment$CalendarFormat,
lastDay?: moment$CalendarFormat,
lastWeek?: moment$CalendarFormat,
sameElse?: moment$CalendarFormat
};
declare class moment$LocaleData {
months(moment: moment$Moment): string,
monthsShort(moment: moment$Moment): string,
monthsParse(month: string): number,
weekdays(moment: moment$Moment): string,
weekdaysShort(moment: moment$Moment): string,
weekdaysMin(moment: moment$Moment): string,
weekdaysParse(weekDay: string): number,
longDateFormat(dateFormat: string): string,
isPM(date: string): boolean,
meridiem(hours: number, minutes: number, isLower: boolean): string,
calendar(
key:
| "sameDay"
| "nextDay"
| "lastDay"
| "nextWeek"
| "prevWeek"
| "sameElse",
moment: moment$Moment
): string,
relativeTime(
number: number,
withoutSuffix: boolean,
key: "s" | "m" | "mm" | "h" | "hh" | "d" | "dd" | "M" | "MM" | "y" | "yy",
isFuture: boolean
): string,
pastFuture(diff: any, relTime: string): string,
ordinal(number: number): string,
preparse(str: string): any,
postformat(str: string): any,
week(moment: moment$Moment): string,
invalidDate(): string,
firstDayOfWeek(): number,
firstDayOfYear(): number
}
declare class moment$MomentDuration {
humanize(suffix?: boolean): string,
milliseconds(): number,
asMilliseconds(): number,
seconds(): number,
asSeconds(): number,
minutes(): number,
asMinutes(): number,
hours(): number,
asHours(): number,
days(): number,
asDays(): number,
months(): number,
asWeeks(): number,
weeks(): number,
asMonths(): number,
years(): number,
asYears(): number,
add(value: number | moment$MomentDuration | Object, unit?: string): this,
subtract(value: number | moment$MomentDuration | Object, unit?: string): this,
as(unit: string): number,
get(unit: string): number,
toJSON(): string,
toISOString(): string,
isValid(): boolean
}
declare class moment$Moment {
static ISO_8601: string,
static (
string?: string,
format?: string | Array<string>,
strict?: boolean
): moment$Moment,
static (
string?: string,
format?: string | Array<string>,
locale?: string,
strict?: boolean
): moment$Moment,
static (
initDate: ?Object | number | Date | Array<number> | moment$Moment | string
): moment$Moment,
static unix(seconds: number): moment$Moment,
static utc(): moment$Moment,
static utc(number: number | Array<number>): moment$Moment,
static utc(
str: string,
str2?: string | Array<string>,
str3?: string
): moment$Moment,
static utc(moment: moment$Moment): moment$Moment,
static utc(date: Date): moment$Moment,
static parseZone(): moment$Moment,
static parseZone(rawDate: string): moment$Moment,
static parseZone(
rawDate: string,
format: string | Array<string>
): moment$Moment,
static parseZone(
rawDate: string,
format: string,
strict: boolean
): moment$Moment,
static parseZone(
rawDate: string,
format: string,
locale: string,
strict: boolean
): moment$Moment,
isValid(): boolean,
invalidAt(): 0 | 1 | 2 | 3 | 4 | 5 | 6,
creationData(): moment$MomentCreationData,
millisecond(number: number): this,
milliseconds(number: number): this,
millisecond(): number,
milliseconds(): number,
second(number: number): this,
seconds(number: number): this,
second(): number,
seconds(): number,
minute(number: number): this,
minutes(number: number): this,
minute(): number,
minutes(): number,
hour(number: number): this,
hours(number: number): this,
hour(): number,
hours(): number,
date(number: number): this,
dates(number: number): this,
date(): number,
dates(): number,
day(day: number | string): this,
days(day: number | string): this,
day(): number,
days(): number,
weekday(number: number): this,
weekday(): number,
isoWeekday(number: number): this,
isoWeekday(): number,
dayOfYear(number: number): this,
dayOfYear(): number,
week(number: number): this,
weeks(number: number): this,
week(): number,
weeks(): number,
isoWeek(number: number): this,
isoWeeks(number: number): this,
isoWeek(): number,
isoWeeks(): number,
month(number: number): this,
months(number: number): this,
month(): number,
months(): number,
quarter(number: number): this,
quarter(): number,
year(number: number): this,
years(number: number): this,
year(): number,
years(): number,
weekYear(number: number): this,
weekYear(): number,
isoWeekYear(number: number): this,
isoWeekYear(): number,
weeksInYear(): number,
isoWeeksInYear(): number,
get(string: string): number,
set(unit: string, value: number): this,
set(options: { [unit: string]: number }): this,
static max(...dates: Array<moment$Moment>): moment$Moment,
static max(dates: Array<moment$Moment>): moment$Moment,
static min(...dates: Array<moment$Moment>): moment$Moment,
static min(dates: Array<moment$Moment>): moment$Moment,
add(
value: number | moment$MomentDuration | moment$Moment | Object,
unit?: string
): this,
subtract(
value: number | moment$MomentDuration | moment$Moment | string | Object,
unit?: string
): this,
startOf(unit: string): this,
endOf(unit: string): this,
local(): this,
utc(): this,
utcOffset(
offset: number | string,
keepLocalTime?: boolean,
keepMinutes?: boolean
): this,
utcOffset(): number,
format(format?: string): string,
fromNow(removeSuffix?: boolean): string,
from(
value: moment$Moment | string | number | Date | Array<number>,
removePrefix?: boolean
): string,
toNow(removePrefix?: boolean): string,
to(
value: moment$Moment | string | number | Date | Array<number>,
removePrefix?: boolean
): string,
calendar(refTime?: any, formats?: moment$CalendarFormats): string,
diff(
date: moment$Moment | string | number | Date | Array<number>,
format?: string,
floating?: boolean
): number,
valueOf(): number,
unix(): number,
daysInMonth(): number,
toDate(): Date,
toArray(): Array<number>,
toJSON(): string,
toISOString(
keepOffset?: boolean
): string,
toObject(): moment$MomentObject,
isBefore(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSame(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isAfter(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSameOrBefore(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSameOrAfter(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isBetween(
fromDate: moment$Moment | string | number | Date | Array<number>,
toDate?: ?moment$Moment | string | number | Date | Array<number>,
granularity?: ?string,
inclusion?: ?string
): boolean,
isDST(): boolean,
isDSTShifted(): boolean,
isLeapYear(): boolean,
clone(): moment$Moment,
static isMoment(obj: any): boolean,
static isDate(obj: any): boolean,
static locale(locale: string, localeData?: Object): string,
static updateLocale(locale: string, localeData?: ?Object): void,
static locale(locales: Array<string>): string,
locale(locale: string, customization?: Object | null): moment$Moment,
locale(): string,
static months(): Array<string>,
static monthsShort(): Array<string>,
static weekdays(): Array<string>,
static weekdaysShort(): Array<string>,
static weekdaysMin(): Array<string>,
static months(): string,
static monthsShort(): string,
static weekdays(): string,
static weekdaysShort(): string,
static weekdaysMin(): string,
static localeData(key?: string): moment$LocaleData,
static duration(
value: number | Object | string,
unit?: string
): moment$MomentDuration,
static isDuration(obj: any): boolean,
static normalizeUnits(unit: string): string,
static invalid(object: any): moment$Moment
}
declare module "moment" {
declare module.exports: Class<moment$Moment>;
}

View File

@@ -1,137 +0,0 @@
// flow-typed signature: ba35d02d668b0d0a3e04a63a6847974e
// flow-typed version: <<STUB>>/react-jss_v8.6.1/flow_v0.79.1
/**
* This is an autogenerated libdef stub for:
*
* 'react-jss'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module 'react-jss' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module 'react-jss/dist/react-jss' {
declare module.exports: any;
}
declare module 'react-jss/dist/react-jss.min' {
declare module.exports: any;
}
declare module 'react-jss/lib/compose' {
declare module.exports: any;
}
declare module 'react-jss/lib/compose.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/contextTypes' {
declare module.exports: any;
}
declare module 'react-jss/lib/createHoc' {
declare module.exports: any;
}
declare module 'react-jss/lib/getDisplayName' {
declare module.exports: any;
}
declare module 'react-jss/lib/index' {
declare module.exports: any;
}
declare module 'react-jss/lib/index.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/injectSheet' {
declare module.exports: any;
}
declare module 'react-jss/lib/injectSheet.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/jss' {
declare module.exports: any;
}
declare module 'react-jss/lib/JssProvider' {
declare module.exports: any;
}
declare module 'react-jss/lib/JssProvider.test' {
declare module.exports: any;
}
declare module 'react-jss/lib/ns' {
declare module.exports: any;
}
declare module 'react-jss/lib/propTypes' {
declare module.exports: any;
}
// Filename aliases
declare module 'react-jss/dist/react-jss.js' {
declare module.exports: $Exports<'react-jss/dist/react-jss'>;
}
declare module 'react-jss/dist/react-jss.min.js' {
declare module.exports: $Exports<'react-jss/dist/react-jss.min'>;
}
declare module 'react-jss/lib/compose.js' {
declare module.exports: $Exports<'react-jss/lib/compose'>;
}
declare module 'react-jss/lib/compose.test.js' {
declare module.exports: $Exports<'react-jss/lib/compose.test'>;
}
declare module 'react-jss/lib/contextTypes.js' {
declare module.exports: $Exports<'react-jss/lib/contextTypes'>;
}
declare module 'react-jss/lib/createHoc.js' {
declare module.exports: $Exports<'react-jss/lib/createHoc'>;
}
declare module 'react-jss/lib/getDisplayName.js' {
declare module.exports: $Exports<'react-jss/lib/getDisplayName'>;
}
declare module 'react-jss/lib/index.js' {
declare module.exports: $Exports<'react-jss/lib/index'>;
}
declare module 'react-jss/lib/index.test.js' {
declare module.exports: $Exports<'react-jss/lib/index.test'>;
}
declare module 'react-jss/lib/injectSheet.js' {
declare module.exports: $Exports<'react-jss/lib/injectSheet'>;
}
declare module 'react-jss/lib/injectSheet.test.js' {
declare module.exports: $Exports<'react-jss/lib/injectSheet.test'>;
}
declare module 'react-jss/lib/jss.js' {
declare module.exports: $Exports<'react-jss/lib/jss'>;
}
declare module 'react-jss/lib/JssProvider.js' {
declare module.exports: $Exports<'react-jss/lib/JssProvider'>;
}
declare module 'react-jss/lib/JssProvider.test.js' {
declare module.exports: $Exports<'react-jss/lib/JssProvider.test'>;
}
declare module 'react-jss/lib/ns.js' {
declare module.exports: $Exports<'react-jss/lib/ns'>;
}
declare module 'react-jss/lib/propTypes.js' {
declare module.exports: $Exports<'react-jss/lib/propTypes'>;
}

View File

@@ -9,6 +9,6 @@
"@scm-manager/ui-extensions": "^0.0.7"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.17"
"@scm-manager/ui-bundler": "^0.0.19"
}
}

View File

@@ -37,11 +37,19 @@
</dependencies>
<!-- create test jar -->
<build>
<plugins>
<plugin>
<groupId>sonia.scm.maven</groupId>
<artifactId>smp-maven-plugin</artifactId>
<configuration>
<corePlugin>true</corePlugin>
</configuration>
</plugin>
<!-- create test jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
@@ -55,34 +63,6 @@
</executions>
</plugin>
<plugin>
<groupId>com.github.sdorra</groupId>
<artifactId>buildfrontend-maven-plugin</artifactId>
<executions>
<execution>
<id>link-ui-types</id>
<phase>process-sources</phase>
<goals>
<goal>install-link</goal>
</goals>
<configuration>
<pkg>@scm-manager/ui-types</pkg>
</configuration>
</execution>
<execution>
<id>link-ui-components</id>
<phase>process-sources</phase>
<goals>
<goal>install-link</goal>
</goals>
<configuration>
<pkg>@scm-manager/ui-components</pkg>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -641,9 +641,9 @@
version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.17":
version "0.0.17"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb"
"@scm-manager/ui-bundler@^0.0.19":
version "0.0.19"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae"
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -0,0 +1,95 @@
// flow-typed signature: 65d42f62f8de603dcc631ea5a6b00580
// flow-typed version: f3f13164e0/react-i18next_v7.x.x/flow_>=v0.64.x
declare module "react-i18next" {
declare type TFunction = (key?: ?string, data?: ?Object) => string;
declare type TranslatorProps = {|
t: TFunction,
i18nLoadedAt: Date,
i18n: Object
|};
declare type TranslatorPropsVoid = {
t: TFunction | void,
i18nLoadedAt: Date | void,
i18n: Object | void
};
declare type Translator<P: {}, Component: React$ComponentType<P>> = (
WrappedComponent: Component
) => React$ComponentType<
$Diff<React$ElementConfig<Component>, TranslatorPropsVoid>
>;
declare type TranslateOptions = $Shape<{
wait: boolean,
nsMode: "default" | "fallback",
bindi18n: false | string,
bindStore: false | string,
withRef: boolean,
translateFuncName: string,
i18n: Object,
usePureComponent: boolean
}>;
declare function translate<P: {}, Component: React$ComponentType<P>>(
namespaces?: | string
| Array<string>
| (($Diff<P, TranslatorPropsVoid>) => string | Array<string>),
options?: TranslateOptions
): Translator<P, Component>;
declare type I18nProps = {
i18n?: Object,
ns?: string | Array<string>,
children: (t: TFunction, { i18n: Object, t: TFunction }) => React$Node,
initialI18nStore?: Object,
initialLanguage?: string
};
declare var I18n: React$ComponentType<I18nProps>;
declare type InterpolateProps = {
className?: string,
dangerouslySetInnerHTMLPartElement?: string,
i18n?: Object,
i18nKey?: string,
options?: Object,
parent?: string,
style?: Object,
t?: TFunction,
useDangerouslySetInnerHTML?: boolean
};
declare var Interpolate: React$ComponentType<InterpolateProps>;
declare type TransProps = {
count?: number,
parent?: string,
i18n?: Object,
i18nKey?: string,
t?: TFunction
};
declare var Trans: React$ComponentType<TransProps>;
declare type ProviderProps = { i18n: Object, children: React$Element<*> };
declare var I18nextProvider: React$ComponentType<ProviderProps>;
declare type NamespacesProps = {
components: Array<React$ComponentType<*>>,
i18n: { loadNamespaces: Function }
};
declare function loadNamespaces(props: NamespacesProps): Promise<void>;
declare var reactI18nextModule: {
type: "3rdParty",
init: (instance: Object) => void
};
declare function setDefaults(options: TranslateOptions): void;
declare function getDefaults(): TranslateOptions;
declare function getI18n(): Object;
declare function setI18n(instance: Object): void;
}

View File

@@ -12,7 +12,7 @@
"eslint-fix": "eslint src --fix"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.17",
"@scm-manager/ui-bundler": "^0.0.19",
"create-index": "^2.3.0",
"enzyme": "^3.5.0",
"enzyme-adapter-react-16": "^1.3.1",

View File

@@ -48,6 +48,14 @@ class ApiClient {
return this.httpRequestWithJSONBody("PUT", url, contentType, payload);
}
head(url: string) {
let options: RequestOptions = {
method: "HEAD"
};
options = Object.assign(options, fetchOptions);
return fetch(createUrl(url), options).then(handleStatusCode);
}
delete(url: string): Promise<Response> {
let options: RequestOptions = {
method: "DELETE"

View File

@@ -0,0 +1,25 @@
//@flow
import React from "react";
import Button, { type ButtonProps } from "./Button";
import type {File} from "@scm-manager/ui-types";
type Props = {
displayName: string,
url: string
};
class DownloadButton extends React.Component<Props> {
render() {
const {displayName, url} = this.props;
return (
<a className="button is-large is-info" href={url}>
<span className="icon is-medium">
<i className="fas fa-arrow-circle-down" />
</span>
<span>{displayName}</span>
</a>
);
}
}
export default DownloadButton;

View File

@@ -7,4 +7,4 @@ export { default as DeleteButton } from "./DeleteButton.js";
export { default as EditButton } from "./EditButton.js";
export { default as RemoveEntryOfTableButton } from "./RemoveEntryOfTableButton.js";
export { default as SubmitButton } from "./SubmitButton.js";
export {default as DownloadButton} from "./DownloadButton.js";

View File

@@ -641,9 +641,9 @@
version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.17":
version "0.0.17"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb"
"@scm-manager/ui-bundler@^0.0.19":
version "0.0.19"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae"
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -14,7 +14,7 @@
"check": "flow check"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.17"
"@scm-manager/ui-bundler": "^0.0.19"
},
"browserify": {
"transform": [

View File

@@ -707,9 +707,9 @@
version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.17":
version "0.0.17"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb"
"@scm-manager/ui-bundler@^0.0.19":
version "0.0.19"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae"
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -27,6 +27,7 @@
"react-redux": "^5.0.7",
"react-router-dom": "^4.3.1",
"react-router-redux": "^5.0.0-alpha.9",
"react-syntax-highlighter": "^9.0.1",
"redux": "^4.0.0",
"redux-devtools-extension": "^2.13.5",
"redux-logger": "^3.0.6",
@@ -47,7 +48,7 @@
"pre-commit": "jest && flow && eslint src"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.17",
"@scm-manager/ui-bundler": "^0.0.19",
"copyfiles": "^2.0.0",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",

View File

@@ -36,5 +36,9 @@
</script>
<script src="{{ contextPath }}/vendor.bundle.js"></script>
<script src="{{ contextPath }}/scm-ui.bundle.js"></script>
{{#liveReloadURL}}
<script src="{{liveReloadURL}}"></script>
{{/liveReloadURL}}
</body>
</html>

View File

@@ -51,7 +51,11 @@
"name": "Name",
"length": "Length",
"lastModified": "Last modified",
"description": "Description"
"description": "Description",
"branch": "Branch"
},
"content": {
"downloadButton": "Download"
}
},
"changesets": {

View File

@@ -14,7 +14,7 @@ import {
modifyConfigReset
} from "../modules/config";
import { connect } from "react-redux";
import type { Config, Link } from "@scm-manager/ui-types";
import type { Config } from "@scm-manager/ui-types";
import ConfigForm from "../components/form/ConfigForm";
import { getConfigLink } from "../../modules/indexResource";

View File

@@ -22,7 +22,6 @@ import reducer, {
getConfig,
getConfigUpdatePermission
} from "./config";
import { getConfigLink } from "../../modules/indexResource";
const CONFIG_URL = "/config";
const URL = "/api/v2" + CONFIG_URL;

View File

@@ -19,9 +19,8 @@ import {
Footer,
Header
} from "@scm-manager/ui-components";
import type { Me, Link } from "@scm-manager/ui-types";
import type { Me } from "@scm-manager/ui-types";
import {
fetchIndexResources,
getConfigLink,
getFetchIndexResourcesFailure,
getGroupsLink,

View File

@@ -11,7 +11,7 @@ import {
getLogoutFailure
} from "../modules/auth";
import { Loading, ErrorPage } from "@scm-manager/ui-components";
import { fetchIndexResources, getLogoutLink } from "../modules/indexResource";
import { getLogoutLink } from "../modules/indexResource";
type Props = {
authenticated: boolean,

View File

@@ -9,8 +9,7 @@ import {
createGroup,
isCreateGroupPending,
getCreateGroupFailure,
createGroupReset,
getCreateGroupLink
createGroupReset
} from "../modules/groups";
import type { Group } from "@scm-manager/ui-types";
import type { History } from "history";

View File

@@ -14,7 +14,6 @@ import type { BrowserHistory } from "history/createBrowserHistory";
import createReduxStore from "./createReduxStore";
import { ConnectedRouter } from "react-router-redux";
import PluginLoader from "./containers/PluginLoader";
import { urls } from "@scm-manager/ui-components";
@@ -37,7 +36,7 @@ ReactDOM.render(
<I18nextProvider i18n={i18n}>
{/* ConnectedRouter will use the store from Provider automatically */}
<ConnectedRouter history={history}>
<Index />
<Index />
</ConnectedRouter>
</I18nextProvider>
</Provider>,

View File

@@ -7,8 +7,8 @@ import { isPending } from "./pending";
import { getFailure } from "./failure";
import {
callFetchIndexResources,
FETCH_INDEXRESOURCES_SUCCESS,
fetchIndexResources, fetchIndexResourcesPending,
fetchIndexResources,
fetchIndexResourcesPending,
fetchIndexResourcesSuccess
} from "./indexResource";
@@ -156,7 +156,7 @@ export const login = (
return apiClient
.post(loginLink, login_data)
.then(response => {
dispatch(fetchIndexResourcesPending())
dispatch(fetchIndexResourcesPending());
return callFetchIndexResources();
})
.then(response => {

View File

@@ -1,9 +1,9 @@
//@flow
import React from "react";
import {Link} from "react-router-dom";
import { Link } from "react-router-dom";
import injectSheet from "react-jss";
import type {Repository} from "@scm-manager/ui-types";
import {DateFromNow} from "@scm-manager/ui-components";
import type { Repository } from "@scm-manager/ui-types";
import { DateFromNow } from "@scm-manager/ui-components";
import RepositoryEntryLink from "./RepositoryEntryLink";
import classNames from "classnames";
import RepositoryAvatar from "./RepositoryAvatar";
@@ -45,7 +45,7 @@ class RepositoryEntry extends React.Component<Props> {
return (
<RepositoryEntryLink
iconClass="fa-code-branch"
to={repositoryLink + "/history"}
to={repositoryLink + "/changesets"}
/>
);
}

View File

@@ -7,7 +7,6 @@ import FileTreeLeaf from "./FileTreeLeaf";
import type { Repository, File } from "@scm-manager/ui-types";
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
import {
fetchSources,
getFetchSourcesFailure,
isFetchSourcesPending,
getSources
@@ -29,7 +28,6 @@ type Props = {
revision: string,
path: string,
baseUrl: string,
fetchSources: (Repository, string, string) => void,
// context props
classes: any,
t: string => string,
@@ -49,19 +47,6 @@ export function findParent(path: string) {
}
class FileTree extends React.Component<Props> {
componentDidMount() {
const { fetchSources, repository, revision, path } = this.props;
fetchSources(repository, revision, path);
}
componentDidUpdate(prevProps) {
const { fetchSources, repository, revision, path } = this.props;
if (prevProps.revision !== revision || prevProps.path !== path) {
fetchSources(repository, revision, path);
}
}
render() {
const {
error,
@@ -167,18 +152,7 @@ const mapStateToProps = (state: any, ownProps: Props) => {
};
};
const mapDispatchToProps = dispatch => {
return {
fetchSources: (repository: Repository, revision: string, path: string) => {
dispatch(fetchSources(repository, revision, path));
}
};
};
export default compose(
withRouter,
connect(
mapStateToProps,
mapDispatchToProps
)
connect(mapStateToProps)
)(injectSheet(styles)(translate("repos")(FileTree)));

View File

@@ -49,14 +49,18 @@ class FileTreeLeaf extends React.Component<Props> {
</Link>
);
}
return <FileIcon file={file} />;
return (
<Link to={this.createLink(file)}>
<FileIcon file={file} />
</Link>
);
};
createFileName = (file: File) => {
if (file.directory) {
return <Link to={this.createLink(file)}>{file.name}</Link>;
}
return file.name;
return <Link to={this.createLink(file)}>{file.name}</Link>;
};
render() {

View File

@@ -0,0 +1,26 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import type { File } from "@scm-manager/ui-types";
import { DownloadButton } from "@scm-manager/ui-components";
type Props = {
t: string => string,
file: File
};
class DownloadViewer extends React.Component<Props> {
render() {
const { t, file } = this.props;
return (
<div className="has-text-centered">
<DownloadButton
url={file._links.self.href}
displayName={t("sources.content.downloadButton")}
/>
</div>
);
}
}
export default translate("repos")(DownloadViewer);

View File

@@ -0,0 +1,24 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import type { File } from "@scm-manager/ui-types";
type Props = {
t: string => string,
file: File
};
class ImageViewer extends React.Component<Props> {
render() {
const { file } = this.props;
return (
<div className="has-text-centered">
<figure>
<img src={file._links.self.href} alt={file._links.self.href} />
</figure>
</div>
);
}
}
export default translate("repos")(ImageViewer);

View File

@@ -0,0 +1,97 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import { apiClient } from "@scm-manager/ui-components";
import type { File } from "@scm-manager/ui-types";
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
import SyntaxHighlighter from "react-syntax-highlighter";
import { arduinoLight } from "react-syntax-highlighter/styles/hljs";
type Props = {
t: string => string,
file: File,
language: string
};
type State = {
content: string,
error?: Error,
loaded: boolean
};
class SourcecodeViewer extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
content: "",
loaded: false
};
}
componentDidMount() {
const { file } = this.props;
getContent(file._links.self.href)
.then(result => {
if (result.error) {
this.setState({
...this.state,
error: result.error,
loaded: true
});
} else {
this.setState({
...this.state,
content: result,
loaded: true
});
}
})
.catch(err => {});
}
render() {
const { content, error, loaded } = this.state;
const language = this.props.language;
if (error) {
return <ErrorNotification error={error} />;
}
if (!loaded) {
return <Loading />;
}
if (!content) {
return null;
}
return (
<SyntaxHighlighter
showLineNumbers="true"
language={getLanguage(language)}
style={arduinoLight}
>
{content}
</SyntaxHighlighter>
);
}
}
export function getLanguage(language: string) {
return language.toLowerCase();
}
export function getContent(url: string) {
return apiClient
.get(url)
.then(response => response.text())
.then(response => {
return response;
})
.catch(err => {
return { error: err };
});
}
export default translate("repos")(SourcecodeViewer);

View File

@@ -0,0 +1,33 @@
//@flow
import fetchMock from "fetch-mock";
import {
getContent,
getLanguage
} from "./SourcecodeViewer";
describe("get content", () => {
const CONTENT_URL = "/repositories/scmadmin/TestRepo/content/testContent";
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it("should return content", done => {
fetchMock.getOnce("/api/v2" + CONTENT_URL, "This is a testContent");
getContent(CONTENT_URL).then(content => {
expect(content).toBe("This is a testContent");
done();
});
});
});
describe("get correct language type", () => {
it("should return javascript", () => {
expect(getLanguage("JAVASCRIPT")).toBe("javascript");
});
it("should return nothing for plain text", () => {
expect(getLanguage("")).toBe("");
});
});

View File

@@ -0,0 +1,217 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import { getSources } from "../modules/sources";
import type { Repository, File } from "@scm-manager/ui-types";
import {
ErrorNotification,
Loading,
DateFromNow
} from "@scm-manager/ui-components";
import { connect } from "react-redux";
import ImageViewer from "../components/content/ImageViewer";
import SourcecodeViewer from "../components/content/SourcecodeViewer";
import DownloadViewer from "../components/content/DownloadViewer";
import FileSize from "../components/FileSize";
import injectSheet from "react-jss";
import classNames from "classnames";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { getContentType } from "./contentType";
type Props = {
loading: boolean,
error: Error,
file: File,
repository: Repository,
revision: string,
path: string,
classes: any,
t: string => string
};
type State = {
contentType: string,
language: string,
loaded: boolean,
collapsed: boolean,
error?: Error
};
const styles = {
toCenterContent: {
display: "block"
},
pointer: {
cursor: "pointer"
}
};
class Content extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
contentType: "",
language: "",
loaded: false,
collapsed: true
};
}
componentDidMount() {
const { file } = this.props;
getContentType(file._links.self.href)
.then(result => {
if (result.error) {
this.setState({
...this.state,
error: result.error,
loaded: true
});
} else {
this.setState({
...this.state,
contentType: result.type,
language: result.language,
loaded: true
});
}
})
.catch(err => {});
}
toggleCollapse = () => {
this.setState(prevState => ({
collapsed: !prevState.collapsed
}));
};
showHeader() {
const { file, classes } = this.props;
const collapsed = this.state.collapsed;
const icon = collapsed ? "fa-angle-right" : "fa-angle-down";
const fileSize = file.directory ? "" : <FileSize bytes={file.length} />;
return (
<span className={classes.pointer} onClick={this.toggleCollapse}>
<article className="media">
<div className="media-left">
<i className={classNames("fa", icon)} />
</div>
<div className="media-content">
<div className="content">{file.name}</div>
</div>
<p className="media-right">{fileSize}</p>
</article>
</span>
);
}
showMoreInformation() {
const collapsed = this.state.collapsed;
const { classes, file, revision } = this.props;
const date = <DateFromNow date={file.lastModified} />;
const description = file.description ? (
<p>
{file.description.split("\n").map((item, key) => {
return (
<span key={key}>
{item}
<br />
</span>
);
})}
</p>
) : null;
if (!collapsed) {
return (
<div className={classNames("panel-block", classes.toCenterContent)}>
<table className="table">
<tbody>
<tr>
<td>Path</td>
<td>{file.path}</td>
</tr>
<tr>
<td>Branch</td>
<td>{revision}</td>
</tr>
<tr>
<td>Last modified</td>
<td>{date}</td>
</tr>
<tr>
<td>Description</td>
<td>{description}</td>
</tr>
</tbody>
</table>
</div>
);
}
return null;
}
showContent() {
const { file, revision } = this.props;
const { contentType, language } = this.state;
if (contentType.startsWith("image/")) {
return <ImageViewer file={file} />;
} else if (language) {
return <SourcecodeViewer file={file} language={language} />;
} else if (contentType.startsWith("text/")) {
return <SourcecodeViewer file={file} language="none" />;
} else {
return (
<ExtensionPoint
name="repos.sources.view"
props={{ file, contentType, revision }}
>
<DownloadViewer file={file} />
</ExtensionPoint>
);
}
}
render() {
const { file, classes } = this.props;
const { loaded, error } = this.state;
if (!file || !loaded) {
return <Loading />;
}
if (error) {
return <ErrorNotification error={error} />;
}
const header = this.showHeader();
const content = this.showContent();
const moreInformation = this.showMoreInformation();
return (
<div>
<nav className="panel">
<article className="panel-heading">{header}</article>
{moreInformation}
<div className={classNames("panel-block", classes.toCenterContent)}>
{content}
</div>
</nav>
</div>
);
}
}
const mapStateToProps = (state: any, ownProps: Props) => {
const { repository, revision, path } = ownProps;
const file = getSources(state, repository, revision, path);
return {
file
};
};
export default injectSheet(styles)(
connect(mapStateToProps)(translate("repos")(Content))
);

View File

@@ -13,6 +13,8 @@ import {
isFetchBranchesPending
} from "../../modules/branches";
import { compose } from "redux";
import Content from "./Content";
import { fetchSources, isDirectory } from "../modules/sources";
type Props = {
repository: Repository,
@@ -22,9 +24,11 @@ type Props = {
branches: Branch[],
revision: string,
path: string,
currentFileIsDirectory: boolean,
// dispatch props
fetchBranches: Repository => void,
fetchSources: (Repository, string, string) => void,
// Context props
history: any,
@@ -33,14 +37,26 @@ type Props = {
class Sources extends React.Component<Props> {
componentDidMount() {
const { fetchBranches, repository } = this.props;
const {
fetchBranches,
repository,
revision,
path,
fetchSources
} = this.props;
fetchBranches(repository);
fetchSources(repository, revision, path);
}
componentDidUpdate(prevProps) {
const { fetchSources, repository, revision, path } = this.props;
if (prevProps.revision !== revision || prevProps.path !== path) {
fetchSources(repository, revision, path);
}
}
branchSelected = (branch?: Branch) => {
const { baseUrl, history, path } = this.props;
let url;
if (branch) {
if (path) {
@@ -55,7 +71,15 @@ class Sources extends React.Component<Props> {
};
render() {
const { repository, baseUrl, loading, error, revision, path } = this.props;
const {
repository,
baseUrl,
loading,
error,
revision,
path,
currentFileIsDirectory
} = this.props;
if (error) {
return <ErrorNotification error={error} />;
@@ -65,21 +89,28 @@ class Sources extends React.Component<Props> {
return <Loading />;
}
return (
<>
{this.renderBranchSelector()}
<FileTree
repository={repository}
revision={revision}
path={path}
baseUrl={baseUrl}
/>
</>
);
if (currentFileIsDirectory) {
return (
<>
{this.renderBranchSelector()}
<FileTree
repository={repository}
revision={revision}
path={path}
baseUrl={baseUrl}
/>
</>
);
} else {
return (
<Content repository={repository} revision={revision} path={path} />
);
}
}
renderBranchSelector = () => {
const { repository, branches, revision } = this.props;
if (repository._links.branches) {
return (
<BranchSelector
@@ -99,10 +130,12 @@ const mapStateToProps = (state, ownProps) => {
const { repository, match } = ownProps;
const { revision, path } = match.params;
const decodedRevision = revision ? decodeURIComponent(revision) : undefined;
const loading = isFetchBranchesPending(state, repository);
const error = getFetchBranchesFailure(state, repository);
const branches = getBranches(state, repository);
const currentFileIsDirectory = decodedRevision
? isDirectory(state, repository, decodedRevision, path)
: isDirectory(state, repository, revision, path);
return {
repository,
@@ -110,7 +143,8 @@ const mapStateToProps = (state, ownProps) => {
path,
loading,
error,
branches
branches,
currentFileIsDirectory
};
};
@@ -118,6 +152,9 @@ const mapDispatchToProps = dispatch => {
return {
fetchBranches: (repository: Repository) => {
dispatch(fetchBranches(repository));
},
fetchSources: (repository: Repository, revision: string, path: string) => {
dispatch(fetchSources(repository, revision, path));
}
};
};

View File

@@ -0,0 +1,16 @@
//@flow
import { apiClient } from "@scm-manager/ui-components";
export function getContentType(url: string) {
return apiClient
.head(url)
.then(response => {
return {
type: response.headers.get("Content-Type"),
language: response.headers.get("X-Programming-Language")
};
})
.catch(err => {
return { error: err };
});
}

View File

@@ -0,0 +1,29 @@
//@flow
import fetchMock from "fetch-mock";
import { getContentType } from "./contentType";
describe("get content type", () => {
const CONTENT_URL = "/repositories/scmadmin/TestRepo/content/testContent";
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it("should return content", done => {
let headers = {
"Content-Type": "application/text",
"X-Programming-Language": "JAVA"
};
fetchMock.head("/api/v2" + CONTENT_URL, {
headers
});
getContentType(CONTENT_URL).then(content => {
expect(content.type).toBe("application/text");
expect(content.language).toBe("JAVA");
done();
});
});
});

View File

@@ -102,6 +102,20 @@ export default function reducer(
// selectors
export function isDirectory(
state: any,
repository: Repository,
revision: string,
path: string
): boolean {
const currentFile = getSources(state, repository, revision, path);
if (currentFile && !currentFile.directory) {
return false;
} else {
return true; //also return true if no currentFile is found since it is the "default" path
}
}
export function getSources(
state: any,
repository: Repository,

View File

@@ -1,6 +1,6 @@
// @flow
import type { Repository } from "@scm-manager/ui-types";
import type { Repository, File } from "@scm-manager/ui-types";
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
@@ -14,7 +14,8 @@ import {
isFetchSourcesPending,
default as reducer,
getSources,
fetchSourcesSuccess
fetchSourcesSuccess,
isDirectory
} from "./sources";
const sourcesUrl =
@@ -79,6 +80,21 @@ const collection = {
}
};
const noDirectory: File = {
name: "src",
path: "src",
directory: true,
length: 176,
revision: "abc",
_links: {
self: {
href:
"http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/src"
}
},
_embedded: collection
};
describe("sources fetch", () => {
const mockStore = configureMockStore([thunk]);
@@ -168,6 +184,28 @@ describe("reducer tests", () => {
});
describe("selector tests", () => {
it("should return false if it is no directory", () => {
const state = {
sources: {
"scm/core/abc/src/main/package.json": {
noDirectory
}
}
};
expect(
isDirectory(state, repository, "abc", "src/main/package.json")
).toBeFalsy();
});
it("should return true if it is directory", () => {
const state = {
sources: {
"scm/core/abc/src": noDirectory
}
};
expect(isDirectory(state, repository, "abc", "src")).toBe(true);
});
it("should return null", () => {
expect(getSources({}, repository)).toBeFalsy();
});
@@ -181,7 +219,7 @@ describe("selector tests", () => {
expect(getSources(state, repository)).toBe(collection);
});
it("should return the source collection without revision and path", () => {
it("should return the source collection with revision and path", () => {
const state = {
sources: {
"scm/core/abc/src/main": collection

View File

@@ -727,9 +727,9 @@
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
integrity sha512-0Xp8yMaK4RA7nxgF5/Q8hC7ATfwHZLtGogPeEAEHyT+HI5HqWeQpwd+vPuoD1QveoypNDjqDoctmFKdzi26Q+Q==
"@scm-manager/ui-bundler@^0.0.17":
version "0.0.17"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb"
"@scm-manager/ui-bundler@^0.0.19":
version "0.0.19"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae"
integrity sha512-k8Hp73HlF+Me3l0ZwmU0SPVadYgYSBL5/bE/9PASO+mdm7dfkjgPIvOPfdXT8GlWwzFt0RIk7ROMf4UEr7Q1ow==
dependencies:
"@babel/core" "^7.0.0"
@@ -1352,7 +1352,7 @@ babel-register@^6.26.0:
mkdirp "^0.5.1"
source-map-support "^0.4.15"
babel-runtime@^6.22.0, babel-runtime@^6.26.0:
babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
@@ -1990,6 +1990,18 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
character-entities-legacy@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz#7c6defb81648498222c9855309953d05f4d63a9c"
character-entities@^1.0.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.2.tgz#58c8f371c0774ef0ba9b2aca5f00d8f100e6e363"
character-reference-invalid@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed"
chardet@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
@@ -2105,6 +2117,14 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
clipboard@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.1.tgz#a12481e1c13d8a50f5f036b0560fe5d16d74e46a"
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
tiny-emitter "^2.0.0"
cliui@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
@@ -2231,6 +2251,12 @@ combined-stream@^1.0.5, combined-stream@~1.0.5, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
comma-separated-tokens@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.5.tgz#b13793131d9ea2d2431cf5b507ddec258f0ce0db"
dependencies:
trim "0.0.1"
commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0:
version "2.18.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970"
@@ -2675,6 +2701,10 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
delegate@^3.1.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@@ -3533,6 +3563,12 @@ fast-xml-parser@^3.12.0:
dependencies:
nimnjs "^1.3.2"
fault@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa"
dependencies:
format "^0.2.2"
fb-watchman@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
@@ -3775,6 +3811,10 @@ form-data@~2.3.1, form-data@~2.3.2:
combined-stream "1.0.6"
mime-types "^2.1.12"
format@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
fragment-cache@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
@@ -4124,6 +4164,12 @@ glogg@^1.0.0:
dependencies:
sparkles "^1.0.0"
good-listener@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
dependencies:
delegate "^3.1.2"
got@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a"
@@ -4401,6 +4447,19 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
hast-util-parse-selector@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.0.tgz#2175f18cdd697308fc3431d5c29a9e48dfa4817a"
hastscript@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-4.1.0.tgz#ea5593fa6f6709101fc790ced818393ddaa045ce"
dependencies:
comma-separated-tokens "^1.0.0"
hast-util-parse-selector "^2.2.0"
property-information "^4.0.0"
space-separated-tokens "^1.0.0"
hawk@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
@@ -4411,6 +4470,10 @@ hawk@~3.1.3:
hoek "2.x.x"
sntp "1.x.x"
highlight.js@~9.12.0:
version "9.12.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
history@^4.7.2:
version "4.7.2"
resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b"
@@ -4759,6 +4822,17 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-alphabetical@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.2.tgz#1fa6e49213cb7885b75d15862fb3f3d96c884f41"
is-alphanumerical@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz#1138e9ae5040158dc6ff76b820acd6b7a181fd40"
dependencies:
is-alphabetical "^1.0.0"
is-decimal "^1.0.0"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@@ -4819,6 +4893,10 @@ is-date-object@^1.0.1:
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=
is-decimal@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.2.tgz#894662d6a8709d307f3a276ca4339c8fa5dff0ff"
is-descriptor@^0.1.0:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@@ -4921,6 +4999,10 @@ is-glob@^4.0.0:
dependencies:
is-extglob "^2.1.1"
is-hexadecimal@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835"
is-in-browser@^1.0.2, is-in-browser@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
@@ -6168,6 +6250,13 @@ lowercase-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
lowlight@~1.9.1:
version "1.9.2"
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.9.2.tgz#0b9127e3cec2c3021b7795dd81005c709a42fdd1"
dependencies:
fault "^1.0.2"
highlight.js "~9.12.0"
lru-cache@2:
version "2.7.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
@@ -7167,6 +7256,17 @@ parse-asn1@^5.0.0:
evp_bytestokey "^1.0.0"
pbkdf2 "^3.0.3"
parse-entities@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.0.tgz#9deac087661b2e36814153cb78d7e54a4c5fd6f4"
dependencies:
character-entities "^1.0.0"
character-entities-legacy "^1.0.0"
character-reference-invalid "^1.0.0"
is-alphanumerical "^1.0.0"
is-decimal "^1.0.0"
is-hexadecimal "^1.0.0"
parse-filepath@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891"
@@ -7500,6 +7600,12 @@ pretty-hrtime@^1.0.0:
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=
prismjs@^1.8.4, prismjs@~1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.15.0.tgz#8801d332e472091ba8def94976c8877ad60398d9"
optionalDependencies:
clipboard "^2.0.0"
private@^0.1.6, private@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -7541,6 +7647,12 @@ prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
loose-envify "^1.3.1"
object-assign "^4.1.1"
property-information@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-4.2.0.tgz#f0e66e07cbd6fed31d96844d958d153ad3eb486e"
dependencies:
xtend "^4.0.1"
ps-tree@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014"
@@ -7781,6 +7893,16 @@ react-router@^4.2.0, react-router@^4.3.1:
prop-types "^15.6.1"
warning "^4.0.1"
react-syntax-highlighter@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-9.0.1.tgz#cad91692e1976f68290f24762ac3451b1fec3d26"
dependencies:
babel-runtime "^6.18.0"
highlight.js "~9.12.0"
lowlight "~1.9.1"
prismjs "^1.8.4"
refractor "^2.4.1"
react-test-renderer@^16.0.0-0, react-test-renderer@^16.4.1:
version "16.5.2"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.5.2.tgz#92e9d2c6f763b9821b2e0b22f994ee675068b5ae"
@@ -7967,6 +8089,14 @@ redux@^4.0.0:
loose-envify "^1.1.0"
symbol-observable "^1.2.0"
refractor@^2.4.1:
version "2.6.0"
resolved "https://registry.yarnpkg.com/refractor/-/refractor-2.6.0.tgz#6b0d88f654c8534eefed3329a35bc7bb74ae0979"
dependencies:
hastscript "^4.0.0"
parse-entities "^1.1.2"
prismjs "~1.15.0"
regenerate-unicode-properties@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c"
@@ -8382,6 +8512,10 @@ scss-tokenizer@^0.2.3:
js-base64 "^2.1.8"
source-map "^0.4.2"
select@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1:
version "5.5.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
@@ -8690,6 +8824,12 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
space-separated-tokens@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412"
dependencies:
trim "0.0.1"
sparkles@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c"
@@ -9149,6 +9289,10 @@ timers-ext@^0.1.5:
es5-ext "~0.10.46"
next-tick "1"
tiny-emitter@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c"
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -9248,6 +9392,10 @@ trim-right@^1.0.1:
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
trim@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd"
"true-case-path@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d"

View File

@@ -103,7 +103,6 @@
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>test</scope>
</dependency>
<!-- rest api -->

View File

@@ -0,0 +1,20 @@
package sonia.scm;
import com.google.common.collect.ImmutableMap;
import com.google.inject.servlet.ServletModule;
import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
import javax.inject.Singleton;
import java.util.Map;
public class ResteasyModule extends ServletModule {
@Override
protected void configureServlets() {
bind(HttpServletDispatcher.class).in(Singleton.class);
Map<String, String> initParams = ImmutableMap.of(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, "/api");
serve("/api/*").with(HttpServletDispatcher.class, initParams);
}
}

View File

@@ -126,6 +126,7 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
ClassOverrides overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader());
List<Module> moduleList = Lists.newArrayList();
moduleList.add(new ResteasyModule());
moduleList.add(new ScmInitializerModule());
moduleList.add(new ScmEventBusModule());
moduleList.add(new EagerSingletonModule());

View File

@@ -57,5 +57,9 @@ public class TemplatingPushStateDispatcher implements PushStateDispatcher {
return request.getContextPath();
}
public String getLiveReloadURL() {
return System.getProperty("livereload.url");
}
}
}

View File

@@ -136,7 +136,7 @@ public class ContentResource {
private void appendContentHeader(String path, byte[] head, Response.ResponseBuilder responseBuilder) {
ContentType contentType = ContentTypes.detect(path, head);
responseBuilder.header("Content-Type", contentType.getRaw());
contentType.getLanguage().ifPresent(language -> responseBuilder.header("Language", language));
contentType.getLanguage().ifPresent(language -> responseBuilder.header("X-Programming-Language", language));
}
private byte[] getHead(String revision, String path, RepositoryService repositoryService) throws IOException, PathNotFoundException, RevisionNotFoundException {

View File

@@ -34,22 +34,19 @@ package sonia.scm.boot;
//~--- non-JDK imports --------------------------------------------------------
import com.github.legman.Subscribe;
import com.google.inject.servlet.GuiceFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContext;
import sonia.scm.Stage;
import sonia.scm.event.ScmEventBus;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.FilterConfig;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletException;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
@@ -65,6 +62,8 @@ public class BootstrapContextFilter extends GuiceFilter
//~--- methods --------------------------------------------------------------
private final BootstrapContextListener listener = new BootstrapContextListener();
/**
* Restart the whole webapp context.
*
@@ -85,29 +84,20 @@ public class BootstrapContextFilter extends GuiceFilter
}
else
{
logger.warn(
"destroy filter pipeline, because of a received restart event");
logger.warn("destroy filter pipeline, because of a received restart event");
destroy();
logger.warn(
"reinitialize filter pipeline, because of a received restart event");
super.init(filterConfig);
logger.warn("reinitialize filter pipeline, because of a received restart event");
initGuice();
}
}
/**
* Method description
*
*
* @param filterConfig
*
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
this.filterConfig = filterConfig;
super.init(filterConfig);
initGuice();
if (SCMContext.getContext().getStage() == Stage.DEVELOPMENT)
{
@@ -116,6 +106,19 @@ public class BootstrapContextFilter extends GuiceFilter
}
}
public void initGuice() throws ServletException {
super.init(filterConfig);
listener.contextInitialized(new ServletContextEvent(filterConfig.getServletContext()));
}
@Override
public void destroy() {
super.destroy();
listener.contextDestroyed(new ServletContextEvent(filterConfig.getServletContext()));
ServletContextCleaner.cleanup(filterConfig.getServletContext());
}
//~--- fields ---------------------------------------------------------------
/** Field description */

View File

@@ -148,13 +148,15 @@ public class BootstrapContextListener implements ServletContextListener
{
context = sce.getServletContext();
PluginIndex index = readCorePluginIndex(context);
File pluginDirectory = getPluginDirectory();
try
{
extractCorePlugins(context, pluginDirectory, index);
if (!isCorePluginExtractionDisabled()) {
extractCorePlugins(context, pluginDirectory);
} else {
logger.info("core plugin extraction is disabled");
}
ClassLoader cl =
ClassLoaders.getContextClassLoader(BootstrapContextListener.class);
@@ -181,31 +183,8 @@ public class BootstrapContextListener implements ServletContextListener
}
}
/**
* Restart the whole webapp context.
*
*
* @param event restart event
*/
@Subscribe
public void handleRestartEvent(RestartEvent event)
{
logger.warn("received restart event from {} with reason: {}",
event.getCause(), event.getReason());
if (context == null)
{
logger.error("context is null, scm-manager is not initialized");
}
else
{
ServletContextEvent sce = new ServletContextEvent(context);
logger.warn("destroy context, because of a received restart event");
contextDestroyed(sce);
logger.warn("reinitialize context, because of a received restart event");
contextInitialized(sce);
}
private boolean isCorePluginExtractionDisabled() {
return Boolean.getBoolean("sonia.scm.boot.disable-core-plugin-extraction");
}
/**
@@ -214,7 +193,6 @@ public class BootstrapContextListener implements ServletContextListener
*
* @param context
* @param pluginDirectory
* @param name
* @param entry
*
* @throws IOException
@@ -269,17 +247,15 @@ public class BootstrapContextListener implements ServletContextListener
*
* @param context
* @param pluginDirectory
* @param lines
* @param index
*
* @throws IOException
*/
private void extractCorePlugins(ServletContext context, File pluginDirectory,
PluginIndex index)
throws IOException
private void extractCorePlugins(ServletContext context, File pluginDirectory) throws IOException
{
IOUtil.mkdirs(pluginDirectory);
PluginIndex index = readCorePluginIndex(context);
for (PluginIndexEntry entry : index)
{
extractCorePlugin(context, pluginDirectory, entry);

View File

@@ -0,0 +1,97 @@
package sonia.scm.boot;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.Priority;
import sonia.scm.SCMContext;
import sonia.scm.Stage;
import sonia.scm.event.ScmEventBus;
import sonia.scm.filter.WebElement;
import javax.inject.Inject;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* This servlet sends a {@link RestartEvent} to the {@link ScmEventBus} which causes scm-manager to restart the context.
* The {@link RestartServlet} can be used for reloading java code or for installing plugins without a complete restart.
* At the moment the Servlet accepts only request, if scm-manager was started in the {@link Stage#DEVELOPMENT} stage.
*
* @since 2.0.0
*/
@Priority(0)
@WebElement("/restart")
public class RestartServlet extends HttpServlet {
private static final Logger LOG = LoggerFactory.getLogger(RestartServlet.class);
private final ObjectMapper objectMapper = new ObjectMapper();
private final AtomicBoolean restarting = new AtomicBoolean();
private final ScmEventBus eventBus;
private final Stage stage;
@Inject
public RestartServlet() {
this(ScmEventBus.getInstance(), SCMContext.getContext().getStage());
}
RestartServlet(ScmEventBus eventBus, Stage stage) {
this.eventBus = eventBus;
this.stage = stage;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
LOG.info("received sendRestartEvent request");
if (isRestartAllowed()) {
try (InputStream requestInput = req.getInputStream()) {
Reason reason = objectMapper.readValue(requestInput, Reason.class);
sendRestartEvent(resp, reason);
} catch (IOException ex) {
LOG.warn("failed to trigger sendRestartEvent event", ex);
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
} else {
LOG.debug("received restart event in non development stage");
resp.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
}
}
private boolean isRestartAllowed() {
return stage == Stage.DEVELOPMENT;
}
private void sendRestartEvent(HttpServletResponse response, Reason reason) {
if ( restarting.compareAndSet(false, true) ) {
LOG.info("trigger sendRestartEvent, because of {}", reason.getMessage());
eventBus.post(new RestartEvent(RestartServlet.class, reason.getMessage()));
response.setStatus(HttpServletResponse.SC_ACCEPTED);
} else {
LOG.warn("scm-manager restarts already");
response.setStatus(HttpServletResponse.SC_CONFLICT);
}
}
public static class Reason {
private String message;
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
}

View File

@@ -0,0 +1,59 @@
package sonia.scm.boot;
import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletContext;
import java.util.Enumeration;
import java.util.Set;
/**
* Remove cached resources from {@link ServletContext} to allow a clean restart of scm-manager without stale or
* duplicated data.
*
* @since 2.0.0
*/
final class ServletContextCleaner {
private static final Logger LOG = LoggerFactory.getLogger(ServletContextCleaner.class);
private static final Set<String> REMOVE_PREFIX = ImmutableSet.of(
"org.jboss.resteasy",
"resteasy",
"org.apache.shiro",
"sonia.scm"
);
private ServletContextCleaner() {
}
/**
* Remove cached attributes from {@link ServletContext}.
*
* @param servletContext servlet context
*/
static void cleanup(ServletContext servletContext) {
LOG.info("remove cached attributes from context");
Enumeration<String> attributeNames = servletContext.getAttributeNames();
while( attributeNames.hasMoreElements()) {
String name = attributeNames.nextElement();
if (shouldRemove(name)) {
LOG.info("remove attribute {} from servlet context", name);
servletContext.removeAttribute(name);
} else {
LOG.info("keep attribute {} in servlet context", name);
}
}
}
private static boolean shouldRemove(String name) {
for (String prefix : REMOVE_PREFIX) {
if (name.startsWith(prefix)) {
return true;
}
}
return false;
}
}

View File

@@ -41,6 +41,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContext;
import sonia.scm.Stage;
import javax.servlet.ServletContext;
import java.net.MalformedURLException;
@@ -69,19 +71,21 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param servletContext
* @param plugins
*/
public DefaultUberWebResourceLoader(ServletContext servletContext,
Iterable<PluginWrapper> plugins)
{
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<PluginWrapper> plugins) {
this(servletContext, plugins, SCMContext.getContext().getStage());
}
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<PluginWrapper> plugins, Stage stage) {
this.servletContext = servletContext;
this.plugins = plugins;
this.cache = CacheBuilder.newBuilder().build();
this.cache = createCache(stage);
}
private Cache<String, URL> createCache(Stage stage) {
if (stage == Stage.DEVELOPMENT) {
return CacheBuilder.newBuilder().maximumSize(0).build(); // Disable caching
}
return CacheBuilder.newBuilder().build();
}
//~--- get methods ----------------------------------------------------------
@@ -97,7 +101,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
@Override
public URL getResource(String path)
{
URL resource = cache.getIfPresent(path);
URL resource = getFromCache(path);
if (resource == null)
{
@@ -105,7 +109,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
if (resource != null)
{
cache.put(path, resource);
addToCache(path, resource);
}
}
else
@@ -116,6 +120,14 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
return resource;
}
private URL getFromCache(String path) {
return cache.getIfPresent(path);
}
private void addToCache(String path, URL url) {
cache.put(path, url);
}
/**
* Method description
*

View File

@@ -65,9 +65,6 @@
<logger name="sonia.scm.event.LegmanScmEventBus" level="DEBUG" />
<logger name="sonia.scm.plugin.ext.DefaultAnnotationScanner" level="INFO" />
<logger name="sonia.scm.security.ConfigurableLoginAttemptHandler" level="DEBUG" />
<!-- event bus -->
<logger name="sonia.scm.event.LegmanScmEventBus" level="INFO" />
<!-- cgi -->
<logger name="sonia.scm.web.cgi.DefaultCGIExecutor" level="DEBUG" />
@@ -93,7 +90,9 @@
<logger name="net.sf.ehcache" level="DEBUG" />
-->
<logger name="org.jboss.resteasy" level="DEBUG" />
<logger name="org.jboss.resteasy" level="INFO" />
<logger name="sonia.scm.boot.RestartServlet" level="TRACE" />
<root level="WARN">
<appender-ref ref="STDOUT" />

View File

@@ -41,10 +41,6 @@
<!-- bootstraping -->
<listener>
<listener-class>sonia.scm.boot.BootstrapContextListener</listener-class>
</listener>
<filter>
<filter-name>BootstrapFilter</filter-name>
<filter-class>sonia.scm.boot.BootstrapContextFilter</filter-class>
@@ -55,25 +51,6 @@
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- rest -->
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/api</param-value>
</context-param>
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<!-- capture sessions -->
<!--
TODO remove, we need no longer a session

View File

@@ -17,6 +17,7 @@ import java.io.StringWriter;
import java.io.Writer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,6 +46,23 @@ public class TemplatingPushStateDispatcherTest {
@Test
public void testDispatch() throws IOException {
TemplatingPushStateDispatcher.IndexHtmlModel model = dispatch();
assertEquals("/scm", model.getContextPath());
assertNull(model.getLiveReloadURL());
}
@Test
public void testDispatchWithLiveReloadURL() throws IOException {
System.setProperty("livereload.url", "/livereload.js");
try {
TemplatingPushStateDispatcher.IndexHtmlModel model = dispatch();
assertEquals("/livereload.js", model.getLiveReloadURL());
} finally {
System.clearProperty("livereload.url");
}
}
private TemplatingPushStateDispatcher.IndexHtmlModel dispatch() throws IOException {
when(request.getContextPath()).thenReturn("/scm");
when(templateEngine.getTemplate(TemplatingPushStateDispatcher.TEMPLATE)).thenReturn(template);
@@ -59,8 +77,7 @@ public class TemplatingPushStateDispatcherTest {
verify(template).execute(any(Writer.class), captor.capture());
TemplatingPushStateDispatcher.IndexHtmlModel model = (TemplatingPushStateDispatcher.IndexHtmlModel) captor.getValue();
assertEquals("/scm", model.getContextPath());
return (TemplatingPushStateDispatcher.IndexHtmlModel) captor.getValue();
}
}

View File

@@ -93,7 +93,7 @@ public class ContentResourceTest {
Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "SomeGoCode.go");
assertEquals(200, response.getStatus());
assertEquals("GO", response.getHeaderString("Language"));
assertEquals("GO", response.getHeaderString("X-Programming-Language"));
assertEquals("text/x-go", response.getHeaderString("Content-Type"));
}
@@ -104,7 +104,7 @@ public class ContentResourceTest {
Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "Dockerfile");
assertEquals(200, response.getStatus());
assertEquals("DOCKERFILE", response.getHeaderString("Language"));
assertEquals("DOCKERFILE", response.getHeaderString("X-Programming-Language"));
assertEquals("text/plain", response.getHeaderString("Content-Type"));
}

View File

@@ -0,0 +1,133 @@
package sonia.scm.boot;
import com.github.legman.Subscribe;
import com.google.common.base.Charsets;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.Stage;
import sonia.scm.event.ScmEventBus;
import sonia.scm.event.ScmTestEventBus;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class RestartServletTest {
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
private RestartServlet restartServlet;
private EventListener listener;
private void setUpObjectUnderTest(Stage stage) {
listener = new EventListener();
ScmEventBus eventBus = ScmTestEventBus.getInstance();
eventBus.register(listener);
restartServlet = new RestartServlet(eventBus, stage);
}
@Test
public void testRestart() throws IOException {
setUpObjectUnderTest(Stage.DEVELOPMENT);
setRequestInputReason("something changed");
restartServlet.doPost(request, response);
verify(response).setStatus(HttpServletResponse.SC_ACCEPTED);
RestartEvent restartEvent = listener.restartEvent;
assertThat(restartEvent).isNotNull();
assertThat(restartEvent.getCause()).isEqualTo(RestartServlet.class);
assertThat(restartEvent.getReason()).isEqualTo("something changed");
}
@Test
public void testRestartCalledTwice() throws IOException {
setUpObjectUnderTest(Stage.DEVELOPMENT);
setRequestInputReason("initial change");
restartServlet.doPost(request, response);
verify(response).setStatus(HttpServletResponse.SC_ACCEPTED);
setRequestInputReason("changed again");
restartServlet.doPost(request, response);
verify(response).setStatus(HttpServletResponse.SC_CONFLICT);
}
@Test
public void testRestartWithInvalidContent() throws IOException {
setUpObjectUnderTest(Stage.DEVELOPMENT);
setRequestInputContent("invalid json");
restartServlet.doPost(request, response);
verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
@Test
public void testRestartInProductionStage() throws IOException {
setUpObjectUnderTest(Stage.PRODUCTION);
setRequestInputReason("initial change");
restartServlet.doPost(request, response);
verify(response).setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
}
private void setRequestInputReason(String message) throws IOException {
String content = createReason(message);
setRequestInputContent(content);
}
private void setRequestInputContent(String content) throws IOException {
InputStream input = createReasonAsInputStream(content);
when(request.getInputStream()).thenReturn(createServletInputStream(input));
}
private ServletInputStream createServletInputStream(final InputStream inputStream) {
return new ServletInputStream() {
@Override
public int read() throws IOException {
return inputStream.read();
}
};
}
private InputStream createReasonAsInputStream(String content) {
return new ByteArrayInputStream(content.getBytes(Charsets.UTF_8));
}
private String createReason(String message) {
return String.format("{\"message\": \"%s\"}", message);
}
public static class EventListener {
private RestartEvent restartEvent;
@Subscribe(async = false)
public void store(RestartEvent restartEvent) {
this.restartEvent = restartEvent;
}
}
}

View File

@@ -0,0 +1,53 @@
package sonia.scm.boot;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import javax.servlet.ServletContext;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Set;
import java.util.Vector;
import static org.junit.Assert.*;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class ServletContextCleanerTest {
@Mock
private ServletContext servletContext;
@Test
public void testCleanup() {
Set<String> names = ImmutableSet.of(
"org.jboss.resteasy.Dispatcher",
"resteasy.Deployment",
"sonia.scm.Context",
"org.eclipse.jetty.HttpServer",
"javax.servlet.Context",
"org.apache.shiro.SecurityManager"
);
when(servletContext.getAttributeNames()).thenReturn(toEnumeration(names));
ServletContextCleaner.cleanup(servletContext);
verify(servletContext).removeAttribute("org.jboss.resteasy.Dispatcher");
verify(servletContext).removeAttribute("resteasy.Deployment");
verify(servletContext).removeAttribute("sonia.scm.Context");
verify(servletContext, never()).removeAttribute("org.eclipse.jetty.HttpServer");
verify(servletContext, never()).removeAttribute("javax.servlet.Context");
verify(servletContext).removeAttribute("org.apache.shiro.SecurityManager");
}
private <T> Enumeration<T> toEnumeration(Collection<T> collection) {
return new Vector<>(collection).elements();
}
}

View File

@@ -42,6 +42,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.Stage;
import javax.servlet.ServletContext;
import java.io.File;
@@ -96,14 +97,12 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase
* Method description
*
*
* @throws MalformedURLException
*/
@Test
public void testGetResourceFromCache() throws MalformedURLException
{
public void testGetResourceFromCache() {
DefaultUberWebResourceLoader resourceLoader =
new DefaultUberWebResourceLoader(servletContext,
new ArrayList<PluginWrapper>());
new ArrayList<PluginWrapper>(), Stage.PRODUCTION);
resourceLoader.getCache().put("/myresource", GITHUB);
@@ -112,6 +111,15 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase
assertSame(GITHUB, resource);
}
@Test
public void testGetResourceCacheIsDisableInStageDevelopment() throws MalformedURLException {
DefaultUberWebResourceLoader resourceLoader = new DefaultUberWebResourceLoader(servletContext, new ArrayList<>(), Stage.DEVELOPMENT);
when(servletContext.getResource("/scm")).thenAnswer(invocation -> new URL("https://scm-manager.org"));
URL url = resourceLoader.getResource("/scm");
URL secondUrl = resourceLoader.getResource("/scm");
assertNotSame(url, secondUrl);
}
/**
* Method description
*